侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 285 篇文章
  • 累计创建 125 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

springboot自动装配原理及SpringApplication执行流程

孔子说JAVA
2022-08-16 / 0 评论 / 0 点赞 / 34 阅读 / 10,812 字 / 正在检测是否收录...

SpringBoot是目前软件中最主流的框架,其中一个主要优势就是解决了传统spring的重量级xml配置Bean,实现了自动装配。实现自动装配的核心是启动类上的@SpringBootApplication注解,主要由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解组成,由这三个注解共同完成了自动装配。

1、springboot启动类

任何一个Spring Boot项目中都会用到如下的启动类,在启动springboot的时候,会自动将Bean装配到IoC容器中。例如我们在使用Redis数据库的时候,会引入依赖spring-boot-starter-data-redis。在引入这个依赖后,服务初始化的时候,会将操作Redis需要的组件注入到IoC容器中以便进行后续使用。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringbootApplication.class, args);
	}

}

自动装配大致过程:

获取到组件(如spring-boot-starter-data-redis)META-INF文件夹下的spring.factories文件(spring.factories文件中列出了需要注入到IoC容器的类) -> 将spring.factories文件中指定的实体类注入到IoC容器中以便后续进行使用。

在SpringbootApplication启动类中,通过 SpringApplication.run(SpringbootApplication.class, args); 启动容器的过程中,由 @SpringBootApplication 注解实现了自动装配。

2、@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 {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
    //根据包路径扫描
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
    //直接根据class类扫描
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

可以看到 @SpringBootApplication 有很多的注解组成,重要的只有三个Annotation:

  1. @Configuration(@SpringBootConfiguration实质就是一个@Configuration)
  2. @EnableAutoConfiguration
  3. @ComponentScan

我们可以尝试将启动类上的 @SpringBootApplication 替换为上面三个注解,是具有同样效果的。

2.1 @Configuration 注解

这个注解实际上就是代表了一个配置类,相当于一个beans.xml文件。SpringBoot社区推荐使用基于JavaConfig的配置形式,启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。

@Configuration 注解介绍详见:SpringBoot配置类的实现 - @Configuration、@Bean注解的使用详解

2.2 @ComponentScan 注解

@ComponentScan注解在Spring中很重要,该注解的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则Spring框架默认实现会从声明@ComponentScan所在类的package进行扫描。所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

2.3 @EnableAutoConfiguration 注解

在spring中有关于@Enablexxx的注解是开启某一项功能的注解,比如@EnableScheduling表示开启spring的定时任务(通过@Import将Spring调度框架相关的bean定义都加载到IoC容器)。@EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器。

@EnableAutoConfiguration的理念和做事方式其实是一脉相承的,简单概括就是,借助@Import的支持,收集和注册特定场景相关的bean定义(将所有符合自动配置条件的bean定义加载到Ioc容器)。

EnableAutoConfiguration代表开启springboot的自动装配。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    //按类型排序不需要自动装配的类
    Class<?>[] exclude() default {};
    //按名称排除不需要自动装配的类
    String[] excludeName() default {};
}

从源码中可以看到,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。同时借助于Spring框架原有的一个工具类:SpringFactoriesLoader,@EnableAutoConfiguration就可以实现智能的自动配置。

AutoConfigurationImportSelector源码

AutoConfigurationImportSelector 可以将需要装配的类装配到IoC容器中,从源码中可以看到,AutoConfigurationImportSelector 实现了很多的 xxxAware 和 DeferredImportSelector,所有的aware都优先于selectImports方法执行,也就是说selectImports方法最后执行,那么在它执行的时候所有需要的资源就都已经获取到了。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            //1加载META-INF/spring-autoconfigure-metadata.properties文件
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            //2获取注解的属性及其值(PS:注解指的是@EnableAutoConfiguration注解)
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //3.在classpath下所有的META-INF/spring.factories文件中查找org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,并将其封装到一个List中返回
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            //4.对上一步返回的List中的元素去重、排序
            configurations = this.removeDuplicates(configurations);
            //5.依据第2步中获取的属性值排除一些特定的类
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            //6对上一步中所得到的List进行过滤,过滤的依据是条件匹配。这里用到的过滤器是
            //org.springframework.boot.autoconfigure.condition.OnClassCondition最终返回的是一个ConditionOutcome[]
            //数组。(PS:很多类都是依赖于其它的类的,当有某个类时才会装配,所以这次过滤的就是根据是否有某个
            //class进而决定是否装配的。这些类所依赖的类都写在META-INF/spring-autoconfigure-metadata.properties文件里)
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }

    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;
    }

