Spring 源码解析16——SpringBoo体系原理简介

  Spring-Boot 特点如下:
①、创建独立的 Spring 应用程序;
②、嵌入的 Tomcat,无须部署 .war 文件;
③、简化 Maven 配置;
④、自动配置 Spring;
⑤、提供生产就绪型功能,如指标、健康检查和外部配置;绝对没有代码生成,以及对XMIL没有配置要求。

1、启动一个简单的Spring-Boot的web项目(Maven构建的工程)

  • 配置maven的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>

    <groupId>com.xxx</groupId>
    <artifactId>SpringBoot_quick</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--所有的springboot工程都需要继承这个父工程-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
    </parent>

    <dependencies>
        <!--web起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--热部署配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <!--@ConfigurationProperties执行器配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
  • 新增一个controller层的控制类QuickStartController.class
package com.xxx.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class QuickStartController {

    @ResponseBody
    @RequestMapping("/quick")
    public String quick(){
        return "springboot 访问成功...^^..^_^.hello...00.0.0";
    }
}
  • 新增一个Spring-Boot的启动引导类MySpringBootApplication.class
package com.xxx;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

//声明该类是一个SpringBoot引导类
@SpringBootApplication
public class MySpringBootApplication {
    public static void main(String[] args) {
        //run方法,表示SpringBoot引导类,run参数就是SpringBoot引导类的字节码对象
        SpringApplication.run(MySpringBootApplication.class);
    }
}
  • 在/resources目录下添加application.properties文件和application.yaml文件,这2个文件可以为空

  • 测试,启动Spring-Boot的启动引导类MySpringBootApplication.class,并在浏览器中访问http://127.0.0.1:8080/quick ,结果如下
    image

2、第一个jar包并加载到Spring-Boot的starter之中(Maven构建的工程)

  Spring-Boot之所以流行,是因为spring starter模式的提出。spring starter 的出现,可以让模块开发更加独立化,相互间依赖更加松散以及可以更加方便地集成。我们可以自己构建一个jar包,并把该jar包加载到标题1、启动一个简单的Spring-Boot的web项目(Maven构建的工程)的工程中

  • 新建一个maven工程,并配置maven的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>

    <groupId>com.springstudy</groupId>
    <artifactId>study-client-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.7.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>
  • 新增一个接口HelloService.interface,可以认为它是独立业务开发模块中对外暴漏的可以直接调用的接口
package com.spring.study.moudle;
public interface HelloService {
    public String sayHello();
}

  • 新增HelloService.interface的实现类HelloServiceImpl
package com.spring.study.moudle;
import org.springframework.stereotype.Component;
@Component
public class HelloServiceImpl implements HelloService{

    public String sayHello() {
        return "hello world";
    }
}

  以上实现为了尽量屏蔽 Spring Boot 基础理论以外的东西,把演示设计得尽量简单,如果是真实的业务,这个接口以及接口实现可能会非常复杂,甚至还会间接依赖于非常多的其他的bean。它基本上就是一个独立的业务模块,当然这个模块并不是自己部署,而是运行在依赖它的主函数中。如果我们开发到这种程度,想要主函数感知的话也不是不可以,但是至少要让主工程知道当前业务的 bean路径并加入scan 列表中,否则在 Spring 启动的过程中没有办法把client 中所有的 bean 载入 Spring 容器,逻辑也就没法生效了,但是,随着业务的增长,模块也会越来越多、越来越分散,大量的配置在主函数中维护,这会造成主函数非常臃肿及冲突严重,而且根据职责划分原则,以上的例子中主模块只关心自己是否使用外部依赖的模块以及对应的接口就好了,再去让主模块感知对应的路径等细节信息显然是不合适的。于是乎,在 Spring Boot出来之前我们会尝试把 Scan 等配置项写人XML里面,然后让主函数直接引用配置项,这样,主函数知道的事情就进一步减少了,但是还有没有更好的解决方式呢,或者,还有没有更好的办法能让主函数做更少的事情呢?Spring Boot做到了这一点,继续追加代码,添加自动配置项:

package com.spring.study;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.spring.study.moudle"})
public class HelloServiceAutoConfiguration {

}

  • 在/resources/META-INF目录下新增一个spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.spring.study.HelloServiceAutoConfiguration

