boot自动配置的原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了boot自动配置的原理相关的知识,希望对你有一定的参考价值。

参考技术A Spring Boot是基于Spring开发的,是约定大于配置的核心思想。并且集成了大量的第三方库配置比如redis、mongoDB、jpa等。Spring Boot就相当于maven整合了所有jar包,Spring Boot整合了所有框架。其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程,并不少什么新的框架。

二、Spring Boot优点

优点其实就是简单、快速

快速创建独立运行的Spring项目以及主流框架集成
使用嵌入式的server容器,应用无需打成WAR包
starters自动依赖与版本控制
有大量的自动配置,简化开发
准生产环境运行应用监控
与云计算的天然集成
Spring Boot主程序分析

//@SpringBootApplication标注这个类是一个Springboot的应用@SpringBootApplicationpublicclassSpringboot02DemoApplicationpublicstaticvoidmain(String[] args)//将Springboot应用启动 SpringApplication.run(Springboot02DemoApplication.class, args);

SpringBootApplication源码剖析,进入源码,结果发现其实它是一个组合注解

第一个SpringBootConfiguration注解:@SpringBootConfiguration-->是Spring Boot配置类。下面有一个叫@Configuration:它是配置类,下面又有@Component,其实它就是一个注入组件。

第二个@EnableAutoConfiguration注解:是开启自配配置功能

@AutoConfigurationPackage//自动配置包
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

@AutoConfigurationPackage:自动配置包,使用@Import(AutoConfigurationImportSelector.class)注解来完成的,它是spring boot底层注解,作用是给容器中导入组件。

自动配置原理

(1)Spring Boot启动的时候首先加载主配置类,开启啦自动配置的功能(@EnableAutoConfiguration)

(2)自动配置功能@EnableAutoConfiguration的作用:它是利用了

@Import(AutoConfigurationImportSelector.class)给容器中导入一些组件。那么,他会给我们导入哪些组件呢?进入AutoConfigurationImportSelector源码看一下部分源码如下。

//@EnableAutoConfiguration注解
@AutoConfigurationPackage
/@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

@Import(AutoConfigurationImportSelector.class)自动配置导入选择

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered
//----部分源码省略----//
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)
if (!isEnabled(annotationMetadata))
return EMPTY_ENTRY;

AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//----部分源码省略----//
参考技术B

方法/步骤

    SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration。

    请点击输入图片描述

    @EnableAutoConfiguration的作用是利用AutoConfigurationImportSelector给容器中导入一些组件。

    请点击输入图片描述

    可以查看public String[] selectImports(AnnotationMetadata annotationMetadata)方法的内容。

    请点击输入图片描述

    通过protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,      AnnotationAttributes attributes)获取候选的配置,这个是扫描所有jar包类路径下"META-INF/spring.factories";

    请点击输入图片描述

    然后把扫描到的这些文件包装成Properties对象。

    请点击输入图片描述

    从properties中获取到EnableAutoConfiguration.class类名对应的值,然后把他们添加在容器中。

    请点击输入图片描述

    整个过程就是将类路径下"META-INF/spring.factories"里面配置的所有EnableAutoConfiguration的值加入到容器中。

    请点击输入图片描述

    每一个这样XXAutoConfiguration类都是容器中的一个组件都加入到容器中,用他们来做自动配置。

    每一个自动配置类进行自动配置功能,以HttpEncodingAutoConfiguration为例解释自动配置原理。

    请点击输入图片描述

    根据当前不同的条件判断,决定这个配置是否生效。

    请点击输入图片描述

Spring boot运行原理-自定义自动配置类

在前面SpringBoot的文章中介绍了SpringBoot的基本配置,今天我们将给大家讲一讲SpringBoot的运行原理,然后根据原理我们自定义一个starter pom。
本章对于后续继续学习SpringBoot至关重要,了解SpringBoot运行原理对于我们深入学习SpringBoot有着非常重要的作用。

SpringBoot的自动配置从何而来

要想了解SpringBoot的自动配置,我们可以在源码看到相关代码和配置。
SpringBoot关于自动配置的源码在spring-boot-autoconfigure-2.1.x.jar内,打开maven依赖我们可以看见

技术图片

如果想了解SpringBoot为我们做了哪些自动配置,可以通过下面方式查看当前项目中已启用和未启用的自动配置的报告。
  1. 运行jar时增加--debug参数:
java -jar xx.jar --debug
  1. 在application.properties中设置属性:
debug=true
启动时,通过控制台我们可以看到哪些配置已使用自动配置,哪些配置没有自动配置。

已启用自动配置
技术图片

