欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Boot 原理分析

程序员文章站 2022-05-04 16:41:43
...

SpringBoot 原理分析

一、起步依赖原理分析

1.1 分析spring-boot-starter-parent

按住 Ctrl 点击 pom.xml 中的 spring-boot-starter-parent,跳转到了 spring-boot-starter-parent 的 pom.xml,xml 配置如下(只摘抄了部分重点配置):

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.0.1.RELEASE</version>
  <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

按住 Ctrl 点击 pom.xml 中的 spring-boot-starter-dependencies,跳转到了 spring-boot-starter-dependencies 的 pom.xml,xml 配置如下(只摘抄了部分重点配置):

<properties>
  	<activemq.version>5.15.3</activemq.version>
  	<antlr2.version>2.7.7</antlr2.version>
  	<appengine-sdk.version>1.9.63</appengine-sdk.version>
  	<artemis.version>2.4.0</artemis.version>
  	<aspectj.version>1.8.13</aspectj.version>
  	<assertj.version>3.9.1</assertj.version>
  	<atomikos.version>4.0.6</atomikos.version>
  	<bitronix.version>2.1.4</bitronix.version>
  	<build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
  	<byte-buddy.version>1.7.11</byte-buddy.version>
  	... ... ...
</properties>
<dependencyManagement>
  	<dependencies>
      	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot</artifactId>
        	<version>2.0.1.RELEASE</version>
      	</dependency>
      	<dependency>
        	<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-test</artifactId>
        	<version>2.0.1.RELEASE</version>
      	</dependency>
      	... ... ...
	</dependencies>
</dependencyManagement>
<build>
  	<pluginManagement>
    	<plugins>
      		<plugin>
        		<groupId>org.jetbrains.kotlin</groupId>
        		<artifactId>kotlin-maven-plugin</artifactId>
        		<version>${kotlin.version}</version>
      		</plugin>
      		<plugin>
        		<groupId>org.jooq</groupId>
        		<artifactId>jooq-codegen-maven</artifactId>
        		<version>${jooq.version}</version>
      		</plugin>
      		<plugin>
        		<groupId>org.springframework.boot</groupId>
        		<artifactId>spring-boot-maven-plugin</artifactId>
        		<version>2.0.1.RELEASE</version>
      		</plugin>
          	... ... ...
    	</plugins>
  	</pluginManagement>
</build>

从上面的 spring-boot-starter-dependencies 的 pom.xml 中我们可以发现,一部分坐标的版本、依赖管理、插件管理已经定义好,所以我们的 SpringBoot 工程继承 spring-boot-starter-parent 后已经具备版本锁定等配置了。所以起步依赖的作用就是进行依赖的传递。

1.2 分析spring-boot-starter-web

spring-boot-starter-webSpring Boot 场景启动器,帮我们导入了 web 模块正常运行所依赖的组件。

Spring Boot 将所有的功能场景都抽取出来,做成一个个的 starters(启动器)。只需要在项目中引入这些 starter, 相关场景的所有依赖都会导入进来,要用什么功能就导入什么场景的启动器。

按住 Ctrl 点击 pom.xml 中的 spring-boot-starter-web,跳转到了 spring-boot-starter-web 的 pom.xml,xml 配置如下(只摘抄了部分重点配置):

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  	<modelVersion>4.0.0</modelVersion>
  	<parent>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starters</artifactId>
    	<version>2.0.1.RELEASE</version>
  	</parent>
  	<groupId>org.springframework.boot</groupId>
  	<artifactId>spring-boot-starter-web</artifactId>
  	<version>2.0.1.RELEASE</version>
  	<name>Spring Boot Web Starter</name>
  
  	<dependencies>
    	<dependency>
      		<groupId>org.springframework.boot</groupId>
      		<artifactId>spring-boot-starter</artifactId>
      		<version>2.0.1.RELEASE</version>
      		<scope>compile</scope>
    	</dependency>
    	<dependency>
      		<groupId>org.springframework.boot</groupId>
      		<artifactId>spring-boot-starter-json</artifactId>
      		<version>2.0.1.RELEASE</version>
      		<scope>compile</scope>
    	</dependency>
    	<dependency>
      		<groupId>org.springframework.boot</groupId>
      		<artifactId>spring-boot-starter-tomcat</artifactId>
      		<version>2.0.1.RELEASE</version>
      		<scope>compile</scope>
    	</dependency>
    	<dependency>
      		<groupId>org.hibernate.validator</groupId>
      		<artifactId>hibernate-validator</artifactId>
      		<version>6.0.9.Final</version>
      		<scope>compile</scope>
    	</dependency>
    	<dependency>
      		<groupId>org.springframework</groupId>
      		<artifactId>spring-web</artifactId>
      		<version>5.0.5.RELEASE</version>
      		<scope>compile</scope>
    	</dependency>
    	<dependency>
      		<groupId>org.springframework</groupId>
      		<artifactId>spring-webmvc</artifactId>
      		<version>5.0.5.RELEASE</version>
      		<scope>compile</scope>
    	</dependency>
  	</dependencies>
</project>

从上面的 spring-boot-starter-web 的 pom.xml 中我们可以发现,spring-boot-starter-web 就是将 web 开发要使用的 spring-web、spring-webmvc 等坐标进行了“打包”,这样我们的工程只要引入 spring-boot-starter-web 起步依赖的坐标就可以进行 web 开发了,同样体现了依赖传递的作用。

二、自动配置原理

2.1 自动配置原理解析

