开发手机网站用什么语言,织梦电影网站免费模板,一站式营销型网站建设服务,wordpress会员文章Spring Boot 自动装配原理
使用Spring Boot最方便的一点体验在于我们可以几零配置的搭建一个Spring Web项目#xff0c;那么他是怎么做到不通过配置来对Bean完成注入的呢。这就要归功于Spring Boot的自动装配实现#xff0c;他也是Spring Boot中各个Starter的实现基础#…Spring Boot 自动装配原理
使用Spring Boot最方便的一点体验在于我们可以几零配置的搭建一个Spring Web项目那么他是怎么做到不通过配置来对Bean完成注入的呢。这就要归功于Spring Boot的自动装配实现他也是Spring Boot中各个Starter的实现基础Spring Boot的核心。自动装配就是Spring Boot会自动的寻找Bean并且装配到IOC容器中如下我们通过一个Spring Boot项目说明案例如下添加pom.xml文件依赖
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.ljm/groupIdartifactIdspring-cloud-alibaba-learn/artifactIdversion1.0-SNAPSHOT/versionpropertiesjava.version8/java.versionproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/propertiesdependencies!--Spring Boot autoConfig start--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactIdversion2.2.2.RELEASE/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactIdversion2.2.2.RELEASE/version/dependency/dependenciesbuildpluginsplugin!-- 指定 JDK 版本 --groupIdorg.apache.maven.plugins/groupIdartifactIdmaven-compiler-plugin/artifactIdversion3.8.1/versionconfigurationskiptrue/skipsource${java.version}/sourcetarget${java.version}/targetcompilerVersion${java.version}/compilerVersionencoding${project.build.sourceEncoding}/encoding/configuration/plugin/plugins/build
/projectapplication.properties
spring.redis.hostlocalhost
spring.redis.port6379HelloController.java
package com.springcloud.mystart;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** author liaojiamin* Date:Created in 10:50 2022/3/7*/
RestController
public class HelloController {Autowiredprivate RedisTemplateString, String redisTemplate;GetMapping(helloRedis)public String helloRedis(){redisTemplate.opsForValue().set(helloRedis, RedisTemplateAutoConfig);return helloRedis;}
}SpringBoot启动类 Application.java
package com.springcloud;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** author liaojiamin* Date:Created in 11:08 2022/3/7*/
SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}Redis操作界面结果 如上案例中我们并没有通过XML文件的形式注入Redis Template到IOC容器中但是HelloController中却可以直接用Autowired注入Redis Template实例说明IOC容器中已经存在了Redis Template这个是Spring Boot自动装配实现的自动加载机制。在针对Redis的配置以及jar来说我们只添加了一个Start依赖就完成了依赖组件相关的Bean自动注入
自实现自动装配标签
自动装配在Spring Boot中通过EnableAutoConfiguration 注解来开启的这个注解我们没有在项目中显示的声明他是包含在SpringBootApplication注解中如下我们可以看到SpringBootApplication注解的声明
Target(ElementType.TYPE)
Retention(RetentionPolicy.RUNTIME)
Documented
Inherited
SpringBootConfiguration
EnableAutoConfiguration
ComponentScan(excludeFilters { Filter(type FilterType.CUSTOM, classes TypeExcludeFilter.class),Filter(type FilterType.CUSTOM, classes AutoConfigurationExcludeFilter.class) })
public interface SpringBootApplication {......}在理解EnableAutoConfiguration之前我们能想到之前在用SpringMvc的时候用到过一个Enable注解他的主要作用就是吧相关的组件Bean装配到IOC容器中。Enable注解对JavaConfig的进一步的优化目的是为了减少配置其实Spring从3.X开始就一直在做优化减少配置降低上手的难度比如常见的有EnableWebMvcEnableScheduling如下代码
Retention(RetentionPolicy.RUNTIME)
Target({ElementType.TYPE})
Documented
Import({DelegatingWebMvcConfiguration.class})
public interface EnableWebMvc {
}如果我们通过JavaConfig的方式来注入Bean的时候离不来Configuration和BeanEnable就是对这两个注解的封装比如在上面的EnableWebMvc注解代码中我们能看到ImportSpring会解析Import倒入的配置类通过对这个配置类的实现来找到需要装配的Bean。
EnableAutoConfiguration
Target(ElementType.TYPE)
Retention(RetentionPolicy.RUNTIME)
Documented
Inherited
AutoConfigurationPackage
Import(AutoConfigurationImportSelector.class)
public interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY spring.boot.enableautoconfiguration;/*** Exclude specific auto-configuration classes such that they will never be applied.* return the classes to exclude*/Class?[] exclude() default {};/*** Exclude specific auto-configuration class names such that they will never be* applied.* return the class names to exclude* since 1.3.0*/String[] excludeName() default {};}以上是EnableAutoConfiguration的注解在除了Import之外还有一个AutoConfigurationPackage
注解作用在于用了该注解的类所在的包以及子包下所有的组件扫描到Spring IOC容器中
Import注解倒入类一个Configuration结尾的配置类和上面不同AutoConfigurationImportSelector类就是本注解的特殊地方
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {......}AutoConfigurationImportSelect 实现了DeferredImportSelector他是ImportSelector的子类其中有一个selectImports方法返回类一个String数组这个数组中就是我们需要制定装配到IOC容器中的所有类也就是说他在ImportSelector 的实现类之后将实现类中返回的class名称都装配进IOC容器里与Configuration不同的是ImportSelector是批量的并且还可以通过逻辑处理来选择对于的Bean那么我们用一个Demo来验证创建两个类 /*** author liaojiamin* Date:Created in 11:49 2022/3/7*/
public class FilterFirstObj {
}
/*** author liaojiamin* Date:Created in 11:49 2022/3/7*/
public class FilterSecondObj {
}
创建一个ImportSelect的实现类直接返回我们新建的类的名字如下
/*** author liaojiamin* Date:Created in 11:49 2022/3/7*/
public class GpImportSelector implements ImportSelector {Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {return new String[]{FilterFirstObj.class.getName(), FilterSecondObj.class.getName()};}
}定义我们自己的注解这个可以直接抄EnableAutoConfiguration
/*** author liaojiamin* Date:Created in 11:52 2022/3/7*/
Target({ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
Documented
Inherited
AutoConfigurationPackage
Import({GpImportSelector.class})
public interface EnableAutoImport {
}
在之前Demo的启动类中从IOC容器中获取我们定义的类
/*** author liaojiamin* Date:Created in 11:08 2022/3/7*/
SpringBootApplication
EnableAutoImport
public class Application {public static void main(String[] args) {ConfigurableApplicationContext ca SpringApplication.run(Application.class, args);System.out.println(ca.getBean(FilterFirstObj.class));}
}以上的实现方式相比Import(*Configuration.class)来说好处在于灵活性更高还可以实现批量的注入我们还有在以上的Demo中GpImportSelector添加N个由于一个Configuration表示某一个技术组件中的一批Bean所以自动装配的过程只需要扫描置顶路径对于的配置类即可。
自动装配源码分析
基于以上Demo的实现我们找到AutoConfigurationImportSelector的实现找到其中的selectImports方法他是ImportSelector接口的实现如下
Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationMetadata autoConfigurationMetadata AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);AutoConfigurationEntry autoConfigurationEntry getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}如上源码中有两个功能 从META-INF/spring-autoconfigure-metadata.properties中加载自动装配的条件元数据手机所有符合条件的配置类autoConfigurationEntry.getConfigurations()完成自动装配 在AutoConfigurationImportSelector 类中并不直接执行对呀selectImport方法其中有一个process方法这个方法最中还是会调用getAutoConfigurationEntry方法获取自动装配的所有配置类我们可以看如下源码 process方法
Overridepublic void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,() - String.format(Only %s implementations are supported, got %s,AutoConfigurationImportSelector.class.getSimpleName(),deferredImportSelector.getClass().getName()));AutoConfigurationEntry autoConfigurationEntry ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);this.autoConfigurationEntries.add(autoConfigurationEntry);for (String importClassName : autoConfigurationEntry.getConfigurations()) {this.entries.putIfAbsent(importClassName, annotationMetadata);}}getAutoConfigurationEntry方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes getAttributes(annotationMetadata);ListString configurations getCandidateConfigurations(annotationMetadata, attributes);configurations removeDuplicates(configurations);SetString exclusions getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations filter(configurations, autoConfigurationMetadata);fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}通过Debug代码可以看每个步骤得到的返回值简单分析代码的作用 getAttributes 获取EnableAutoCOnfiguration注解中的属性exclude excludeNamegetCandidateConfigurations获得所有自动装配的配置类。removeDuplicates 用LinkedHashSet实现去重getExclusions更具EnableAutoConfiguration注解中配置的exclude等属性吧不需要自动装配的配置类移除fireAutoConfigurationImportEvents广播事件最后返回经过过滤之后的配置类集合 以上步骤中核心在于获取自动装配的配置类getCandidateConfigurations其他只是在最筛选等其他步骤我们看该方法实现
protected ListString getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {ListString configurations SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());Assert.notEmpty(configurations, No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.);return configurations;}如上代码用到了SpringFactoriesLoader他是Spring内部提供的一种类加载方式类似java的SPI他会扫描classpath目录下的META-INF/spring.factories文件spring.factories文件中的数据以 keyvalue 的形式存储也就是getCandidateConfigurations 方法的返回值 如下图是spring-boot-autoconfiguration对应的jar包中文件我们可以在jar中找到对应的spring.factories 内容如下
......
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
......里面包含了多种Spring支持的组件的加载我们这以Redis为案例通过Debug我们查看getCandidateConfigurations所扫描到的所有类如下图所示其中就包括我们上图中找到的Redis的支持 我们打开RedisAutoConfiguration可以看到他是一个基于JavaConfig形式的配置类如下
Configuration(proxyBeanMethods false)
ConditionalOnClass(RedisOperations.class)
EnableConfigurationProperties(RedisProperties.class)
Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {BeanConditionalOnMissingBean(name redisTemplate)public RedisTemplateObject, Object redisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {RedisTemplateObject, Object template new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}BeanConditionalOnMissingBeanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {StringRedisTemplate template new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}}除了基本的Configuration 和Bean两个注解还有一个COnditionalOnClass这个条件控制机制在这里用途是判断classpath下是否存在RedisOperations这个类我们找一下这个类属于那个jar中如下图 如上图所示他在spring-data-redis中而我们只引入了一个有关redis的jar就是那个redis-start那么结论显而易见了在Spring Boot自动装配的时候他能扫描到所有支持的组件但是他实际加载到IOC中的会依据每个组件的condition进行第一次筛选只有找到对应的资源文件他才会去加载。EnableConfigurationProperties注解也是我们需要关注的他说有关属性配置的也就是我们按照约定在application.properties中配置的Redis的参数如下Redis.properties
ConfigurationProperties(prefix spring.redis)
public class RedisProperties {/*** Database index used by the connection factory.*/private int database 0;/*** Connection URL. Overrides host, port, and password. User is ignored. Example:* redis://user:passwordexample.com:6379*/private String url;/*** Redis server host.*/private String host localhost;/*** Login password of the redis server.*/private String password;/*** Redis server port.*/private int port 6379;/*** Whether to enable SSL support.*/private boolean ssl;/*** Connection timeout.*/private Duration timeout;/*** Client name to be set on connections with CLIENT SETNAME.*/private String clientName;private Sentinel sentinel;private Cluster cluster;......}我们的properties中的配置样式的由来就是由此得出的
spring.redis.host127.0.0.1
spring.redis.port6379由此自动装配的基本原理就完结了总结过程如下 通过Import(AutoConfigurationImportSelector)实现配置类的导入但是这里并不是传统意义上的单个配置类装配AutoConfigurationImportSelector实现了ImportSelector接口重写了selectImports他用来实现选择性批量配置类的装配通过Spring提供的SpringFactoriesLoader机制扫描classpath路径下的META-INF/spring.factories读取自动装配的配置类通过筛选条件吧不符合的配置类移除最中完成自动装配
下一篇SpringBoot中Bean按条件装配