五、SpringBoot源码分析

本文深入剖析Spring Boot的starter依赖源码,解读自动配置原理,包括如何通过配置文件调整默认设置,以及自动配置实战演示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、starter起步依赖源码分析。

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

1、按住Ctrl点击pom.xml中的spring-boot-starter-parent,跳转到了spring-boot-starter-parent的pom.xml,xml配置如下

部分源码

看到了一些yaml文件,这是编写配置文件的。后面文章会专门讲。

<?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-dependencies</artifactId>
    <version>2.2.1.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
  </parent>
  <artifactId>spring-boot-starter-parent</artifactId>
  <packaging>pom</packaging>
  <name>Spring Boot Starter Parent</name>

#此处为Resources文件夹下需要我自己手动创建的配置文件
<build>
    <resources>
      <resource>
        <filtering>true</filtering>
        <directory>${basedir}/src/main/resources</directory>
        <includes>
          <include>**/application*.yml</include>
          <include>**/application*.yaml</include>
          <include>**/application*.properties</include>
        </includes>
      </resource>
      <resource>
        <directory>${basedir}/src/main/resources</directory>
        <excludes>
          <exclude>**/application*.yml</exclude>
          <exclude>**/application*.yaml</exclude>
          <exclude>**/application*.properties</exclude>
        </excludes>
      </resource>
    </resources>

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

我们看到了Propertis中有很多熟悉的Jar包版本,其实这就是帮我们在进行Jar的版本控制,以前在写SSM工程时总是会遇到Jar版本冲突,springboot为我们在源码中设计了互相匹配的版本。

<?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>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.2.1.RELEASE</version>
  <packaging>pom</packaging>
  <name>Spring Boot Dependencies</name>
  <description>Spring Boot Dependencies</description>
  <url>https://projects.spring.io/spring-boot/#</url>
  <licenses>
    <license>
      <name>Apache License, Version 2.0</name>
      <url>https://www.apache.org/licenses/LICENSE-2.0</url>
    </license>
  </licenses>
  <developers>
    <developer>
      <name>Pivotal</name>
      <email>info@pivotal.io</email>
      <organization>Pivotal Software, Inc.</organization>
      <organizationUrl>https://www.spring.io</organizationUrl>
    </developer>
  </developers>
  <scm>
    <url>https://github.com/spring-projects/spring-boot</url>
  </scm>
  <properties>
    <activemq.version>5.15.10</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.76</appengine-sdk.version>
  
    <flyway.version>6.0.8</flyway.version>
    <freemarker.version>2.3.29</freemarker.version>
    <git-commit-id-plugin.version>3.0.1</git-commit-id-plugin.version>
    <glassfish-el.version>3.0.3</glassfish-el.version>
    <glassfish-jaxb.version>2.3.2</glassfish-jaxb.version>
    <groovy.version>2.5.8</groovy.version>
    <gson.version>2.8.6</gson.version>
    <h2.version>1.4.200</h2.version>
    <hamcrest.version>2.1</hamcrest.version>
    <hazelcast.version>3.12.4</hazelcast.version>
    <hazelcast-hibernate5.version>1.3.2</hazelcast-hibernate5.version>
    <hibernate.version>5.4.8.Final</hibernate.version>
    <hibernate-validator.version>6.0.18.Final</hibernate-validator.version>
    <hikaricp.version>3.4.1</hikaricp.version>
    <junit-jupiter.version>5.5.2</junit-jupiter.version>
    <kafka.version>2.3.1</kafka.version>
    <kotlin.version>1.3.50</kotlin.version>
    <kotlin-coroutines.version>1.3.2</kotlin-coroutines.version>
    <lettuce.version>5.2.1.RELEASE</lettuce.version>
    <liquibase.version>3.8.0</liquibase.version>
    <log4j2.version>2.12.1</log4j2.version>
    <logback.version>1.2.3</logback.version>
    <slf4j.version>1.7.29</slf4j.version>
    <spring-amqp.version>2.2.1.RELEASE</spring-amqp.version>
    <spring-cloud-connectors.version>2.0.6.RELEASE</spring-cloud-connectors.version>
    <spring-data-releasetrain.version>Moore-SR1</spring-data-releasetrain.version>
    <spring-framework.version>5.2.1.RELEASE</spring-framework.version>
    <spring-hateoas.version>1.0.1.RELEASE</spring-hateoas.version>
    <spring-integration.version>5.2.1.RELEASE</spring-integration.version>
    <spring-kafka.version>2.3.3.RELEASE</spring-kafka.version>
    <spring-security.version>5.2.1.RELEASE</spring-security.version>
     .........
   </properties>

