我的编程空间,编程开发者的网络收藏夹
学习永远不晚

SpringBootStarter依赖原理与实例详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

SpringBootStarter依赖原理与实例详解

1 Starter

在开发 SpringBoot 项目的时候,我们常常通过 Maven 导入自动各种依赖,其中很多依赖都是以 xxx-starter 命名的。

像这种 starter 依赖是怎么工作的呢?

2 了解 spring.factories机制

导入一个依赖,我们就可以调用包内的公共类,这是因为公共类可以被异包调用。很多时候我们添加依赖它会自动往我们的主程序注入一些对象或者监听器,这个是怎么做到的?

2.1 不同包路径下的依赖注入

SpringBoot 默认只扫描启动类所在目录里面的对象

而我们导入的依赖是在另外一个包里,SpringBoot 是扫描不到的!

如何让主项目注入(加载)异包对象呢?通常有两种方法:

  • 在启动类上加上@SpringBootApplication注解,配置scanBasePackages属性,指定扫描路径。
  • resources/META-INF目录下创建spring.factories配置文件,在里面配置需要加载的类

2.2 spring.factories 机制

spring.factories机制是springboot的核心基础之一,这可以描述为一种 可插拔结构,模仿自java中的SPI扩展机制。

spring.factories 实现例子

1.在任意一个项目中新建一个starter模块(springboot项目)

导入 springboot 的自动配置依赖,这里我们主要用到它的@Configuration@Bean注解和ApplicationListener监听器接口

<!-- SpringBoot 自动配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

2.随便创建一个bean

public class User {
    private String name;
    public void setName(String name) { this.name = name; }
    public String getName() { return name; }
}

3.创建一个初始化监听器

一般的starter会在容器启动时做一些初始化的操作,这里作为演示只打印一句话。

public class ApplicationInitialize implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println("应用初始化");
    }
}

4.创建配置类

这个配置类就是主项目注入starter模块依赖的入口,当它扫描到这个配置类的时候就会加载里面的Bean对象

@Configuration
public class StarterGenericConfig {
    @Bean
    public User getUser() {
        User user = new User();
        user.setName("我来自starter模块");
        return user;
    }
    @Bean
    public ApplicationInitialize getAppli() {
        return new ApplicationInitialize();
    }
}

5.创建spring.factories配置文件

配置类有了,但是因为和主项目不同包启动类它扫描不到,这时我们就要通过spring.factories机制让它能扫描到这个配置类,完成依赖注入。

先在resource资源目录下创建META-INF文件夹,然后创建一个名为spring.factories的文件

内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.echoo.cloud.nacos.starter.config.StarterGenericConfig

这里需要配置StarterGenericConfig配置类的全限定名。

把这个项目导入主项目(添加到主项目的pom依赖中),运行主项目,看看是否注入成功

这就是starter依赖注入的基本思路,实际可能复杂得多,需要继续摸索。

3 spring.factories 机制的实现源码分析

springframework 框架中有这样一个类

package org.springframework.core.io.support;
public final class SpringFactoriesLoader {
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
	...
	
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
       		...
            try {
            	// 通过类加载器加载资源目录下的"META-INF/spring.factories"文件
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;
                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }
 	...
}

SpringFactoriesLoaderSpring容器初始化时会加载的一个类,而它的静态方法loadSpringFactories()里会用类加载器去加载资源文件resourece/META-INF/spring.factories,然后读取里面的配置参数(应该都是待加载Bean类的映射数据),用集合封装返回Spring容器,后面应该就是Spring容器加载对应的Bean类。

4 程序运行入口run()

前面知道了Spring容器会加载加载资源文件resourece/META-INF/spring.factories然后加载里面对应的类,那为什么对应的keyorg.springframework.boot.autoconfigure.EnableAutoConfiguration?