...
}

执行过程如下:

  1. 加载META-INF/spring-autoconfigure-metadata.properties文件
  2. 获取注解的属性及其值(PS:注解指的是@EnableAutoConfiguration注解)
  3. 获取所有自动装配的配置类,也就是读取spring.factories文件,在classpath下所有的META-INF/spring.factories文件中查找
  4. 去除重复的配置项,即对上一步返回的List中的元素去重、排序
  5. 依据第2步中获取的属性值排除一些特定的类
  6. 对上一步中所得到的List进行过滤,过滤的依据是条件匹配。这里用到的过滤器是org.springframework.boot.autoconfigure.condition.OnClassCondition最终返回的是一个ConditionOutcome[]数组。(PS:很多类都是依赖于其它的类的,当有某个类时才会装配,所以这次过滤的就是根据是否有某个class进而决定是否装配的。这些类所依赖的类都写在META-INF/spring-autoconfigure-metadata.properties文件里)

SpringFactoriesLoader源码

SpringFactoriesLoader作用是加载配置,属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类。

public abstract class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null)
            return result;
        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    List<String> factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
...

3、自动装配总结

1)启动类的@SpringBootApplication注解主要由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解组成,这三个注解共同完成了自动装配功能;
2)@SpringBootConfiguration 注解标记启动类为配置类;
3)@ComponentScan 注解实现启动时扫描启动类所在的包以及子包下所有标记为bean的类,并在IOC容器中注册为bean;
4)@EnableAutoConfiguration通过 @Import 注解导入 ImportSelector 的子类 AutoConfigurationImportSelector类,然后通过AutoConfigurationImportSelector 类的 selectImports 方法去读取需要被自动装配的组件依赖下的spring.factories文件配置的组件的类全名,并按照一定的规则过滤掉不符合要求的组件的类全名,将剩余读取到的各个组件的类全名集合返回给IOC容器并将这些组件注册为bean;

  • AutoConfigurationImportSelector类中重写了ImportSelector中selectImports方法,批量返回需要装配的配置类。
  • AutoConfigurationImportSelector类通过selectImports方法加载读取所有 spring-boot-autoconfigure 依赖下的 spring-autoconfigure-metadata.properties 配置文件和spring.factories 配置文件的内容,并根据 AutoConfigurationImportSelector 类下的 AutoConfigurationImportFilter过滤器的过滤规则和 spring-autoconfigure-metadata.properties 配置文件的内容过滤掉 spring.factories文件中需要被过滤掉的组件元素(当然这之前还有一步根据@EnableAutoConfiguration注解的 exclude 和 excludeName属性过滤 spring.factories 配置文件的内容,由于 @EnableAutoConfiguration注解的这两个属性默认为空,所以这步操作什么都没做),最终返回spring.factories文件中剩余组件的类全名数组,并由IOC容器注册为Bean。
  • SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。这些功能配置类要生效的话,会去classpath中找是否有该类的依赖类(也就是pom.xml必须有对应功能的jar包才行)并且配置类里面注入了默认属性值类,功能类可以引用并赋默认值。生成功能类的原则是自定义优先,没有自定义时才会使用自动装配类。

4、SpringApplication执行流程

SpringApplication的run方法的主要流程如下:

1) 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:

  • 根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。
  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
  • 推断并设置main方法的定义类。

2) SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。

3) 创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。

4) 遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。

5) 如果SpringApplication的showBanner属性被设置为true,则打印banner。

6) 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。

7) ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriesLoader,查找并加载classpath中所有可用的ApplicationContext-Initializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。

8) 遍历调用所有SpringApplicationRunListener的contextPrepared()方法。

9) 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。

10) 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。

11) 调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。

12) 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。

13) 正常情况下,遍历执行SpringApplicationRunListener的finished()方法、(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理)

去除事件通知点后,整个流程如下:

image-1660614772005

0

评论区