3、下面就帮我们引入了很多的依赖。这也就是Springboot可以让程序员添加很少的依赖运行项目  的原因。

  
 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc</artifactId>
        <version>2.2.1.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        <version>2.2.1.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-ldap</artifactId>
        <version>2.2.1.RELEASE</version>
      </dependency>
     <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
        <version>2.2.1.RELEASE</version>
      </dependency>
 ............

4、在众多的依赖中我们抽个奖,选择spring-boot-starter-json,就它了

点击spring-boot-starter-json。

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.2.1.RELEASE</version>
      <scope>compile</scope>
    </dependency>

5、来到了spring-boot-starters-2.2.1.RELEASE中

我们看到了一些依赖。这些依赖很形象的说明了,Maven的传递依赖这个特征。

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.1.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.10.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jdk8</artifactId>
      <version>2.10.0</version>
      <scope>compile</scope>
    </dependency>

以上就是对starter起步依赖的分析。


 

二、自动配置分析

自动配置其实就是将一些默认的设置,提前配置了。

1、@SpringBootApplication   按住Ctrl点击查看启动类MySpringBootApplication上的注解@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 。点击@SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

可以看到他的底层就是一个@Configuration 既标注该类是Spring的一个配置类。

 

2、@ComponentScan 组件扫描

我们在纯Spring 开发中一般都会配置@ComponentScan,但是在SpringBoot中就不需要了。

原因是,SpringBoot中@ComponentScan的扫描规则是,当前引导类,也就是被@SpringBootApplication所修饰的类,所在的包,及其子包中的所有的Bean,交给SpringIOC容器管理。

com.study和@SpringBootApplication是处于同一级包。所以com.study下的所有子类全都会被扫描到,加载进SpringIOC容器。

 

3、@EnableAutoConfiguration:SpringBoot自动配置功能开启

按住Ctrl点击查看注解@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 {};
}

在学习Spring时我们@Import可以导入其他配置类,当然在SpringBoot中它也可以用来导入其他配置类。按住Ctrl点击AutoConfigurationImportSelector.class。看到源码

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
    private static final String[] NO_IMPORTS = new String[0];
    private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
    private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    private ConfigurableListableBeanFactory beanFactory;
    private Environment environment;
    private ClassLoader beanClassLoader;
    private ResourceLoader resourceLoader;

    public AutoConfigurationImportSelector() {
    }

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

此类实现了很多的接口,看到selectImports( AnnotationMetadata annotationMetadata)方法中的this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata)方法。点击它就在此方法下。在getAutoConfigurationEntry(...)方法中我们看到getCandidateConfigurations(annotationMetadata, attributes) 获取候选配置这个方法的返回值是一个List集合,且是String类型的。经常挖源码的人会有经验的判断这是去找此类的全包名。点击getCandidateConfigurations(annotationMetadata, attributes);看到方法引用。

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

看到此句,我大体意思理解为SpringFactory工厂根据名字去加载一些Bean。

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.");

断言,当Bean工厂通过名字去加载一些配置Bean出现问题时,就会报在 META-INF/spring.factories中找不到自动配置类。如果使用自定义打包,请确保文件正确无误。(在META-INF/spring.factories中找不到自动配置类。如果使用自定义打包,请确保文件正确无误。)

那么去哪找呢,它说在META-INF/spring.factories,可是我们有很多的Jar包啊,他们中间都有META-INF/spring.factories这个文件夹,那么这里默认情况下是在当前所在类,所在包的META-INF/spring.factories在。

回到顶部类声明的地方,查看当前包路径。

点击此处查看所有的依赖。

找到了org.springframework.boot.autoconfigure包,也找到了spring.factories 。

