springboot自动加载--原理

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 父类 AutoConfigurationImportSelectorselectImports方法来读取所有依赖的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

参考文章:
Springboot自动加载的原理
深入理解springboot工作原理

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会叫的狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值