未启用自动配置
技术图片

仔细看上图我们可以发现,相关如

@ConditionalOnClass found required class ...    \\   @ConditionalOnClass did not find required class ...

的字眼非常多,可见@ConditionalOnClass注解可能在自动配置中起着主要作用,那究竟是如何起作用的呢?

运行原理

关于SpringBoot的运作原理,我们还是回归到@SpringBootApplication注解上来,这个注解是一个组合注解,它的核心功能是一个开启自动配置注解@EnableAutoConfiguration

下面我们来看下@EnableAutoConfiguration注解的源码:
@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的导入功能,AutoConfigurationImportSelector使用SpringFactoriesLoader.loadFactoryNames方法来扫描META-INF/spring.factories文件中描述的jar包,所以我们立马到刚刚打开的自动配置类中的META-INF/spring.factories中找一下是否真的有这样一个文件。

好家伙,还真的有
技术图片

马上打开看一看
技术图片

核心注解

我们打开上面配置的任何其中一个注解,一般都有下面的条件注解,打开源码spring-boot-autoconfigure下的org/springframework/boot/condition看看都有哪些注解

技术图片

简单介绍一下每个注解代表代表的条件:
@ConditionalOnBean: 当容器里有指定的Bean条件下。
@ConditionalOnClass: 当类路径下有指定的类的条件下。
@ConditionalOnExpression: 基于SpEL表达式作为判断条件。
@ConditionalOnJava: 基于JVM版本作为判断条件。
@ConditionalOnjndi: 在基于JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean: 当容器里没有Bean的情况下。
@ConditionalOnMIssingClass: 当类路径下没有指定的类的条件下。
@ConditionalOnNotWebApplication: 当前项目不是Web项目的条件下。
@ConditionalOnProperty: 指定的属性是否有指定的值。
@ConditionalOnResource: 类路径是否有指定的值。
@ConditionalOnSingleCandidate: 当指定Bean在容器中只有一个,或者虽然有多个但是指定首选的Bean
@ConditionalOnWebApplication: 当前项目是web项目的条件下
这些注解都是组合了@Conditional元注解,只是使用了不同的条件(Condition)
下面我们简单分析一下@ConditionalOnWebApplication注解。
@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication 
    ConditionalOnWebApplication.Type type() default ConditionalOnWebApplication.Type.ANY;

    public static enum Type 
        ANY,
        SERVLET,
        REACTIVE;

        private Type() 
        
    

看看OnWebApplicationCondition.class 是如何定义条件的

这里我们主要看isWebApplication方法,判断条件主要如下:
(1)GenericWebApplicationContext是否在类路径中;
(2)容器中是否有名为session的scope;
(3)当前容器的Environment是否为ConfigurableWebEnvironment
(4)当前的ResourceLoader是否为WebApplicationContext(ResourceLoader是ApplicationContext的顶级接口之一);
(5)构造ConditionOutcom类的isMatch方法返回布尔值来确定条件。

==这里的isWebApplication()方法,spring-boot-1.x和2.x有区别,在1.x中只判断了servlet,而在2.x增加了了reative和any的webapplication判断==

简单示例

在以往的web项目中,通常需要在web.xml中配置一个filter,对请求进行编码,如下所示:
<filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
因此如果要实现自动配置的话,需要满足一下条件:
(1)能配置CharacterEncodingFilter这个Bean;
(2)能配置encoding和forceEncoding这两个参数。

参数配置

在上一章我们讲到了类型安全的配置,Spring Boot的自动配置也是基于这一点实现的,这里的配置可以在application.properties中直接配置。
源码如下图:

技术图片

技术图片

代码解释:

(1)在application.properties配置的前缀是spring.http.encoding;
(2)默认编码方式为UTF-8,若修改可配置spring.http.encoding.charset=编码;
(3)设置force,默认为true,若修改可配置spring.http.encoding.force=false;

配置Bean

上面我们已经配置好相关参数,现在根据条件配置CharacterEncodingFilter的Bean,下面看看源码:

@Configuration
@EnableConfigurationProperties(HttpProperties.class) //1
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass(CharacterEncodingFilter.class)//2
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = "enabled",
    matchIfMissing = true
)//3
public class HttpEncodingAutoConfiguration 
    private final Encoding properties;

    public HttpEncodingAutoConfiguration(HttpProperties properties) 
        this.properties = properties.getEncoding();
    

    
    @Bean//4
    @ConditionalOnMissingBean//5
    public CharacterEncodingFilter characterEncodingFilter() 
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    

