Bean自动装配原理
Spring Boot实现自动配置加载的原理
一、Bean自动装载的核心问题
Spring Boot里面的各种Bean(类对象)能够实现自动装载,自动的装载帮我们减少了XML的配置,和手动编码进行Bean的加载工作。从而极大程度上帮我们减少了配置量和代码量。
要实现Bean的自动装载,需要解决两个问题
- 如何保证Bean自动装载的灵活性?
这个问题通过配置文件来解决(就好比Maven中依赖传递的关系),在配置A情况下去装载BeanY;在配置B情况下去装载BeanZ。(通常情况下配置A和B会有默认值,来决定默认的装载行为,这样就不需要我们配置了,进一步减少配置量)
- 如何保证Bean装载的顺序性?
当BeanA装载完成之后再去装载BeanY,BeanY装载完成之后才去装载BeanX。这个装载顺序问题由@ConditionOnXXXXXXX注解来解决。
二、全局配置文件
SpringBoot使用一个全局的配置文件,配置文件名是固定的;(properties或者是yml格式的文件)
- application.properties
- application.yml
全局配置文件的作用:修改SpringBoot自动配置的默认值,通过配置来影响SpringBoot自动加载行为。
三、配置加载原理源码解析
1、首先是SpringBoot入口函数的@SpringBootApplication注解中的@EnableAutoConfiguration就是打开自动装载的一个行为。没有的话不走装配。
2、所有的Spring Boot应用程序都是以SpringApplication.run()作为应用程序入口的。下面我们来一步一步跟踪一下这个函数。
首先点进去:发现它又调用了类中的run方法。(这里的run方法都是重载的)
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
再点进去:发现它带参构造创建了本类对象又调用了run方法。run方法传入了SpringApplication
对象和一些运行期参数。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
继续点入:我们主要是找它怎么实现Bean的自动装载的,发现它又调用了本类一个获取Spring工厂实例的方法
public ConfigurableApplicationContext run(String... args) {
...
try {
...
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
...
if (this.logStartupInfo) {
...
}
...
} catch (Throwable var10) {
...
}
...
}
继续点入:从本方法中看到出现一个SpringFactoriesLoader的一个类,这里面体现了Spring Boot加载配置文件的核心逻辑。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
进入这个类:会发现他有一个常量:工厂资源位置FACTORIES_RESOURCE_LOCATION。到这儿也就看不懂了,肯定会到时候根据这个位置信息找内容。
从`META-INF/spring.factories`文件夹下下面加载了spring.factories文件资源。然后读取文件中的ClassName作为值放入Properties。
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
...
}
根据这个路径的文件名搜索一下:会发现有很多。
随便点进去一个,你就会发现这里的每一行都是一个自动装配的类。因为SpringBoot入口方法的注解中就开启了@EnableAutoConfiguration注解。在EnableAutoConfiguration往下的每一行都可以被装载。
然后通过反射机制,对spring.factories里面的类资源进行实例化,所以spring.factories文件里面究竟写了什么类?这些类是做什么的?就是我们下一步要探究的问题了。
宏观原理图:
3、每一行的自动装配类里面是如何完成自动装配的
详情请看标题五
四、@EnableAutoConfiguration 作用
SpringBoot入口启动类使用了SpringBootApplication,实际上就是开启了自动配置功能@EnableAutoConfiguration。
SpringFactoriesLoader会以@EnableAutoConfiguration的包名和类名org.springframework.boot.autoconfigure.EnableAutoConfiguration为Key查找spring.factories文件,并将value中的类名实例化加载到Spring Boot应用中。如下图:
spring.factories文件中的每一行都是一个自动装配类。
五、Bean的自动装配实现原理简述
每一个自动配置类进行自动配置功能(spring.factories中的每一行对应的类),我们以HttpEncodingAutoConfiguration为例讲解一下:
//加载application全局配置文件内的部分配置到HttpEncodingProperties里面
@Configuration
@EnableConfigurationProperties({HttpEncodingProperties.class})
//当web容器类型是servlet的时候执行本类中的自动装配代码
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//当有一个CharacterEncodingFilter的这样一个类的字节码文件时时执行本类中的自动装配代码
@ConditionalOnClass({CharacterEncodingFilter.class})
//当spring.http.encoding配置值为enabled的时候执行本类中的自动装配代码
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true //如果application配置文件里面不配置,默认为true
)
public class HttpEncodingAutoConfiguration {
private final HttpEncodingProperties properties;
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean
//当没有CharacterEncodingFilter这个Bean就实例化CharacterEncodingFilter为一个bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.RESPONSE));
return filter;
}
@Bean
public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
}
//此处省略与自动加载无关的代码:HttpEncode的逻辑及其他
}
在配置类加载过程中,大量的使用到了条件加载注解:
条件注解 | 使用说明 |
---|---|
@ConditionalOnClass | classpath中存在该类字节码文件时,才执行实例化方法或将类实例化 |
@ConditionalOnMissingClass | classpath中不存在该类字节码文件时,才执行实例化方法。(不存在A的时候去初始化B) |
@ConditionalOnBean | DI容器中存在该类型Bean时,才执行实例化方法或将类实例化 |
@ConditionalOnMissingBean | DI容器中不存在该类型Bean时,才执行实例化方法或将类实例化 |
@ConditionalOnSingleCandidate | DI容器中该类型Bean只有一个或@Primary的只有一个时,才执行实例化方法或将类实例化 |
@ConditionalOnExpression | SpEL表达式结果为true时,才执行实例化方法或将类实例化 |
@ConditionalOnProperty | 参数设置或者值一致时,才执行实例化方法或将类实例化 |
@ConditionalOnResource | 指定的文件存在时,才执行实例化方法或将类实例化 |
@ConditionalOnJndi | 指定的JNDI存在时,才执行实例化方法或将类实例化 |
@ConditionalOnJava | 指定的Java版本存在时,才执行实例化方法或将类实例化 |
@ConditionalOnWebApplication | Web应用环境下,才执行实例化方法或将类实例化 |
@ConditionalOnNotWebApplication | 非Web应用环境下,才执行实例化方法或将类实例化 |
我们讲的这个实现原理实际上就是一个自定义spring-boot-starter的实现原理,我们会在后面章节中自己编码实现一个分布式文件系统fastdfs与spring boot整合的starter。大家届时会有更深一步的理解。在以上的自动装配过程中依赖于HttpEncodingProperties的自定义属性,我们后面会讲如何读取自定义配置属性。
原文:https://www.cnblogs.com/jinyuanya/p/13972804.html