还记得我们怎么配置文件的吗,里面可以写啥,怎么写。这么多,估计没几个人能够背下来,那么怎么才能知道这个东西怎么写呢?今天就来谈谈配置的终极奥秘——自动配置原理!!!
”![a1d6f7f944d94301c3d4a6f89e622b90.png](https://i-blog.csdnimg.cn/blog_migrate/5ace6b283f9fdb074b838521aed92179.png)
自动配置原理
我们首先来看看启动类,从这个入口开始,一步一步的究其源码。
在我们的spring boot应用启动的时候,我们是直接运行main方法,而这个main方法有一个注解@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 {@Target(ElementType.TYPE)
不要慌,这么多注解我们只需要关注下面三个即可!我们首先关注这个@EnableAutoConfiguration,这个注解直白的告诉我们,开启自动配置,这下我们就能够知道,我们不用手动的配置,都是这个东西起作用。再来看看这个注解里面的东西:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class) //选择导入导入自动配置组件public @interface EnableAutoConfiguration {
点开一看,又是一堆,这个需要我们关注的只有一个@Import,这个注解我们之前在spring纯注解中说过,就是将一个类导入容器。也就是说向容器中加入这个组件。这个类也告诉我们有什么作用,就是选择导入自动配置。然后我们就可以继续跟进,再点开这个看看,里面有什么东西:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { ......//东西很多,直接挑出我们需要关注的几个方法 //选择导入 @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } //获取自动配置的条目信息,也就是哪些配置类。 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } //这个方法调用了getAutoConfigurationEntry protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); //来关注这个方法,获取配置信息,封装成一个集合。点进去看 List configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { //这个方法又调用了loadFactoryNames方法(加载工厂名字)。再点进入看看 List 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; } public static List loadFactoryNames(Class> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); //这个loadSpringFactories方法,我们在点过去一看 return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } //首先这个方法的所属类中有这个一个常量: public final class SpringFactoriesLoader { //直接指向了META-INF/spring.factories public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
然后我们再看看loadSpringFactories这个方法:
这个方法利用类加载器,从这个配置文件中获取了信息封装到Map集合中,我们再打开这个加载的位置:
很清楚的看到类加载器把这个配置文件中的XXXAutoConfiguration都加载进入了这个容器。而这个就是我们自动配置的根本来源,也就是说自动配置就是从这个里面来的。
知道了这个东西怎么来的。我们再来细究这个自动配置怎么实现的:
就用这个配置文件中的HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理:
点开源码:
@Configuration(proxyBeanMethods = false) //表示这个是一个配置类,可以向容器添加组件//使ServerProperties和对应的配置文件中的属性绑定,并且将这个对象加入容器@EnableConfigurationProperties(ServerProperties.class)//spring底层的@Conditional注解,可以指定这个配置类是否生效(是否加入容器)@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)@ConditionalOnClass(CharacterEncodingFilter.class)/** * 判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的 * 即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的; */@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)public class HttpEncodingAutoConfiguration { //和对应的配置文件绑定,也就是配置文件的信息封装成了一个对象 private final Encoding properties; //只有一个有参构造的情况下,参数会默认从容器中获取,刚好这个组件就是上面的注解加入容器的 public HttpEncodingAutoConfiguration(ServerProperties properties) { this.properties = properties.getServlet().getEncoding(); } //给容器中添加组件,有的值就会从properties中获取 @Bean @ConditionalOnMissingBean //判断容器是否有这个组件,没有才会添加 public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE)); return filter; } .....
不难发现点开哪个自动配置类,里面的注解大同小异,首先具体解释这个@EnableConfigurationProperties(ServerProperties.class)
开启配置属性,这个注解的功能就是将这个ServerProperties.class加入容器,我们点开这个这个XXXProperties,
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)public class ServerProperties { /** * Server HTTP port. */ private Integer port;
还记得上次我说的记住的一个注解吗?@ConfigurationProperties
这个注解不正好就是把配置文件中的属性和我们的类属性对应的进行绑定吗,也就是配置文件中我们写的东西,会绑定到这个xxxProperties这个类中。然后我们回想端口配置的时候,是不是写的server.port,再看看这个类中的属性,是不是刚好就对应上了!所以现在知道配置文件中的属性怎么写了吗?不就是找到这个xxxAutoconfiguration,会导入相应的xxxpeiperties,这个刚好指定了前缀,属性就是我们可以配置的选项!
我们在看看其他的注解:
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
这个注解有没有一点点的眼熟,不急,我们拆开来看:Conditional+OnWebApplication,这下子是不是就眼熟了。点开源码一看:
@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(OnWebApplicationCondition.class)public @interface ConditionalOnWebApplication {
好家伙,这个总知道了吧,在spring纯注解中就讲到这@Conditional(OnWebApplicationCondition.class)注解,作用不就是满足这个组件里面的条件就会被加入容器吗?再看看这个注解里面的条件,当有这个类的时候就会生效,也就是当我们选中Web场景模块的时候才会有这个类,这个配置才会生效。所以我们又可以知道一个点,@conditionalXXX,就是当XXX条件满足的时候,这个组件就会被加入容器!
我们再来看看这个:
//和对应的配置文件绑定,也就是配置文件的信息封装成了一个对象private final Encoding properties;//只有一个有参构造的情况下,参数会默认从容器中获取,刚好这个组件就是上面的注解加入容器的public HttpEncodingAutoConfiguration(ServerProperties properties) { this.properties = properties.getServlet().getEncoding();}
Encoding properties这个是从ServerProperties中获取的,也就是我们需要配置Encoding,可以直接server.encoding.xxx就是我们可选的配置!
我们再看这个:
//给容器中添加组件,有的值就会从properties中获取@Bean@ConditionalOnMissingBean //判断容器是否有这个组件,没有才会添加public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE)); return filter;}
首先是这个注解:@Bean,这个注解不就是将这个方法的返回值加入容器吗?再看看上面刚说了的这种形式的注解:@ConditionalOnMissingBean,按照上面的方式判断,这个注解不就是当不存在这个Bean的时候,这个Bean就会生效。不存在这个Bean就会生效是什么意思,不就是如果我们容器中没有这个Bean,就会把这个默认的Bean加入容器,从这我们又可以推断出一个重要的东西,如何定制我们需要的配置!!!也就是说如果我们觉得这个配置不符合我们的想法,我们可以手动的向容器中添加一个我们需要的组件。这样,这个组件就不会生效!比如:
@Configurationpublic class LoginHandlerInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser = request.getSession().getAttribute("loginUser"); if (loginUser == null) { //未登录,拦截,并转发到登录页面 request.setAttribute("msg", "您还没有登录,请先登录!"); request.getRequestDispatcher("/index").forward(request, response); return false; } return true; }}
我们配置自己的拦截器加入容器,它默认的拦截器就不会生效,这个时候,就是按照我们的意愿改变这个行为!
我们来总结一下:
1、spring boot启动的时候加载很多的自动配置类。(xxxAutoConfiguration)
2、这个配置类就会加载配置文件中的配置绑定相应的xxxproperties
3、我们如果需要配置属性,直接点开这个xxxproperties即可
4、这个自动配置类中导入的组件如果不符合自己的需求,就可以自己添加配置,更改这个逻辑。
xxxxAutoConfigurartion
:自动配置类;
xxxxProperties
:封装配置文件中相关属性;
再来拓展一下这个@Conditional派生注解:
@ConditionalOnJava
系统的java版本是否符合要求
@ConditionalOnBean
容器中存在指定Bean
@ConditionalOnMissingBean
容器中不存在指定Bean
@ConditionalOnClass
系统中有指定的类
@ConditionalOnMissingClass
系统中没有指定的类
@ConditionalOnProperty
系统中指定的属性是否有指定的值
@ConditionalOnWebApplication
当前是web环境
@ConditionalOnSingleCandidate
容器中只有一个指定的Bean,或者这个Bean是首选Bean
当然这些只是一部分比较常见的,其他的都是一样的哦!
我们怎么知道哪些自动配置类生效了;
我们可以通过配置文件启用 debug=true
属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
Positive matches
:(自动配置类启用的)Negative matches
:(没有启动,没有匹配成功的自动配置类)
好了,这个终极奥秘搞懂了,就不怕配置怎么写,组件怎么添加了,当然这一个例子是远远不够的,后面我们讲到在一个一个的复习。
别忘了点赞啊!如果有帮助,希望可以推广给你觉得需要的人!