springboot 之 自动配置原理

面试的时候,其实远远不需要回答的这么具体,你只需要这样回答:

Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。

一、前言

相信接触过springboot的朋友都知道,springboot有各种Starter方便引入依赖,同时通过IDE想要什么依赖直接勾选加进来就可以了,非常方便。SpringBoot的核心就是自动配置,而支持自动配置的是一个个Starter项目。除了官方已有的starter,用户自己也可以根据规则自定义自己的Starter项目。

而Starter与自动配置的关系个人理解是这样的:项目在添加某一个Starter后,如果没有特殊需要,基本上是不需要任何配置可以直接使用的,原因是这个Starter已经通过【自动配置】加载了一些默认配置;如果有需要,可以通过springboot配置文件(比如application.yml)设置一些配置项覆盖默认配置。

二、自定义Starter

1、说明

个人认为,理解【自动配置】的原理之前,最好先了解自定义starter的过程,这样更方便理解自动配置。

自定义starter场景:如果springboot自带的入口类不能满足要求,则可以自定义Starter

2、使用

1)创建项目

首先要为starter起名字:

  • Spring官方的Starter:命名为【spring-boot-starter-名字】,如:spring-boot-starter-web
  • Spring官方建议非官方的自定义Starter:命名为【名字-spring-boot-starter】,如myxxx-spring-boot-starter

使用IDEA创建一个普通的maven项目

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

说明1:其中spring-boot-starter-web里面会引用spring-boot-starter里面会引用spring-boot-autoconfigure(网上有很多示例是直接引用后面两个其中之一的,也是可以的)

说明2:为了使用dependencyManagement进行依赖的版本管理,可以设置自定义starter项目的父项目为spring-boot-starter-parent

说明3:自定义的Starter时不能有启动入口的,也就是说它只能作为工具类项目。所以,不要把自定义Starter的pom.xml写成一个可启动的项目

完整pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
 
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.15.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
 
  <groupId>com.mzj.myproject</groupId>
  <artifactId>myproject-spring-boot-starter</artifactId>
  <version>1.0-SNAPSHOT</version>
 
  <name>myproject-spring-boot-starter</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
 
</project>

3)自定义properties类

说明:在使用Spring官方提供的Starter时,可以在application.yml或者application.properties文件中配置参数,以覆盖默认值。在自定义Starter时,也可以根据需要来配置properties类,以保存配置信息。这里配置前缀统一采用spring.mystarter

package com.mzj.myproject;
import org.springframework.boot.context.properties.ConfigurationProperties;
 
@ConfigurationProperties(prefix = "spring.mystarter")
public class MyStarterProperties {
    // 参数
    private String parameter;
    private String url;
    private int port;
 
    public String getParameter() {
        return parameter;
    }
 
    public void setParameter(String parameter) {
        this.parameter = parameter;
    }
 
    public String getUrl() {
        return url;
    }
 
    public void setUrl(String url) {
        this.url = url;
    }
 
    public int getPort() {
        return port;
    }
 
    public void setPort(int port) {
        this.port = port;
    }
}

4)定义核心服务类

说明:每个starter都有自己要处理的业务功能,因此定义服务类,这个服务类有两个作用,一个为引入的项目本身的功能性服务,另外一个用来springboot自动配置时的判断依据。

package com.mzj.myproject;
 
public class MyStarterService {
    private MyStarterProperties myproperties;
 
    public MyStarterService() {
    }
 
    public MyStarterService(MyStarterProperties myproperties) {
        this.myproperties = myproperties;
    }
 
    public String print() {
        System.out.println("参数1: " + myproperties.getParameter());
        System.out.println("参数2: " + myproperties.getUrl());
        System.out.println("参数3: " + myproperties.getPort());
        String s = myproperties.getParameter();
        return s;
    }
}

5)定义自动配置类

每个Starter一般至少有一个自动配置类,命名规则为:【名字+AutoConfiguration】,如:MyStarterServiceAutoConfiguration。

@Configuration用来声明该类为一个配置类;

@ConditionalOnClass注解说明只有当MyStarterService类存在于classpath中时才会进行自动配置;

@EnableConfigurationProperties作用时使使用 @ConfigurationProperties 注解的类生效,这里是MyStarterProperties,也就是将application.properties中对应的属性配置设置于MyStarterProperties对象中;

myStarterService方法上的注解,

  • @Bean表明该方法实例化的对象会被加载到容器当中;
  • @ConditionalOnMissingBean当容器中没有指定Bean的情况下,自动配置MyStarterService类
  • @ConditionalOnProperty指定了配置文件中spring.mystarter.enabled=true时才进行相应的自动装配。

配置方法见以下代码:


package com.mzj.myproject;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
@EnableConfigurationProperties(MyStarterProperties.class)//@EnableConfigurationProperties注解的作用是:使使用 @ConfigurationProperties 注解的类生效,这里是MyStarterProperties
/**
 * Description: 当类路径classpath下有指定的类的情况下进行自动配置
 */
@ConditionalOnClass(MyStarterService.class)
/**
 * Description: 配置文件中matchIfMissing =true时进行自动配置
 * 
 * 配置文件中spring.mystarter.enabled=true时才进行相应的自动装配
 */
