Bean自动装配原理

时间:2020-11-14 13:10:55   收藏:0   阅读:22

Spring Boot实现自动配置加载的原理

一、Bean自动装载的核心问题

Spring Boot里面的各种Bean(类对象)能够实现自动装载,自动的装载帮我们减少了XML的配置,和手动编码进行Bean的加载工作。从而极大程度上帮我们减少了配置量和代码量。

技术分享图片

要实现Bean的自动装载,需要解决两个问题

这个问题通过配置文件来解决(就好比Maven中依赖传递的关系),在配置A情况下去装载BeanY;在配置B情况下去装载BeanZ。(通常情况下配置A和B会有默认值,来决定默认的装载行为,这样就不需要我们配置了,进一步减少配置量)

当BeanA装载完成之后再去装载BeanY,BeanY装载完成之后才去装载BeanX。这个装载顺序问题由@ConditionOnXXXXXXX注解来解决。

二、全局配置文件

SpringBoot使用一个全局的配置文件,配置文件名是固定的;(properties或者是yml格式的文件

全局配置文件的作用:修改SpringBoot自动配置的默认值,通过配置来影响SpringBoot自动加载行为。

三、配置加载原理源码解析

1、首先是SpringBoot入口函数的@SpringBootApplication注解中的@EnableAutoConfiguration就是打开自动装载的一个行为。没有的话不走装配。

2、所有的Spring Boot应用程序都是以SpringApplication.run()作为应用程序入口的。下面我们来一步一步跟踪一下这个函数。

首先点进去:发现它又调用了类中的run方法。(这里的run方法都是重载的)

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class[]{primarySource}, args);
}

再点进去:发现它带参构造创建了本类对象又调用了run方法。run方法传入了SpringApplication对象和一些运行期参数。

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}

继续点入:我们主要是找它怎么实现Bean的自动装载的,发现它又调用了本类一个获取Spring工厂实例的方法

    public ConfigurableApplicationContext run(String... args) {
            ...
        try {
            ...
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            ...
            if (this.logStartupInfo) {
                ...
            }
            ...
        } catch (Throwable var10) {
            ...
        }
            ...
    }

继续点入:从本方法中看到出现一个SpringFactoriesLoader的一个类,这里面体现了Spring Boot加载配置文件的核心逻辑。

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = this.getClassLoader();
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

进入这个类:会发现他有一个常量:工厂资源位置FACTORIES_RESOURCE_LOCATION。到这儿也就看不懂了,肯定会到时候根据这个位置信息找内容。

从`META-INF/spring.factories`文件夹下下面加载了spring.factories文件资源。然后读取文件中的ClassName作为值放入Properties。

public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    ...
}

根据这个路径的文件名搜索一下:会发现有很多。

技术分享图片

随便点进去一个,你就会发现这里的每一行都是一个自动装配的类。因为SpringBoot入口方法的注解中就开启了@EnableAutoConfiguration注解。在EnableAutoConfiguration往下的每一行都可以被装载。

技术分享图片

然后通过反射机制,对spring.factories里面的类资源进行实例化,所以spring.factories文件里面究竟写了什么类?这些类是做什么的?就是我们下一步要探究的问题了。

宏观原理图:

技术分享图片

3、每一行的自动装配类里面是如何完成自动装配的

详情请看标题五

四、@EnableAutoConfiguration 作用

SpringBoot入口启动类使用了SpringBootApplication,实际上就是开启了自动配置功能@EnableAutoConfiguration。

技术分享图片

SpringFactoriesLoader会以@EnableAutoConfiguration的包名和类名org.springframework.boot.autoconfigure.EnableAutoConfiguration为Key查找spring.factories文件,并将value中的类名实例化加载到Spring Boot应用中。如下图:

技术分享图片

spring.factories文件中的每一行都是一个自动装配类。

五、Bean的自动装配实现原理简述

每一个自动配置类进行自动配置功能(spring.factories中的每一行对应的类),我们以HttpEncodingAutoConfiguration为例讲解一下:

//加载application全局配置文件内的部分配置到HttpEncodingProperties里面
@Configuration
@EnableConfigurationProperties({HttpEncodingProperties.class}) 
//当web容器类型是servlet的时候执行本类中的自动装配代码
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
//当有一个CharacterEncodingFilter的这样一个类的字节码文件时时执行本类中的自动装配代码
@ConditionalOnClass({CharacterEncodingFilter.class})
//当spring.http.encoding配置值为enabled的时候执行本类中的自动装配代码
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true   //如果application配置文件里面不配置,默认为true
)
public class HttpEncodingAutoConfiguration {
    private final HttpEncodingProperties properties;

    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
        this.properties = properties;
    }

    @Bean
    //当没有CharacterEncodingFilter这个Bean就实例化CharacterEncodingFilter为一个bean
    @ConditionalOnMissingBean
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.RESPONSE));
        return filter;
    }

    @Bean
    public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
        return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
    }
    
   //此处省略与自动加载无关的代码:HttpEncode的逻辑及其他
}

在配置类加载过程中,大量的使用到了条件加载注解:

条件注解 使用说明
@ConditionalOnClass classpath中存在该类字节码文件时,才执行实例化方法或将类实例化
@ConditionalOnMissingClass classpath中不存在该类字节码文件时,才执行实例化方法。(不存在A的时候去初始化B)
@ConditionalOnBean DI容器中存在该类型Bean时,才执行实例化方法或将类实例化
@ConditionalOnMissingBean DI容器中不存在该类型Bean时,才执行实例化方法或将类实例化
@ConditionalOnSingleCandidate DI容器中该类型Bean只有一个或@Primary的只有一个时,才执行实例化方法或将类实例化
@ConditionalOnExpression SpEL表达式结果为true时,才执行实例化方法或将类实例化
@ConditionalOnProperty 参数设置或者值一致时,才执行实例化方法或将类实例化
@ConditionalOnResource 指定的文件存在时,才执行实例化方法或将类实例化
@ConditionalOnJndi 指定的JNDI存在时,才执行实例化方法或将类实例化
@ConditionalOnJava 指定的Java版本存在时,才执行实例化方法或将类实例化
@ConditionalOnWebApplication Web应用环境下,才执行实例化方法或将类实例化
@ConditionalOnNotWebApplication 非Web应用环境下,才执行实例化方法或将类实例化

我们讲的这个实现原理实际上就是一个自定义spring-boot-starter的实现原理,我们会在后面章节中自己编码实现一个分布式文件系统fastdfs与spring boot整合的starter。大家届时会有更深一步的理解。在以上的自动装配过程中依赖于HttpEncodingProperties的自定义属性,我们后面会讲如何读取自定义配置属性。

原文:https://www.cnblogs.com/jinyuanya/p/13972804.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!