SpringBoot版本:2.1.1.RELEASE
在SpringBoot应用启动时,会加载大量的默认配置,从而简化了我们的开发成本。那么,SpringBoot是如何实现自动配置的呢?
在SpringBoot应用的启动类上,我们可以看到 @SpringBootApplication注解,这个注解是一个组合注解,它不仅能标记这个应用是一个SpringBoot应用,更重要的是它能开启自动配置的功能。
@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 {
}
下面将会详细的分析这个组合注解以及它实现自动配置的过程。
第一个注解:@Inherited
这是Java自带的注解,它的作用是:使用此注解申明出来的自定义注解,在使用此自定义的注解时,如果作用在类上,子类会自动继承此注解。
第二个注解:@SpringBootConfiguration
这是SpringBoot自动的注解,作用就是声明这是SpringBoot的配置类,该注解继承自 @Configuration,所以两者功能也一致,可以将当前类内声明的一个或多个以 @Bean 注解标记的方法的实例纳入到 Spring 容器中,并且实例名就是方法名。
第三个注解:@EnableAutoConfiguration
顾名思义,该注解就是用来启动自动配置功能的注解,也是SpringBoot应用最核心的注解之一。下面将会详细分析该注解
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.SpringFactoriesLoader;
@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 {};
}
这上面有两个重要的注解,我们先分析 @Import(AutoConfigurationImportSelector.class)
其中 @Import 注解会把注解中的类注入到当前的IOC容器中,一般用于资源的导入。
而 AutoConfigurationImportSelector类是导入自动配置相关的资源,而资源的导入我们可以进入该类中详细查看 getCandidateConfigurations() 方法
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;
}
我们可以看下 **getBeanClassLoader()和getSpringFactoriesLoaderFactoryClass()**方法的返回值
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
我们在进入 loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()) 方法一探究竟。
从上图中我们可以看出传递的参数就是 注解EnableAutoConfiguration 和当前的类加载器。然后我们再看 **loadFactoryNames()**方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
该方法首先获取 EnableAutoConfiguration的Name属性,即String factoryClassName=org.springframework.boot.autoconfigure.EnableAutoConfiguration,然后调用loadSpringFactories()方法加载指定类型的EnableAutoConfiguration 对应的在 META-INF/spring.factories 里的类名的数组,如下图
这样子,就会把每一个这样的 xxxAutoConfiguration类作为容器中的一个组件,都加入到容器中,用他们来做自动配置,每一个自动配置类里面都内置了大量的默认配置。我们以 HttpEncodingAutoConfiguration自动配置类为例来说明。
@Configuration //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(HttpProperties.class) //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpProperties绑定起来;并把HttpProperties加入到ioc容器中
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) //Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final HttpEncodingProperties properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取
@ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
所有能在配置文件中配置的属性都会封装在XXXproperties 中,配置文件中具体能配置哪些参数,可以参考对应的 XXXproperties属性类
@ConfigurationProperties(prefix = "spring.http")
//从配置文件中获取指定的值和bean的属性进行绑定
public class HttpProperties {
private boolean logRequestDetails;
private final Encoding encoding = new Encoding();
public boolean isLogRequestDetails() {
return this.logRequestDetails;
}
public void setLogRequestDetails(boolean logRequestDetails) {
this.logRequestDetails = logRequestDetails;
}
public Encoding getEncoding() {
return this.encoding;
}
public static class Encoding {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private Charset charset = DEFAULT_CHARSET;
}
}
XXXAutoConfiguration : 自动配置类,给容器中添加组件
XXXProperties:封装配置文件中的相关属性
在 @EnableAutoConfiguration 注解上还有一个需要值得注意的注解,那就是 @AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
此时,需要把AutoConfigurationPackages中的内部类Registrar注册到IOC容器中。
/**
* Class for storing auto-configuration packages for reference later (e.g. by JPA entity
* scanner).
*/
public abstract class AutoConfigurationPackages {
......
}
上面的注释简单来说,就是将使用 @AutoConfigurationPackage 注解的类所在的包(package),注册成一个 Spring IoC 容器中的 Bean ,如果有其它模块需要使用,就可以通过获得该 Bean ,从而获得所在的包。例如说,JPA 模块,需要使用到。
好了 ,@SpringBootApplication 注解上最重要的组合注解 @EnableAutoConfiguration 基本已分析完毕,还有一个 @ComponentScan,这个是Spring自定义的注解,用于扫描指定路径下的Component(如@Component,@Controller,@Service,@Configuration等等)
下一篇文章将会对SpringBoot中的配置文件作详细的介绍和说明!