先说结论:Spring容器初始化会加载org.springframework.boot.autoconfigure.EnableAutoConfiguration这个类,完了还会去扫描resourece/META-INF/spring.factories加载里面的Bean类,配置文件是键值对形式的,那keyorg.springframework.boot.autoconfigure.EnableAutoConfiguration是因为这个是一个注解,本身就是为了注入拓展类用的,它会在容器初始化或刷新的适当时机注入对应的类。因为扫描不到异包配置类上的@Configuration注解,所以创建了一个@EnableAutoConfiguration注解配合spring.factories配置文件的形式来注入配置类

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch(); // 创建stopWatch对象
        stopWatch.start(); // 开始计算时间
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty(); // 是否使用模拟输入输出设备(默认是,因为服务器不一定有鼠标键盘显示器)
        SpringApplicationRunListeners listeners = this.getRunListeners(args); // 获取并启动监听器
        listeners.starting(); // 获取的监听器为 Event PublishingRunListener,监听并发布启动事件
        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备应用环境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment); // 打印 banner
            context = this.createApplicationContext(); 创建容器
            // 加载 SpringFactories 实例(返回的是实例加载的记录、报告)
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            // 准备上下文环境
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context); // 刷新容器
            this.afterRefresh(context, applicationArguments); // 容器刷新后的动作,这里默认没有做任何实现
            stopWatch.stop(); // 停止计算时间
            if (this.logStartupInfo) { 
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
           ...
        }
        ...
    }

里面有个创建应用容器的方法createApplicationContext(),深入进去发现他是根据webApplicationType类型去决定创建那种容器,而webApplicationType类型在SpringApplication初始化的时候指定。

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }
		// 反射创建容器实例
        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }

这里一共有三种类型的容器

  • SERVLET 类型创建 AnnotationConfigServletWebServerApplicationContextServlet容器)
  • REACTIVE类型创建AnnotationConfigReactiveWebServerApplicationContextReactive容器)
  • 默认创建AnnotationConfigApplicationContextApplication容器)

SpringApplication的构造器中对webApplicationType类型进行了初始化,默认返回SERVLET 类型。

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        ...
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        ...
    }

也就是说默认创建AnnotationConfigServletWebServerApplicationContextServlet容器)

在上面入口方法run()方法中,有一个refreshContext()方式,这个刷新容器的方法里面

跟踪这个refreshContext()底层是一个refresh()方法,三种容器都分别实现了这个方法

这里着重看ServletWebServerApplicationContext.refresh()

    public final void refresh() throws BeansException, IllegalStateException {
        try { super.refresh(); } catch (RuntimeException var3) { ... }
    }

发现它调用的是父类AbstractApplicationContextrefresh()函数

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) { // 上锁,防止并发
            this.prepareRefresh();                  // 刷新准备工作,记录开始时间,校验配置文件
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); // 获取Bean工厂
            this.prepareBeanFactory(beanFactory);   // Bean工厂准备工作,不详谈
            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory); // Spring拓展点之一
                ...
            } catch (BeansException var9) {
                ...
            } finally {
                this.resetCommonCaches();
            }
        }
    }

重点在invokeBeanFactoryPostProcessors(beanFactory)方法上,这是SpringBoot实现Spring拓展的关键节点,这个方法执行时会调用实现了BeanFactoryPostProcessors接口的实现类的postProcessBeanFactory(factory)方法

(也会调用BeanDefinitionRegistryPostProcessor接口的各个实现类的postProcessBeanDefinitionRegistry(registry)方法)

进入invokeBeanFactoryPostProcessors(beanFactory)方法

    public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
   			...
            currentRegistryProcessors = new ArrayList();
            // 获取所有 BeanDefinitionRegistryPostProcessor 接口实现类的全限定名集合
            postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); 
            String[] var16 = postProcessorNames;
            var9 = postProcessorNames.length;
            int var10;
            String ppName;
            for(var10 = 0; var10 < var9; ++var10) {
                ppName = var16[var10];
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                	// 根据 Bean 名获取 Bean 对象放入 currentRegistryProcessors
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory); // 排序,暂时没看,不知道排啥
            registryProcessors.addAll(currentRegistryProcessors);
            // 调用 Bean 定义注册后处理器
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();
			...
	}