@ConditionalOnProperty(prefix = "spring.mystarter", value = "enabled", matchIfMissing = true)
public class MyStarterServiceAutoConfiguration {
    @Autowired
    private MyStarterProperties myproperties;//使用配置
 
    @Bean
    /**
     * Description: 当容器中没有指定Bean的情况下,自动配置MyStarterService类
     */
    @ConditionalOnMissingBean(MyStarterService.class)
    public MyStarterService myStarterService() {
        MyStarterService myStarterService = new MyStarterService(myproperties);
        return myStarterService;
    }
}

其中MyStarterService的创建是在Configuration类(MyStarterServiceAutoConfiguration)中的myStarterService方法创建的

最后,当所有的基础代码和自动配置类都准备完成,就需要对其进行注册:在resources文件下新建目录META-INF,在目录中新建spring.factories文件,并在文件中配置自动配置类XXXXAutoConfiguration(MyStarterServiceAutoConfiguration):

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mzj.myproject.MyStarterServiceAutoConfiguration

在spring.factories配置文件中注册MyStarterServiceAutoConfiguration类。如果有多个自动配置类,用逗号分隔换行即可。

6)打包发布

至此,一个基于Spring Boot的自动配置starter便完成了。

在完成上面的配置后,就可以打包生成JAR文件,然后就可以像使用官方Starter那样使用了。使用“maven:install”将其打包到本地maven仓库或上传至私服。其他项目便可以通过maven依赖使用。

如果不发布到Maven仓库,则需要用户手动添加依赖:

7)测试

新建普通springboot项目,IDEA创建项目时,只选择了依赖spring-boot-starter-web

之后在pom.xml中增加我们自定义starter依赖:

<dependency>
	<groupId>com.mzj.myproject</groupId>
	<artifactId>myproject-spring-boot-starter</artifactId>
	<version>1.0-SNAPSHOT</version>
</dependency>

测试一:

在项目application.yml中设置加入以下参数:

然后需要使用的地方注入starter包中的service依赖即可,比如下面的Controller中:


package com.mzj.myproject.controller;
 
import com.mzj.myproject.MyStarterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("teststarter")
public class TestController {
 
    @Autowired
    private MyStarterService myStarterService;
 
    @RequestMapping(value = "get", method = RequestMethod.GET)
    public String teststarter(String userName) {
        System.out.println("myStarterService :" + myStarterService.print());
        return userName;
    }
}

然后启动springboot项目,postman输入:http://localhost:8080/teststarter/get?userName=123

控制台输出:

测试二:把配置文件中spring.mystarter.enabled=false,则不进行自动装配,访问服务时出现未找到异常:

如果不设置spring.mystarter.enabled属性,默认为true

8)结论

通过此示例可以看出,MyStarterService对象被自动配置,并且测试通过。

而针对我们自定义的starter,可以进行进一步拓展,实现各种基础功能,而当其他项目需要时只需【引入对应的依赖】+【配置具体的参数】即可马上使用,是不是非常方便?

9)总结

正规的Starter是一个独立的工程,可以在Maven仓库中注册、发布,以便供其他开发人员使用,自定义Starter包括以下几方面内容:

  • 自动配置文件:根据classpath下是否存在指定的类来决定是否要执行该功能的自动配置(存在才进行自动配置——自动配置类的@ConditionalOnClass(MyStarterService.class)注解)
  • 可以通过@ConditionalOnProperty注解设置自动装配是否启动可以通过参数进行设置
  • spring.factories:指导springboot找到指定的自动配置文件
  • endpoint:包含对服务的描述、界面、交互(业务信息的查询)——本文未涉及
  • health indicator:该Starter提供服务的健康指标——本文未涉及

三、springboot自动配置原理

Spring Boot关于自动配置的源码在spring-boot-autoconfigure-x.x.x.x.jar中。

掌握了自定义starter,接下来看看spring自动配置的原理:

一、springboot程序入口类要使用@SpringBootApplication注解声明,它是SpringBoot的核心注解

package com.mzj.myproject;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

二、@SpringBootApplication注解里面,最主要的就是@EnableAutoConfiguration注解,这么直白的名字,一看就知道它要开启自动配置,再进去@EnableAutoConfiguration注解。

三、@EnableAutoConfiguration注解也是一个派生注解,可以看到,在@EnableAutoConfiguration注解内使用到了@import注解来完成导入配置的功能:

package org.springframework.boot.autoconfigure;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.SpringFactoriesLoader;
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
 
    //略
}

而@Import注解的参数AutoConfigurationImportSelector内部则是使用了SpringFactoriesLoader.loadFactoryNames方法进行扫描具有META-INF/spring.factories文件的jar包。下面是2.1.16.RELEASE实现源码:

这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				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;
	}