主程序的 @SpringBootApplication 注解标注在某个类上就说明这个类是 Spring Boot 的主配置类,Spring Boot 就应该运行这个类的 main 方法来启动 Spring Boot 应用。

@SpringBootApplication
public class MySpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApplication.class);
    }
}

按住 Ctrl 点击查看启动类上的注解 @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 的一个配置类。由 @Confituration 标注的类本质上也属于容器中的一个 @Component

  • @EnableAutoConfiguration:Spring Boot 自动配置功能开启。以前我们需要配置的东西,如今 Spring Boot 帮我们自动配置了。

按住 Ctrl 点击查看注解 @EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	... ... ...
}

其中:

  • @AutoConfigurationPackage:自动配置包。

    进入 @AutoConfigurationPackage 可以看到如下配置:

    @Import({Registrar.class})
    public @interface AutoConfigurationPackage {
    	... ... ...
    }
    

    @Import({Registrar.class}):Spring 的底层注解 @Import,给容器中导入一个组件。导入的组件为 Registrar.class,由它将主配置类所在的包以及下面所有子包的所有组件扫描到 Spring 容器中。

  • @Import(AutoConfigurationImportSelector.class)AutoConfigurationImportSelector 是指定导入哪些组件的选择器。它会将所有需要导入的组件以全类名的方式返回,这些组件将会被添加到容器中。

    按住 Ctrl 点击查看 AutoConfigurationImportSelector 源码:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
    			AnnotationAttributes attributes) {
    		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
    				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    		
    		return configurations;
    }
    

    其中,loadFactoryNames() 方法的作用就是从 META-INF/spring.factories 文件中读取指定类对应的类名称列表 :
    Spring Boot 原理分析

    spring.factories 文件中有关自动配置的配置信息如下:

    ... ... ...
    
    org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
    
    ... ... ...
    

    上面配置文件存在大量的以 Configuration 为结尾的类名称,这些类就是存有自动配置信息的类。

    Spring Boot 在启动的时候从上面的配置文件中获取 EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作 。

    有了自动配置类,就免去了之前我们使用 Spring 框架需要配置大量繁琐的 XML 的工作,使我们更专注于功能的开发。

例:

我们以 ServletWebServerFactoryAutoConfiguration 为例来分析源码:

// 表示这是一个配置类,可以给容器中添加组件
@Configuration 
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 判断当前项目有没有 ServletRequest 这个类,如果有,当前配置类才生效。
@ConditionalOnClass(ServletRequest.class)	
// 判断当前应用是否是 web 应用,如果是,当前配置类才生效
@ConditionalOnWebApplication(type = Type.SERVLET) 
// 启动指定类的 ConfigurationProperties 功能,就是把配置文件的配置数据注入到类中
@EnableConfigurationProperties(ServerProperties.class) 
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
	... ... ...
}

其中:

@ConditionalOnXXX:该注解会根据给定的条件判断,决定这个配置类是否生效。一旦这个配置类生效,这个配置类就会给容器中添加各种组件,这些组件的属性是从对应的 properties 类中获取的,这些类里面的每一个属性都是和配置文件绑定的。

@EnableConfigurationProperties(ServerProperties.class):该注解代表加载 ServerProperties 服务器配置属性类。

进入 ServerProperties.class, 源码如下:

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

	/**
	 * Server HTTP port.
	 */
	private Integer port;

	/**
	 * Network address to which the server should bind.
	 */
	private InetAddress address;
  
  	... ... ...
}

其中:

prefix = "server" 表示 SpringBoot 配置文件中的前缀,SpringBoot 会将配置文件中以 server 开始的属性映射到该类的字段中。映射关系如下:
Spring Boot 原理分析

2.2 Spring Boot 自动配置的精髓

  1. Spring Boot 启动会加载大量的自动配置类
  2. 我们看我们需要的功能有没有已经被 Spring Boot 提供的自动配置类默认写好
  3. 如果有,我们再来看这个自动配置类中到底配置了哪些组件,如果有我们要用的组件,就无需我们手动配置了
  4. 如果没有,我们给容器中自动配置类添加组件的时候,会从 Properties 类中获取某些属性,所以我们可以在配置文件中指定这些属性的值。

2.3 细节

2.3.1 @Conditional 派生注解

@Conditional 派生注解的作用是:必须 @Conditional 注解所指定的条件成立,该注解所修饰的配置类里面的所有内容才能生效,才给容器中添加组件。

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的 Java 版本是否符合要求
@ConditionalOnBean 容器中存在指定 Bean
@ConditionalOnMissingBean 容器中不存在指定 Bean
@ConditionalOnExpression 满足 SpEL 表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的 Bean,或者这个 Bean 是首选 Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是 web 环境
@ConditionalOnNotWebApplication 当前不是 web 环境
@ConditionalOnJndi JNDI 存在指定项
2.3.2 查看自动配置类是否生效

我们可以通过在主配置文件中配置 debug 属性来达到让控制台打印自动配置报告的目的,这样我们就可以方便的知道哪些自动配置类生效了。

debug = true

在主配置文件中配置好 debug 属性之后,启动 Spring Boot,查看控制台自动配置报告:

============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:(自动配置类启用的)
-----------------

   AopAutoConfiguration matched:
      - @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)

   AopAutoConfiguration.AspectJAutoProxyingConfiguration matched:
      - @ConditionalOnClass found required class 'org.aspectj.weaver.Advice' (OnClassCondition)
   ... ...

Negative matches:(没有启用,没有匹配成功的自动配置类)
-----------------

   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
   ... ...