点击spring.factories查看 发现是跟properties一样的配置文件。部分源码如下:

org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
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,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
.......................

上面配置文件存在大量的以Configuration为结尾的类名称,这些类就是存有自动配置信息的类,而SpringApplication在获取这些类名后再加载 。

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

@Configuration(
    proxyBeanMethods = false
)
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
    public ServletWebServerFactoryAutoConfiguration() {
    }
...................

其中@EnableConfigurationProperties({ServerProperties.class}) 代表加载ServerProperties服务器配置属性类

进入ServerProperties.class源码如下:

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties {
    private Integer port;
    private InetAddress address;
    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();
    private ServerProperties.ForwardHeadersStrategy forwardHeadersStrategy;
    private String serverHeader;
    private DataSize maxHttpHeaderSize;
    private Duration connectionTimeout;
    @NestedConfigurationProperty
    private Ssl ssl;
    @NestedConfigurationProperty
    private final Compression compression;
    @NestedConfigurationProperty
    private final Http2 http2;
..................

其中,我们发现有@ConfigurationProperties 将它声明为一个配置信息Properties。

它是通过前缀server.配置属性="需要配置的值" 例如server.port=8081,将端口改成8081的。

打开这个Json文件夹。

Ctrl+F 搜索 server.port

    {
      "name": "server.netty.connection-timeout",
      "type": "java.time.Duration",
      "description": "Connection timeout of the Netty channel.",
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties$Netty"
    },
    {
      "name": "server.port",
      "type": "java.lang.Integer",
      "description": "Server HTTP port.",
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
      "defaultValue": 8080
    },
    {
      "name": "server.server-header",
      "type": "java.lang.String",
      "description": "Value to use for the Server response header (if empty, no header is sent).",
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
    },

看到一些Porperties键值对,并且看到我们熟悉的8080.当我们不对SpringBoot的端口进行改变时,我们的默认端口就是8080,SpringBoot就是在这配置的。

好了。我们回过头来倒推回去看一下SpringBoot是怎么加载这些配置文件的。

1、Properties端口配置文件被ServerProperties.class读取。

2、ServerProperties.class被 ServletWebServerFactoryAutoConfiguration.class  @EnableConfigurationProperties({ServerProperties.class})自动导入。

3、ServletWebServerFactoryAutoConfiguration.class 被WebSocketServletAutoConfiguration.class自动导入@AutoConfigureBefore({ServletWebServerFactoryAutoConfiguration.class})

4、WebSocketServletAutoConfiguration.class由spring.factories配置文件配置。

org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\

5、spring.factories由 AutoConfigurationImportSelector.class源码中的getCandidateConfigurations(....)方法加载

6、AutoConfigurationImportSelector.class由 @interface EnableAutoConfiguration的@Import({AutoConfigurationImportSelector.class}),

引入。

7、 @interface EnableAutoConfiguration由@SpringBootApplication的@EnableAutoConfiguration自动配置所配置。

这是一个倒叙的顺序哦。

以上就是SpringBoot自动配置的部分源码分析。

 

三、自动配置实战。

编写Controller

@Controller
@RequestMapping("/Spring")
public class HelloController {

    @ResponseBody
    @RequestMapping("/hello")
    public String hello(){

        return "Hello SpringBoot";
    }
}

在刚刚挖源码的过程中,我们看到了properties中有很多的配置项。

打开POM文件 按住Ctrl 点击<artifactId>spring-boot-starter-parent</artifactId>看到

   <resources>
      <resource>
        <filtering>true</filtering>
        <directory>${basedir}/src/main/resources</directory>
        <includes>
          <include>**/application*.yml</include>
          <include>**/application*.yaml</include>
          <include>**/application*.properties</include>
        </includes>
      </resource>

*代表任意匹配。

在工程的Resources文件夹,右击New ——》File。我们选择使用.properties,名字一般习惯都是application。

摘抄.json源码文件中"name":"server.port"

在我们没配置前控制台输出的是

application.yaml配置 后

server.port=8081

输出

再加一个项目名字。

server.servlet.context-path=/springboot

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

真香号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值