代码解释:
(1)开启属性注入,通过@EnableCondigurationProperties声明
(2)当CharacterEncodingFilter在类路径的条件下;
(3)当设置spring.http.encoding.enabled的情况下,如果没有设置则默认为true,即条件符合;
(4)像使用Java配置的方式配置CharacterEncoding这个Bean;
(5)当容器中没有这个Bean的时候新建Bean

实战-自定义一个starter pom

前面我们已经详细讲述了spring boot是如何实现自动化配置的,现在我们来动手自己写一个starter pom实现自动化配置。

要求:当某个类存在的时候,自动配置这个类的Bean,并可将Bean的属性在application.properties中配置

创建一个普通MAVEN工程

创建一个普通MAVEN工程,并加入spring boot自动配置依赖

技术图片

属性配置

我们仿照HttpProperties来配置,我们自定义starter 的配置文件
@ConfigurationProperties(prefix = "xicent.service")
public class MyServiceProperties 
    private MyServiceProperties.MyProperties myProperties = new MyServiceProperties.MyProperties();

    public MyProperties getMyProperties() 
        return myProperties;
    

    public void setMyProperties(MyProperties myProperties) 
        this.myProperties = myProperties;
    

    public static class MyProperties
        public static final String DEFAULT_NAME;
        private String author;
        private String age;
        
        static 
            DEFAULT_NAME = "wjx";
        
        
    省略 get/set..

使用类型安全的方式获取属性。author如果不设置,会给默认值。

判断依据类

public class MyService 
    private String name;
    private String age;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public String getAge() 
        return age;
    

    public void setAge(String age) 
        this.age = age;
    

这个类作为我们的判断依据类,如果存在,则创建这个类的Bean。

自动配置类(关键)


@Configuration
@EnableConfigurationProperties(MyServiceProperties.class)
@ConditionalOnClass(MyService.class)
@ConditionalOnProperty(prefix = "xicent.service",value = "enabled",matchIfMissing = true)
public class MyServiceAutoConfiguration 
    private MyServiceProperties.MyProperties properties;

    public MyServiceAutoConfiguration(MyServiceProperties properties) 
        this.properties = properties.getMyProperties();
    
    
    @Bean
    @ConditionalOnMissingBean(MyService.class)
    public MyService myService()
        MyService myService = new MyService();
        myService.setName(properties.getAuthor());
        myService.setAge(properties.getAge());
        
        return myService;
    

这里我们仿照了HttpEncodingAutoConfiguration的写法,其实MyServiceProperties也可以直接用@Autowired直接注入的。
@ConditionalOnClass判断MyService这个类是否在类路径中存在,并且容器中没有这个Bean的情况下,我们对这个Bean进行自动配置。

注册配置

在前面我们也带大家看过,每个自动配置的包中,在src/main/resources/META-INF下都会有一个spring.factories配置文件。
下面我们就将我们刚刚写好的自动配置类,在这个配置文件中进行注册。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=  com.xicent.starter.config.MyServiceAutoConfiguration
如果有多个自动配置,用","隔开,此处的"\\"是为了换行后仍能读到属性。

添加在仓库

如果是公司提供给其他项目使用,则可以直接上传到公司私服。这里为了方便测试,我们就打包到本地仓库。
直接点击idea->maven project->lifecycle->install

技术图片

新建Spring Boot项目加入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.xicent</groupId>
            <artifactId>mystarter</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

mystarter是我们刚刚手动写好的自动配置,加入web是为了待会用接口访问,方便测试。

添加配置

刚刚我们的自动配置中允许配置两个参数,其中author如果不配置,提供默认值。
xicent.service.my-properties.age=23
xicent.service.my-properties.author=kris

注入依赖

@SpringBootApplication
@RestController
public class TeststarterApplication 

    @Autowired
    MyService myService;

    @GetMapping("/")
    String testStarter()
        return myService.getName()+":"+myService.getAge();
    

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


访问接口

技术图片

如果不配置author
技术图片

到这里,我们自定义的starter pom就大功告成啦~ 是不是感觉其实挺简单的,Spring Boot自动配置的神秘面纱也就被我们悄悄揭开了。

如果您觉得有用记得分享喔~
公众号搜索:喜讯XiCent 获取更多福利~


公众号搜索:喜讯XiCent 获取更多福利资源~

以上是关于boot自动配置的原理的主要内容,如果未能解决你的问题,请参考以下文章

4Spring Boot 自动配置原理

Spring boot 自动配置原理

spring boot 2 - *自动配置原理*(续)

Spring Boot 自动化配置原理带图全面讲解

Spring Boot自动配置原理(转)

spring boot 自动配置原理