每一个Bean类的定义注册是在Spring容器中完成的,在上面invokeBeanFactoryPostProcessors()方法中,通过 Bean 工厂获取 了所有BeanDefinitionRegistryPostProcessor接口的实现类名,然后再通过 invokeBeanDefinitionRegistryPostProcessors()方法调用所有实现类的postProcessBeanDefinitionRegistry()方法去做 Bean 类注册后的相关处理动作。

BeanDefinitionRegistryPostProcessor接口:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}

知道这个有什么用呢?前面我们知道了Springboot容器中的AnnotationConfigServletWebServerApplicationContextServlet容器)是通过反射获取AnnotationConfigServletWebServerApplicationContext的构造器创建实例的,所以我们看看AnnotationConfigServletWebServerApplicationContext的构造器长什么样儿。

    public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
        super(beanFactory);
        this.annotatedClasses = new LinkedHashSet();
        this.reader = new AnnotatedBeanDefinitionReader(this); // 注解 Bean 定义读取器
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

进入AnnotatedBeanDefinitionReader看它的构造器

    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
        ...
        // 通过 AnnotationConfigUtils 工具注册 注解配置处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

再进入AnnotationConfigUtils 工具的registerAnnotationConfigProcessors()看看它是如何注册的

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
	...
	Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet(8); // 存储 BeanDefinitionHolder 对象的集合
    RootBeanDefinition def;
	if (!registry.containsBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor")) {
		// 创建一个 ConfigurationClassPostProcessor 的 RootBeanDefinition 对象
		def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        // 把 ConfigurationClassPostProcessor 的 RootBeanDefinition 对象装入一个 BeanDefinitionHolder (容器)
        // 并映射名字为 org.springframework.context.annotation.internalConfigurationAnnotationProcessor
        beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.annotation.internalConfigurationAnnotationProcessor"));
    }
	...
}

在它的registerAnnotationConfigProcessors()方法中看到了它用注册后处理器registerPostProcessor去注册org.springframework.context.annotation.internalConfigurationAnnotationProcessorBeanDefinition(Bean定义描述对象)和BeanDefinitionHolder(Bean定义描述对象容器),然后返回这个BeanDefinitionHolder(Bean定义描述对象容器)存储到beanDefs(Bean定义描述对象容器集合)里面。

到这里就是说明在初始化AnnotationConfigServletWebServerApplicationContextServlet容器)时,会用org.springframework.context.annotation.internalConfigurationAnnotationProcessor这个名字注册ConfigurationClassPostProcessor这个 Bean对象,然后就能根据它的BeanDefinitionHolder(Bean定义描述对象容器)去创建ConfigurationClassPostProcessor对象。

现在问题就来到了ConfigurationAnnotationProcessor对象身上了,为啥要创建它?因为它就是加载spring.factories配置文件的关键。

进入它的postProcessBeanDefinitionRegistry()方法

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {       // 判断是否有对应的注册记录
            throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        } else if (this.factoriesPostProcessed.contains(registryId)) { // 是否有对应的 Bean 工厂
            throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);
        } else { // 都没有,说明这个 BeanDefinition 没有注册加载过
            this.registriesPostProcessed.add(registryId); // 添加注册记录
            this.processConfigBeanDefinitions(registry);  // 处理这个 BeanDefinition 的配置
        }
    }

深入processConfigBeanDefinitions()看它怎么处理这个 BeanDefinition 的配置

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList(); // 待处理配置集合
        String[] candidateNames = registry.getBeanDefinitionNames();
        String[] var4 = candidateNames;
        int var5 = candidateNames.length;
        for(int var6 = 0; var6 < var5; ++var6) {
            String beanName = var4[var6];
            BeanDefinition beanDef = registry.getBeanDefinition(beanName); // 根据名称获取 BeanDefinition
            // 判断这个 BeanDefinition 的配置属性是不是空
            if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
                if (this.logger.isDebugEnabled()) {
                	// 如果不是空就说明这个 BeanDefinition 已经被当作配置类处理过了
                    this.logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            	// 如果为空,放入待处理配置集合里等待后续处理
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
		// 指令排序相关,不深究
        if (!configCandidates.isEmpty()) {
            configCandidates.sort((bd1, bd2) -> {
                int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                return Integer.compare(i1, i2);
            });
            ...
            // 根据环境创建了一个配置类解析器
            ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
            Set<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidates);
            HashSet alreadyParsed = new HashSet(configCandidates.size());

            do {
                parser.parse(candidates); // 解析配置类
                parser.validate();
                Set<ConfigurationClass> configClasses = new LinkedHashSet(parser.getConfigurationClasses());
                configClasses.removeAll(alreadyParsed);
                if (this.reader == null) {
                    this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
                }
				...
            } while(!candidates.isEmpty())
            ...
        }
    }

