Springboot自动加载原理
@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 {
@SpringBootConfiguration
:其实就是一个@Configuration
注解而已,它只是告诉 spring 这是一个配置类,可以被加载到 spring IOC 容器中。@ComponentScan
:自动扫描并加载符合条件的组件(如@Component、@Controller、@Service、@Repository等)或者 bean 定义,最终将这些 bean 定义加载到IOC容器中。可以指定目录,默认所在类的 package 进行扫描。@EnableAutoConfiguration
:自动配置重点,下面详细说明。扫描所有 jar 包下面的META-INF/spring.factories
,将所有符合自动配置类加载到IOC容器中,也是自动配置的核心。
@EnableAutoConfiguration
@EnableAutoConfiguration
源码:
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage
源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
@EnableAutoConfiguration
包含两个注解:
@AutoConfigurationPackage
:借助 @Import 注解导入符合自动配置的类集合,加载到IOC容器。@Import(EnableAutoConfigurationImportSelector.class)
:借助 EnableAutoConfigurationImportSelector 父类AutoConfigurationImportSelector
的selectImports
方法来读取所有依赖的jar包下面 META-INF/spring.factories 文件,并且根据加载条件来加载项目所需要的类,这样就完成了 springboot 的自动加载。
EnableAutoConfigurationImportSelector
@EnableAutoConfigurationImportSelector.selectImports()
源码:
/**
* 最主要的方法
* annotationMetadata
* [@org.springframework.boot.autoconfigure.SpringBootApplication
* (scanBasePackageClasses=[], excludeName=[], exclude=[], scanBasePackages=[])]
* @param annotationMetadata
* @return
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
/**
*
*/
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
/**
* 得到注解中的所有属性信息{excludeName=[], exclude=[]}
*/
AnnotationAttributes attributes = getAttributes(annotationMetadata);
/**
*加载META-INF/spring-autoconfigure-metadata.properties,获取所有支持自动配置的信息
* 获取所有支持EnableAutoConfiguration的组件信息,这部分信息配置在spring-boot-autoconfig包下的spring.factories下
*
* 使用了内部工具使用SpringFactoriesLoader,查找classpath上所有jar包中的
* META-INF\spring.factories,找出其中key为
* org.springframework.boot.autoconfigure.EnableAutoConfiguration
* 的属性定义的工厂类名称。
*/
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
/**
* 去除不需要的
* @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, RedisAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, })
*/
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
/**
* 然后使用AutoConfigurationImportFilter进行过滤,过滤的方式基本上是判断现有系统是否引入了某个组件,(系统是否使用哪个组件是在pom定义的时候就确定了的)
* ,如果有的话则进行相关配置。比如ServletWebServerFactoryAutoConfiguration
* ,会在ServletRequest.class等条件存在的情况下进行配置,
* 而EmbeddedTomcat会在Servlet.class, Tomcat.class存在的情况下创建TomcatServletWebServerFactory
*
* org.springframework.boot.autoconfigure.condition.OnClassCondition
* 总而言之,此过滤器会检查候选配置类的注解@ConditionalOnClass,如果要求的类在classpath 中不存在,则这个候选配置类会被排除掉
*/
configurations = filter(configurations, autoConfigurationMetadata);
/**
* 现在已经找到所有需要被应用的候选配置类
* 广播事件AutoConfigurationImportEvent
*/
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
private void fireAutoConfigurationImportEvents(List<String> configurations,
Set<String> exclusions) {
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
configurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods(listener);
listener.onAutoConfigurationImportEvent(event);
}
}
}
getCandidateConfigurations
方法会读取到所有依赖 jar 包下面的 META-INF/spring.factories
,并将 spring.factories 中的配置类的全名称获取到。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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
:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
loadSpringFactories
:
springboot 之所以能拿到 spring.factories 就是通过
SpringFactoriesLoader
来读取的,SpringFactoriesLoader 会将依赖包所有的 spring.factories 读取出来,并用一个 map 来封装读取出来的 vaule。SpringFactoriesLoader 是 spring 提供的一种扩张方案,其主要功能就是从指定的配置文件 META-INF/spring.factories 加载配置。
spring.factories
说了半天 META-INF/spring.factories ,那这到底是个什么文件?我们打开一个启动器的 spring.factories 文件,例如spring-boot-autoconfigure
:
可以发现以,EnableAutoConfiguration
为 key 的一系列配置,key 所对应的值,就是所有的自动配置类,可以在当前的 jar 包中找到这些自动配置类。values 几乎涵盖了现在主流的开源框架 , 我们来看一个我们熟悉的,例如SpringMVC,查看mvc 的自动配置类 WebMvcAutoConfiguration
:
可以看到自动配置类上有一些 ConditionXxxx 注解,这些注解的作用就是进行条件化选择,所谓条件化选择就是如果满足条件,该配置类就生效,如果不满足该配置类就不生效。
常用的条件化选择注解如下 :
@ConditionalOnBean
:如果存在某个Bean,配置类生效@ConditionalOnMissingBean
:如果不存在某个Bean,配置类生效@ConditionalOnClass
:如果存在某个类,配置类生效@ConditionalOnMissingClass
:如果不存在某个类,配置类生效@ConditionalOnProperty
:如果存在某个属性配置,配置类生效@ConditionalOnWebApplication
:如果是一个web应用,配置类生效@ConditionalOnNotWebApplication
:如果不是一个web应用,配置类生效
配置类:
@Configuration
public class MyConfig {
@Bean
public Pet getPet(){
return new Pet();
}
//在容器中有Pet的Bean时,getUser的Bean才会注册到容器中。否则不会注册
@ConditionalOnBean (Pet.class)
@Bean
public User getUser(Pet pet){
User zhangsan = new User();
zhangsan.setPet(pet);
return zhangsan;
}
//当容器存在User对象时,该Bean不注册到容器。即没有User时,才执行getUser2(),将方法返回类注册到容器。
//一般SpringBoot自动配置类都会加入该注解,默认使用SpringBoot提供的,用户也可以自定义实现类,覆盖原自动配置类实现。
@ConditionalOnMissingBean(User.class)
@Bean
public User getUser2(Pet pet){
User zhangsan = new User();
zhangsan.setPet(pet);
return zhangsan;
}
}
启动类:
@SpringBootApplication
public class App
{
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
User user = (User) context.getBean("getUser");
System.out.println(user);
User user2 = (User) context.getBean("getUser2");
System.out.println(user2);
}
}
运行结果:
@ConditionalOnBean (Pet.class)
:当容器中有 Pet 的 Bean 时,getUser 的 Bean 才会注册到容器中。否则不会注册。
@ConditionalOnMissingBean(User.class)
:当容器中没有 User 的 Bean 时,才执行 getUser2(),将方法返回 Bean 注册到容器。当容器存在User 对象时,该 Bean 不注册到容器。一般SpringBoot自动配置类都会加入该注解,默认使用SpringBoot提供的Bean,用户也可以自定义实现类,覆盖原自动配置类实现。
默认属性配置
配置类我们找到了,那么这些默认配置的属性来自哪里呢?比如:配置视图解析器的时候需要配置前缀和后缀,那么这些配置在哪配置的呢?
通过源码发现,这个配置是从 this.mvcProperties.getView() 中读取的,this.mvcProperties
又是什么呢?我们继续跟踪,发现其实就是定义的一个变量
WebMvcProperties
类中包含一个View
类型的变量,这个变量中配置的就是前缀和后缀。
View
中的源码如下:
可以看到,默认读取就是这里的前缀和后缀变量的值,默认就是 null。
覆盖默认属性配置
如果我们想自己指定视图的前缀和后缀该如何去做呢?我们看到,自动配置类WebMvcAutoConfiguration
注入了WebMvcProperties
这个配置类。
WebMvcProperties
配置类前缀是spring.mvc
,所以如果我们在配置文件中配置 spring.mvc 前缀开头的配置,就可以将自己配置的数据注入到这个对象的属性中。
spring:
mvc:
view:
prefix: /WEB-INF/
suffix: .jsp