上述所有文件的目录结构如下:
image

  • 利用mvn install命令将该项目打包成一个jar包,放到maven仓库中,如下所示
    image

  • 修改标题1、启动一个简单的Spring-Boot的web项目(Maven构建的工程)中maven的pom.xml文件,添加如下依赖

<!--其余依赖(<dependency>标签)不变,只在<dependencies></dependencies>标签中加入下面的依赖(<dependency>标签)-->
<dependency>
    <groupId>com.springstudy</groupId>
    <artifactId>study-client-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
  • 修改一个controller层的控制类TestController.class,基本和标题1、启动一个简单的Spring-Boot的web项目(Maven构建的工程)中controller层的控制类QuickStartController.class相同,只是在成员变量中注入了HelloService.interface类型的实例,实际上就是HelloServiceImpl.class类型的实例
package com.xxx.controller;
import com.spring.study.moudle.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestController {
    @Autowired
    private HelloService helloService;

    @ResponseBody
    @RequestMapping("/")
    String home() {
        return helloService.sayHello();
    }
}
  • 测试,启动Spring-Boot的启动引导类MySpringBootApplication.class,并在浏览器中访问http://127.0.0.1:8080 ,结果如下
    image
      这给模块开发带来了非常大的方便,同时也为后续的模块拆分提供了便利,因为当业务逐渐复杂的时候我们会引入大量的中间件,而这些中间件的配置、依赖、以及初始化是非常麻烦的,现在有了Starter 模式,它帮我们做到了只关注于逻辑本身。

3、探索SpringApplication启动Spring

  我们找到主函数人口 MySpringBootApplication,发现这个入口的启动还是比较奇怪的,这也是 Spring-Boot启动的必要做法,那么,这也可以作为我们分析 Spring-Boot 的入口:

package com.xxx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//声明该类是一个SpringBoot引导类
@SpringBootApplication
public class MySpringBootApplication {
    public static void main(String[] args) {
        //run方法,表示SpringBoot引导类,run参数就是SpringBoot引导类的字节码对象
        SpringApplication.run(MySpringBootApplication.class);
    }
}

继续查看SpringApplication.class::run()函数
SpringApplication.class::run()

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

其中就最为核心的就是 ConfigurableApplicationContext 的创建、初始化、刷新等。那么我们可以直接进入查看其中的逻辑,同时,Spring作为一个全球都在使用的框架,会有非常多的需要考虑的问题,我们在阅读源码的过程中只需要关系核心的主流程,了解其工作原理,并在阅读的过程中感受它的代码风格以及设计理念就好了,如果真的追求理解每一行代码真的是非常耗时的一件事情,毕竟我们阅读源码的目的大多数是成长而不是真的要去维护Spring。
如果要详细了解ConfigurableApplicationContext 的创建、初始化、刷新等,请查看我的另一篇博客:Spring源码解析5——bean的加载(一)

3.1、创建ConfigurableApplicationContext

SpringApplication.class::createApplicationContext()

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }

  这个函数似乎没有什么特别之处,无非就是实例化一个ApplicationContext,因为ApplicationContext是Spring 存在的基础。

3.2、bean的加载

  继续返回追踪prepareContext()函数
SpringApplication.class::prepareContext()
SpringApplication.class::load()

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        this.postProcessApplicationContext(context);
        this.applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }

        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }

        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        this.load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

    protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }

        BeanDefinitionLoader loader = this.createBeanDefinitionLoader(this.getBeanDefinitionRegistry(context), sources);
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }

        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }

        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }

        loader.load();
    }
3.3、spring扩展属性的加载

SpringApplication.class::refreshContext()
SpringApplication.class::refresh()

    private void refreshContext(ConfigurableApplicationContext context) {
        this.refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException var3) {
                ;
            }
        }

    }

    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext)applicationContext).refresh();
    }
3.4、总结

  分析下来,Spring-Boot的启动并不是我们想象的那么神秘,按照约定大于配置的原则,内置了 Spring 原有的启动类,并在启动的时候启动及刷新,仅此而已。

4、Starter自动化配置原理

  我们已经知道了 Spring-Boot 如何启动 Spring,但是目前为止我们并没有揭开 Spring-Boot的面纱,究竟 Starter是如何生效的呢?这些逻辑现在看来只能体现在注解@SpringBootApplication本身了。
