SpringBoot基础 SpringBoot系列之自动装配约定大于配置篇 2021-04-09 [TOC] # 一、本章目的 通过源码来学习一下Spring Boot的自动装配机制是如何做到的。 ### 1.@SpringBootApplication注解 > 添加该注解的类为SpringBoot项目的主配置类,SpringBoot的核心功能就是由@SpringBootApplication注解来实现的。 ```java ...... @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 { ...... } ``` #### 1.1@SpringBootConfiguration > 进入@SpringBootConfiguration后发现该注解包含了@Configuration, > > **@Configuration:** > > - 表示该类是一个配置类 > - @Configuration包含了@Component,所以加了@Configuration注解的类会被自动加载到Spring容器中。 #### 1.2@EnableAutoConfiguration > 进入@EnableAutoConfiguration该注解包含了@AutoConfigurationPackage注解,并导入了一个AutoConfigurationImportSelector类。 > > **@AutoConfigurationPackage:** > > - 该注解实现了只需要将我们自己写的java文件,放到@SpringBootApplication所在类的包或子包下,spring boot就可以自动的帮我们完成扫描。 > > 而Spring传统方式开发时,该操作需要我们在xml配置文件中定义一个scan扫描器,spring才可以自动去扫描base-pack下面或者子包下面的java文件,当扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean加载到容器中。 > > 原理:在@AutoConfigurationPackage注解中导入了一个名为Registrar的静态内部类,通过该类,SpringBoot可以找到@SpringBootApplication所在类的包,并将该包与其子包全部注册到Spring容器中进行管理 > > **@Import(AutoConfigurationImportSelector.class):** > > - 通过导入该类,SpringBoot帮我们实现了自动引入第三方依赖。该类通过SpringFactoriesLoader.loadFactoryNames这个方法来加载第三方配置 > > ```java > @Override > public String[] selectImports(AnnotationMetadata annotationMetadata) { > if (!isEnabled(annotationMetadata)) { > return NO_IMPORTS; > } > AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);//自动加载 > return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); > } > .... > protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { > if (!isEnabled(annotationMetadata)) { > return EMPTY_ENTRY; > } > AnnotationAttributes attributes = getAttributes(annotationMetadata); > List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//自动加载 > configurations = removeDuplicates(configurations); > Set<String> exclusions = getExclusions(annotationMetadata, attributes); > checkExcludedClasses(configurations, exclusions); > configurations.removeAll(exclusions); > configurations = getConfigurationClassFilter().filter(configurations); > fireAutoConfigurationImportEvents(configurations, exclusions); > return new AutoConfigurationEntry(configurations, exclusions); > } > .... > protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { > List<String> 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; > } > ``` > > loadFactoryNames方法又去调用了loadSpringFactories这个方法,最终这个方法里,SpringFactoriesLoader会去加载jar包中的META-INF/spring.factories。如果找到这个文件,SpringFactoriesLoader就会搜索文件中的一个配置org.springframework.boot.autoconfigure.EnableAutoConfiguration,并读取后面的值。 ### 2.自动装配原理-实例分析(HttpEncodingAutoConfiguration) > 现在来简单的分析一个Spring Boot内置的自动配置功能:http的编码配置。 - 之前项目中配置Http编码的方式,在web.xml添加一个filter,如: ```xml <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` - 现在SpringBoot要完成自动配置,只需要满足两个条件 ①能配置CharacterEncodingFilter这个Bean ②能配置encoding和forceEncoding这两个参数 **满足上述两个条件的类:在org.springframework.boot.autoconfigure.web.servlet包中HttpEncodingAutoConfiguration类** 源码如下: ```java @Configuration(proxyBeanMethods = false)//表示这个类是一个配置类,并将这个类加入IOC容器。 //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和ServerProperties绑定起来;并把ServerProperties加入到ioc容器中 @EnableConfigurationProperties(ServerProperties.class) //Spring底层@Conditional注解满足指定的条件,整个配置类里面的配置就会生效 //该注解是判断当前应用是否是web应用,如果是,则HttpEncodingAutoConfiguration配置类生效; @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) //判断当前项目有没有这个类 CharacterEncodingFilter;SpringMVC 中进行乱码解决的过滤器,如果有则 HttpEncodingAutoConfiguration 配置生效; @ConditionalOnClass(CharacterEncodingFilter.class) //判断application.properties配置文件中是否存在server.servlet.encoding.enabled,如果不存在,判断也是成立的,因为 matchIfMissing=true,即缺省时默认为 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(); } @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; } .... //进入ServerProperties.class @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties { /** * Server HTTP port. */ private Integer port; /** * Network address to which the server should bind. */ private InetAddress address; ...... /** * Servlet properties. */ public static class Servlet { //静态内部类 ..... private String applicationDisplayName = "application"; /** * Whether to register the default Servlet with the container. */ private boolean registerDefaultServlet = true; @NestedConfigurationProperty//对复杂数据结构嵌套完成配置 private final Encoding encoding = new Encoding();//静态内部类 ``` 因此,我们只需要在application.properties配置文件中,按照prefix + 属性名,就可以修改对应的属性。在本例中,我们如果想修改项目的默认编码为GBK,只需要在全局配置文件中添加一行代码即可,代码如下: ```properties server.servlet.encoding.charset=GBK ``` > 注: > > - **Conditional 相关的注解:** > > ConditionalOnXXX类型的注解,这类注解都是@Conditional注解的派生注解,作用是必须是@Conditional指定的条件成立,才给容器中添加组件,配置里的内容才会生效。针对本例来说,必须@ConditionalOnWebApplication、@ConditionalOnClass、@ConditionalOnProperty三个注解对应的条件都生效,HttpEncodingAutoConfiguration配置类才会生效。 > > - **@Conditional扩展注解:** > > 作用:必须是 @Conditional 指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效 ![](/uploads/1/image/public/202104/20210409175511_8w85he8lp5.png)