任何一个springboot应用,都会引入spring-boot-autoconfigure(因为springboot入口类需要通过@SpringBootApplication注解修饰,而这个注解就是在spring-boot-autoconfigure包,具体方式是通过直接引入或者通过引入比如spring-boot-starter-web间接引入),而spring.factories文件就在该包下面。spring.factories文件是Key=Value形式,多个Value时使用,隔开,该文件中定义了:

  • 初始化信息
  • 监听器信息(应用监听器、自动配置导入监听器)
  • 过滤信息
  • 需要springboot进行自动配置的类范围的key:org.springframework.boot.autoconfigure.EnableAutoConfiguration,如下所示:

上面的EnableAutoConfiguration配置了多个类,这些都是Spring Boot中内置的需要进行自动配置相关类;在启动过程中会解析对应类配置信息。每个XXXConfiguation类都定义了相关bean的实例化配置。都说明了哪些bean可以被自动配置,什么条件下可以自动配置,并把这些bean实例化出来。

如果我们自定义了一个starter的话,也要在该starter的jar包中提供 spring.factories文件,并且为其配置org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置类。所有框架的自动配置流程基本都是一样的:

  • 判断是否引入框架(使用各种条件注解限定自动配置生效条件)
  • 获取配置参数
  • 根据配置参数初始化框架相应组件

每一个XxxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,以下为SpringBoot内置条件注解(红色的是经常使用的):

  • @ConditionalOnBean:当SpringIoc容器内存在指定Bean的条件
  • @ConditionalOnClass:当SpringIoc容器内存在指定Class的条件
  • @ConditionalOnExpression:基于SpEL表达式作为判断条件
  • @ConditionalOnJava:基于JVM版本作为判断条件
  • @ConditionalOnJndi:在JNDI存在时查找指定的位置
  • @ConditionalOnMissingBean:当SpringIoc容器内不存在指定Bean的条件
  • @ConditionalOnMissingClass:当SpringIoc容器内不存在指定Class的条件
  • @ConditionalOnNotWebApplication:当前项目不是Web项目的条件
  • @ConditionalOnProperty:指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true。
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnSingleCandidate:当指定Bean在SpringIoc容器内只有一个,或者虽然有多个但是指定首选的Bean
  • @ConditionalOnWebApplication:当前项目是Web项目的条件

以上注解都是元注解@Conditional演变而来的,根据不用的条件对应创建以上的具体条件注解。

举例讲解

本小节参考:https://blog.csdn.net/u014745069/article/details/83820511

接下来,以ServletWebServerFactoryAutoConfiguration配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081,是如何生效的(当然不配置也会有默认值,这个默认值来自于org.apache.catalina.startup.Tomcat)。

上图中第2个红框用来加载我们自定义的配置(比如:server.port=9090),下面的红框加载默认配置(以tomcat配置作为默认配置),这种设置默认配置的方式适用于涉及多个配置中有一些需要自定义,有一些采用默认配置,比如server.ip进行自定义,而server.port采用默认的

在ServletWebServerFactoryAutoConfiguration类上,有一个@EnableConfigurationProperties注解:开启配置属性,而它后面的参数是一个ServerProperties类,这就是习惯优于配置的最终落地点。

在这个类上,我们看到了一个非常熟悉的注解:@ConfigurationProperties,它的作用就是从配置文件中绑定属性到对应的bean上,而@EnableConfigurationProperties负责导入这个已经绑定了属性的bean到spring容器中(见上面截图)。那么所有其他的和这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些XxxxProperties类,它与配置文件中定义的prefix关键字开头的一组属性是唯一对应的。

至此,我们大致可以了解。在全局配置的属性如:server.port等,通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties注解导入到Spring容器中。

而诸多的XxxxAutoConfiguration自动配置类,就是Spring容器的JavaConfig形式,作用就是为Spring 容器导入bean,而所有导入的bean所需要的属性都通过xxxxProperties的bean来获得。

面试的时候,其实远远不需要回答的这么具体,你只需要这样回答:

Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。

通过一张图标来理解一下这一繁复的流程:

springboot自动化配置默认值设置与自动配置覆盖默认值(20201012)

        
1、ServletWebServerFactoryConfiguration:

        @Bean
        public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }
        ------创建TomcatServletWebServerFactory中默认属性为8080

2、WebServerFactoryCustomizerBeanPostProcessor:
    
        执行流程

3、ServletWebServerFactoryAutoConfiguration

        加载spring.factories文件中Configuration类,创建Service类(通过Properties创建,这里完成了自动配置8081注入创建的Service对象)return new TomcatServletWebServerFactoryCustomizer(serverProperties);
        
        这里Service类是TomcatServletWebServerFactoryCustomizer

4、执行Service的方法,这里是ServletWebServerFactoryCustomizer的customize方法:将个性化值覆盖默认值

总结

综上是对自动配置原理的讲解。当然,在浏览源码的时候一定要记得不要太过拘泥与代码的实现,而是应该抓住重点脉络:

  • 一定要记得XxxxProperties类的含义是:封装配置文件中相关属性;
  • XxxxAutoConfiguration类的含义是:自动配置类,目的是给容器中添加组件。
  • 而其他的主方法启动,则是为了加载这些五花八门的XxxxAutoConfiguration类。

附录

1、springboot支持在application.yml中配置的参数

https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#common-application-properties

猜你喜欢

转载自blog.csdn.net/My_Way666/article/details/112968137