@interface SpringBootApplication.interface

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.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@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 {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

这其中我们更关注 SpringBootApplication上的注解内容,因为注解具有传递性,@EnableAutoConfiguration 是个非常特别的注解,它是 Spring-Boot 的全局开关,如果把这个注解去掉,则一切 Starter 都会失效,这就是约定大于配置的潜规则,那么,Spring-Boot 的核心很可能就藏在这个注解里面:
@interface EnableAutoConfiguration.interface

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.context.annotation.Import;

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

AutoConfigurationImportSelector.class 就是 Starter 自动化导人的关键类

4.1、spring.factories的加载

  AutoConfigurationImportSelector.class的UML图,如下所示:
image

AutoConfigurationImportSelector.class::selectImports()
AutoConfigurationImportSelector.class::getAutoConfigurationEntry()
AutoConfigurationImportSelector.class::isEnabled()
AutoConfigurationImportSelector.class::getCandidateConfigurations()

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

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

    protected boolean isEnabled(AnnotationMetadata metadata) {
        return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
    }

从上面的4个函数中我们看到了 META-INF/spring.factories,在我们之前演示的环节,按照约定大于配置的原则,Starter 如果要生效则必须要在/resources/META-INF 文件下下建立 spring.factories 文件,并把相关的配置类声明在里面,虽然这仅仅是一个报错异常提示,但是其实我们已经可以推断出来这一定就是这个逻辑的处理之处,继续进人SpringFactoriesLoader类:
SpringFactoriesLoader.class::loadFactoryNames()
SpringFactoriesLoader.class::loadSpringFactories()

    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

至此,我们终于明白了为什么 Starter 的生效必须要依赖于配置 META-INF/spring.factories文件,因为在启动过程中有一个硬编码的逻辑就是会扫描各个包中的对应文件,并把配置捞取出来。

5、Conditional机制实现

5.1、Conditional使用

  Spring 提供了一个更通用的基于条件的 bean 的创建--使用@Conditional 注解。@Conditional根据满足的某一个特定条件创建一个特定的bean。比方说,当某一个 JAR 包在一个类路径下的时候,会自动配置一个或多个 bean;或者只有某个 bean 被创建后才会创建另外一个 bean。总的来说,就是根据特定条件来控制bean的创建行为,这样我们可以利用这个特性进行一些自动的配置。当然,@Conditional 注解有非常多的使用方式,我们仅仅通过 @ConditionalOnProperty注解来深人探讨它的运行机制。如下所示:

  • 更改标题2、第一个jar包并加载到Spring-Boot的starter之中(Maven构建的工程)中的HelloServiceAutoConfiguration.class,更改后如下(新增了onalOnProperty(prefix = "study",name = "enable",havingValue = "true"))
package com.spring.study;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.spring.study.moudle"})
@ConditionalOnProperty(prefix = "study",name = "enable",havingValue = "true")
public class HelloServiceAutoConfiguration {

}

上面的声明想要表达的逻辑是如果配置属性中显示的声明 study.enable=true(配置在application.yaml文件中)生效,我们可以进行验证。

  • 当没有在application.yaml文件中配置study.enable = true时,启动Spring-Boot的启动引导类MySpringBootApplication.class,会报如下错误
    image

  • 当在application.yaml文件中配置study.enable = true时,可以正常启动Spring-Boot的启动引导类MySpringBootApplication.class,如下:
    image

5.2、Conditional的原理

  @ConditionalOnProperty注解的调用都出现在OnPropertyCondition.class::getMatchOutcome()函数中
OnPropertyCondition.class::getMatchOutcome()

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
                metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
        List<ConditionMessage> noMatch = new ArrayList<>();
        List<ConditionMessage> match = new ArrayList<>();
        for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
            ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
            (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
        }
        if (!noMatch.isEmpty()) {
            return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
        }
        return ConditionOutcome.match(ConditionMessage.of(match));
    }

按照通常的设计,这里应该返回是否匹配的boolean值,但是现在却返回ConditionOutcome这样一个对象,这是什么道理呢?我们看一下这个类的结构:

public class ConditionOutcome {

    private final boolean match;

    private final ConditionMessage message;
    
    ...省略...
}

这里面除了大量的方法外有一个比较重要的属性字段 —— boolean match 字段,继续看

ConditionOutcome.noMatch(ConditionMessage.of(noMatch));