进入ConfigurationClassParser.parse()方法,看看它怎么解析配置类

	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try { // 对 BeanDefinition 做解析操作
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}
		// 延迟导入选择器
		this.deferredImportSelectorHandler.process();
	}

解析发现不了什么线索,进入this.deferredImportSelectorHandler.process()延迟导入选择处理器看看

层层追踪:

this.deferredImportSelectorHandler.process()

DeferredImportSelectorGroupingHandler.processGroupImports()

grouping.getImports()

grouping.getImports()

this.group.selectImports()

追踪到public interface ImportSelector {...}接口

在找到它的实现类AutoConfigurationImportSelector

在实现类AutoConfigurationImportSelector里层层追踪

selectImports()

getAutoConfigurationEntry()

getCandidateConfigurations()

getSpringFactoriesLoaderFactoryClass()

最后追踪到 getSpringFactoriesLoaderFactoryClass()方法

    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

里面返回的是一个EnableAutoConfiguration.class类,这个类就是我们在spring.factories配置文件里面配置的org.springframework.boot.autoconfigure.EnableAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.echoo.cloud.nacos.starter.config.StarterGenericConfig

到此这篇关于SpringBoot Starter依赖原理与实例详解的文章就介绍到这了,更多相关SpringBoot Starter依赖内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

SpringBootStarter依赖原理与实例详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Spring循环依赖原理实例分析

本篇内容介绍了“Spring循环依赖原理实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!简介概述@Autowired进行属性注入可以解
2023-07-02

OpenSCA技术原理npm依赖示例解析

这篇文章主要为大家介绍了OpenSCA技术原理npm依赖示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-17

Nest.js之依赖注入原理及实现过程详解

这篇文章主要为大家介绍了Nest.js之依赖注入原理及实现过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-01-12

Gradle依赖切换源码实践示例详解

这篇文章主要为大家介绍了Gradle依赖切换源码实践示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-09

SpringBoot快速入门及起步依赖解析(实例详解)

SpringBoot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化 Spring 应用的初始搭建以及开发过程,这篇文章主要介绍了SpringBoot快速入门及起步依赖解析,需要的朋友可以参考下
2022-11-13

python实现图像处理之PiL依赖库的案例应用详解

Python实现图像处理:PiL依赖库的应用 本文包含的练习题主要是PIL依赖库,即pillow相关的应用。 练习一:使用python给图片增加数字 实现思路:使用PIL的Image.open导入图片。获取图片的大小。调用ImageDraw
2022-06-02

Spring依赖注入与第三方Bean管理基础详解

依赖注入(DependencyInjection)和控制反转(InversionofControl)是同一个概念。具体含义是:当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例
2022-12-23

详解PNPM Monorepo依赖项管理功能模拟实现

这篇文章主要介绍了PNPM Monorepo依赖项管理功能模拟实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-10

mysql视图原理与用法实例详解

本文实例讲述了mysql视图原理与用法。分享给大家供大家参考,具体如下: 本文内容:什么是视图创建视图查看视图视图的修改视图的删除视图的数据操作首发日期:2018-04-13什么是视图:视图是一种基于查询结果的虚拟表,数据来源的表称为基本表
2022-05-29

Redis 实现队列原理的实例详解

Redis 实现队列原理的实例详解 场景说明: ·用于处理比较耗时的请求,例如批量发送邮件,如果直接在网页触发执行发送,程序会出现超时 ·高并发场景,当某个时刻请求瞬间增加时,可以把请求写入到队列,后台在去处理这些请求 ·抢购场景,先入先出
2022-06-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录