对应的ConditionOutcome.class中的代码为
ConditionOutcome.class::noMatch()

    public static ConditionOutcome noMatch(ConditionMessage message) {
        return new ConditionOutcome(false, message);
    }

以及

ConditionOutcome.match(ConditionMessage.of(match));

对应的ConditionOutcome.class中的代码为
ConditionOutcome.class::match()

    public static ConditionOutcome match(ConditionMessage message) {
        return new ConditionOutcome(true, message);
    }

差别仅仅是这个属性的初始化值,那么根据这个信息可以断定,getMatchOutcome() 函数中List noMatch 这个变量的逻辑一定是整个逻辑的核心。
继续分析 OnPropertyCondition.class::getMatchOutcome() 函数中的逻辑:

        List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
                metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));

这句代码是要扫描出 @ConditionalOnProperty的注解信息,例如我们刚才配置的

@ConditionalOnProperty(prefix = "study",name = "enable",havingValue = "true")

  继续看OnPropertyCondition.class::determineOutcome()函数的逻辑
OnPropertyCondition.class::determineOutcome()

    private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
        Spec spec = new Spec(annotationAttributes);
        List<String> missingProperties = new ArrayList<>();
        List<String> nonMatchingProperties = new ArrayList<>();
        spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
        if (!missingProperties.isEmpty()) {
            return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
                    .didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
        }
        if (!nonMatchingProperties.isEmpty()) {
            return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
                    .found("different value in property", "different value in properties")
                    .items(Style.QUOTE, nonMatchingProperties));
        }
        return ConditionOutcome
                .match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
    }

这个逻辑表明,不匹配有2种情况:
①、List missingProperties 对应属性缺失的情况;
②、List nonMatchingProperties对应不匹配的情况;
而这两个属性的初始化都在 spec.collectProperties(resolver, missingProperties, nonMatchingProperties)函数中,于是进人这个函数:
OnPropertyCondition.class::Spec.class::collectProperties()

        private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
            for (String name : this.names) {
                String key = this.prefix + name;
                if (resolver.containsProperty(key)) {
                    if (!isMatch(resolver.getProperty(key), this.havingValue)) {
                        nonMatching.add(name);
                    }
                }
                else {
                    if (!this.matchIfMissing) {
                        missing.add(name);
                    }
                }
            }
        }

终于,我们找到了对应的逻辑,这个函数尝试使用PropertyResolver 对象来验证对应的属性是否存在,如果不存在则验证不通过,因为PropertyResolver中包含了所有的配置属性信息。而PropertyResolver的初始化以及相关属性的加载我们会在下面详细介绍。

5.3、调用切入点

那么现在的问题是,OnPropertyCondition.class::getMatchOutcome() 方法是谁去调用的呢?或者说这个类是如何与 Spring整合在一起的呢?它又是怎么样影响 bean 的加载逻辑的呢?我们再从全局的角度来梳理下 Conditional 的实现逻辑。读者可以继续看图14-8中的bean 的 parse 解析链路,在 3.2.1processConfigurationClass()函数的步骤中主要逻辑是要对即将解析的注解做预处理,如下图所示。
image
ConfigurationClassParser.class::processConfigurationClass()

    private final ConditionEvaluator conditionEvaluator;
    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            ConfigurationClass existingClass = (ConfigurationClass)this.configurationClasses.get(configClass);
            if (existingClass != null) {
                if (configClass.isImported()) {
                    if (existingClass.isImported()) {
                        existingClass.mergeImportedBy(configClass);
                    }

                    return;
                }

                this.configurationClasses.remove(configClass);
                this.knownSuperclasses.values().removeIf(configClass::equals);
            }

            ConfigurationClassParser.SourceClass sourceClass = this.asSourceClass(configClass);

            do {
                sourceClass = this.doProcessConfigurationClass(configClass, sourceClass);
            } while(sourceClass != null);

            this.configurationClasses.put(configClass, configClass);
        }
    }

代码的第一行就是整个 Conditional 逻辑生效的切入点,如果验证不通过则会直接忽略掉后面的解析逻辑,那么这个类的属性以及 @ComponentScan 之类的配置也自然不会得到解析了。这个方法会拉取所有的 condition属性,onConditionProperty就是这里拉取的:
ConditionEvaluator.class::shouldSkip()

    public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
        if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
            return false;
        }

        if (phase == null) {
            if (metadata instanceof AnnotationMetadata &&
                    ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
                return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
            }
            return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
        }

        List<Condition> conditions = new ArrayList<>();
        for (String[] conditionClasses : getConditionClasses(metadata)) {
            for (String conditionClass : conditionClasses) {
                Condition condition = getCondition(conditionClass, this.context.getClassLoader());
                conditions.add(condition);
            }
        }

        AnnotationAwareOrderComparator.sort(conditions);

        for (Condition condition : conditions) {
            ConfigurationPhase requiredPhase = null;
            if (condition instanceof ConfigurationCondition) {
                requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
            }
            if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
                return true;
            }
        }

        return false;
    }
    
    @SuppressWarnings("unchecked")
    private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
        MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true);
        Object values = (attributes != null ? attributes.get("value") : null);
        return (List<String[]>) (values != null ? values : Collections.emptyList());
    }

这段方法里面有2个关键的地方。
①、condition的获取。通过代码 getConditionClasses()函数调用,因为代码走到这里已经是对某一个特定类的解析,AnnotatedTypeMetadata metadata 中包含了完整的配置类信息,只要通过 metadata.getAllAnnotationAttributes(Conditional.class.getName(), true)即可获取,所以这一步的逻辑并不复杂。
②、condition的运行匹配。通过代码 condition.matches(this.context, metadata)调用,因为我们的配置为@ConditionalOnProperty(prefix = "study",name = "enable",havingValue = "true")所以此时 condition 对应的运行态类为 OnPropertyCondition对象,这就跟前面的内容结合起来了。

6、属性自动化配置实现

6.1、属性自动化配置使用
  • 更改标题2、第一个jar包并加载到Spring-Boot的starter之中(Maven构建的工程)中的HelloServiceAutoConfiguration.class,更改后如下(新增了String valueInjection变量和getter和setter函数,并通过@Value注解注入该变量的值),这里要注意,每次更改study-client-starter项目时,需要清除本地maven仓库中的jar包,在执行mvn install命令将该项目打包成一个jar包,放到maven仓库中。
package com.spring.study.moudle;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class HelloServiceImpl implements HelloService{
    @Value("${study.valueInjection}")
    private String valueInjection;

    public String sayHello() {
        return "hello world";
    }

    public String getValueInjection() {
        return valueInjection;
    }

    public void setValueInjection(String valueInjection) {
        this.valueInjection = valueInjection;
    }
}

  • 在application.yaml文件中添加如下配置study.valueInjection = I am programmer
    image

  • 启动Spring-Boot的启动引导类MySpringBootApplication.class,并在浏览器中访问http://127.0.0.1:8080 ,结果如下
    image

7、Tomcat启动

  在上文中讲ConfigurableApplicationContext创建的时候,有这样一段代码
SpringApplication.class::createApplicationContext()

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }

其中基于Servlet创建的Spring的web项目的上下文配置是AnnotationConfigServletWebServerApplicationContext.class

    public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
            + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

这也是web项目的上下文扩展的关键,再次来回顾一下AbstractApplicationContext.class(具体内容请看我的另一篇博客Spring源码解析7——容器功能扩展):
AbstractApplicationContext.class::refresh()

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      //准备刷新上下文环境
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      //初始化BeanFactory,并进行Xml文件读取
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      //对BeanFactory进行各种功能的填充
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         //子类覆盖方法做额外的处理
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         //激活各种BeanFactory处理器
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         //注册拦截Bean创建的Bean处理器,这里只是注册,真正的调用是在getBean的时候
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         //国际化处理(为上下文初始化Message源)
         initMessageSource();

         // Initialize event multicaster for this context.
         //初始化应用消息广播器,并放入"applicationEventMulticaster"bean中
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         //留给子类来初始化其他的Bean
         onRefresh();

         // Check for listener beans and register them.
         //在所有注册的Bean中查找Listener Bean,注册到消息广播器中
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         //初始化剩下的单实例(非惰性)
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         //完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

AnnotationConfigServletWebServerApplicationContext.class正是扩展了AbstractApplicationContext.class,AnnotationConfigServletWebServerApplicationContext.class的UML关系图,如下:
image

posted @ 2026-01-18 22:35  Carey_ccl  阅读(12)  评论(0)    收藏  举报