Spring Boot自动装配原理

Spring Boot相对于Spring的一大改变或者优势来说就是”约定大于配置”的思想,不像Spring一样所有的配置都需要我们自己去实现,Spring Boot集成了许多默认的配置。拿Spring MVC来举例,原来Spring时代是通过写两个XML配置文件来实现的,一个web.xml,另一个applicationContext.xml。这些文件内容复杂,且大部分情况下不需要改变,在各个项目中的迁移也只是复制粘贴里面的代码而已,这无疑增加了使用成本。而在Spring Boot中,只需要引入相关spring-boot-starter-web依赖即可,其他的配置都不需要。即使有需要其他配置的地方,统一在application.properties配置文件中进行配置即可,该文件写法是类似于json的键值对的格式,不像XML格式那样的重量级。

那么既然不需要相关配置,Spring Boot是如何实现自动装配类的呢?如何在项目启动的时候将需要加载的类都注入到Spring的IoC容器中?本文将探究这个问题。

通常在Spring Boot的启动类上会加上@SpringBootApplication的注解,如下所示:

@SpringBootApplication

public class Application {

public static void main(String[] args) {

SpringApplication.run(Application.class, args);

其注解主要是由三个子注解构成的,分别是@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan:

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

//…

这样我们可以直接使用@SpringBootApplication注解而不再需要使用以上三个注解来标识启动类了。其中@EnableAutoConfiguration是开启自动装配的功能,该注解的代码如下所示:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import(AutoConfigurationImportSelector.class)

public @interface EnableAutoConfiguration {

//…

由上所示,@EnableAutoConfiguration注解需要引入AutoConfigurationImportSelector这个类或者其子类,如果没有找到,则自动装配失败。

而在上述启动时会调用SpringApplication的run方法。该方法会最终调用如下所示的重载run方法:

/**

  • Run the Spring application, creating and refreshing a new

  • {@link ApplicationContext}.

  • @return a running {@link ApplicationContext}

public ConfigurableApplicationContext run(String… args) {

StopWatch stopWatch = new StopWatch();

stopWatch.start();

ConfigurableApplicationContext context = null;

Collection

configureHeadlessProperty();

SpringApplicationRunListeners listeners = getRunListeners(args);

listeners.starting();

try {

ApplicationArguments applicationArguments = new DefaultApplicationArguments(

args);

ConfigurableEnvironment environment = prepareEnvironment(listeners,

applicationArguments);

configureIgnoreBeanInfo(environment);

Banner printedBanner = printBanner(environment);

context = createApplicationContext();

exceptionReporters = getSpringFactoriesInstances(

SpringBootExceptionReporter.class,

new Class[] { ConfigurableApplicationContext.class }, context);

prepareContext(context, environment, listeners, applicationArguments,

printedBanner);

refreshContext(context);

afterRefresh(context, applicationArguments);

stopWatch.stop();

if (this.logStartupInfo) {

new StartupInfoLogger(this.mainApplicationClass)

.logStarted(getApplicationLog(), stopWatch);

listeners.started(context);

callRunners(context, applicationArguments);

catch (Throwable ex) {

handleRunFailure(context, ex, exceptionReporters, listeners);

throw new IllegalStateException(ex);

try {

listeners.running(context);

catch (Throwable ex) {

handleRunFailure(context, ex, exceptionReporters, null);

throw new IllegalStateException(ex);

return context;

以上方法会创建/刷新ApplicationContext、初始化Environment、listeners等一系列操作。在第23行代码中,其会调用getSpringFactoriesInstances方法,同时会将SpringBootExceptionReporter.class这个参数传入进去:

private

Class[] parameterTypes, Object… args) {

ClassLoader classLoader = getClassLoader();

// Use names and ensure unique to protect against duplicates

Set

SpringFactoriesLoader.loadFactoryNames(type, classLoader));

List

classLoader, args, names);

AnnotationAwareOrderComparator.sort(instances);

return instances;

在该方法中会调用SpringFactoriesLoader的loadFactoryNames方法。SpringFactoriesLoader类的作用是利用工厂的加载机制来读取装配资源的类,其部分源码如下所示:

public final class SpringFactoriesLoader {

/**

  • The location to look for factories.

Can be present in multiple JAR files.

public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”;

//…

private static final Map

//…

/**

  • Load the fully qualified class names of factory implementations of the

  • given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given

  • class loader.

  • @param factoryClass the interface or abstract class representing the factory

  • @param classLoader the ClassLoader to use for loading resources; can be

  • {@code null} to use the default

  • @throws IllegalArgumentException if an error occurs while loading factory names

  • @see #loadFactories

public static List

String factoryClassName = factoryClass.getName();

return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());

private static Map

MultiValueMap

if (result != null) {

return result;

try {

Enumeration

classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :

ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

result = new LinkedMultiValueMap<>();

while (urls.hasMoreElements()) {

URL url = urls.nextElement();

UrlResource resource = new UrlResource(url);

Properties properties = PropertiesLoaderUtils.loadProperties(resource);

for (Map.Entry entry : properties.entrySet()) {

String factoryClassName = ((String) entry.getKey()).trim();

for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {

result.add(factoryClassName, factoryName.trim());

cache.put(classLoader, result);

return result;

catch (IOException ex) {

throw new IllegalArgumentException(“Unable to load factories from location [” +

FACTORIES_RESOURCE_LOCATION + “]”, ex);

//…

其中可以看到FACTORIES_RESOURCE_LOCATION这个常量,它就是读取配置资源的文件地址,也就是说会从spring.factories文件中读取所有需要注入的类出来,每一个jar包下基本上都会有一个spring.factories配置文件。而上面源码中第25行的loadFactoryNames方法会调用第30行的loadSpringFactories方法,该方法的作用是读取加载进来的所有jar包下的spring.factories配置文件中的内容,将其放到一个本地缓存cache中。缓存的意义在于第一次调用该方法时会读取spring.factories文件并将读取中的结果放到缓存中,之后再调用该方法时就不再读取文件,而直接返回缓存中的内容就行了。

在spring-boot-autoconfigure的jar包下META-INF目录中的spring.factories文件的部分内容如下:

Initializers

org.springframework.context.ApplicationContextInitializer=\

org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\

org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

Application Listeners

org.springframework.context.ApplicationListener=\

org.springframework.boot.autoconfigure.BackgroundPreinitializer

Auto Configuration Import Listeners

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\

org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

Auto Configuration Import Filters

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\

org.springframework.boot.autoconfigure.condition.OnBeanCondition,\

org.springframework.boot.autoconfigure.condition.OnClassCondition,\

org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

Auto Configure

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\

org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\

org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\

org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\

org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\

org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\

org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\

org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\

org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\

org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\

org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\

org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\

org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\

org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\

org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\

org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\

org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\

org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\

org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\

org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\

org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\

org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\

org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\

org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\

org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\

org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\

//…

由上可以看到,其文件格式由键值对组成,每一行末尾的反斜杠表示换行,其他jar包下spring.factories文件的内容也都会有所不同。

而在上面说过的SpringFactoriesLoader类的loadFactoryNames方法中会通过ClassLoader来读取spring.factories文件中的内容。通过run方法中传入的SpringBootExceptionReporter.class这个参数,找到文件中键是SpringBootExceptionReporter所对应的值的集合,通过反射机制来实例化相关的类再返回即可。如前面所说,这里是第一次调用SpringFactoriesLoader类的工厂加载机制的地方,后续再调用的话会直接走缓存中的内容。

再回到最开始SpringApplication类中的run方法中的代码中,之前说的都是基于其中的getSpringFactoriesInstances方法的延展,在该方法执行完毕后,在第28行会调用refreshContext方法,在其中会调用AutoConfigurationImportSelector类的getAutoConfigurationEntry方法,该方法就是自动装配类的方法入口,而AutoConfigurationImportSelector类前面也说过,是@EnableAutoConfiguration这个注解必须要引入的类。在getAutoConfigurationEntry方法中会调用getCandidateConfigurations方法,这两个方法的源码如下:

/**

  • Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}

  • of the importing {@link Configuration @Configuration} class.

  • @param autoConfigurationMetadata the auto-configuration metadata

  • @param annotationMetadata the annotation metadata of the configuration class

  • @return the auto-configurations that should be imported

protected AutoConfigurationEntry getAutoConfigurationEntry(

AutoConfigurationMetadata autoConfigurationMetadata,

AnnotationMetadata annotationMetadata) {

if (!isEnabled(annotationMetadata)) {

return EMPTY_ENTRY;

AnnotationAttributes attributes = getAttributes(annotationMetadata);

List

attributes);

configurations = removeDuplicates(configurations);

Set

checkExcludedClasses(configurations, exclusions);

configurations.removeAll(exclusions);

configurations = filter(configurations, autoConfigurationMetadata);

fireAutoConfigurationImportEvents(configurations, exclusions);

return new AutoConfigurationEntry(configurations, exclusions);

/**

  • Return the auto-configuration class names that should be considered. By default

  • this method will load candidates using {@link SpringFactoriesLoader} with

  • {@link #getSpringFactoriesLoaderFactoryClass()}.

  • @param metadata the source metadata

  • @param attributes the {@link #getAttributes(AnnotationMetadata) annotation

  • attributes}

  • @return a list of candidate configurations

protected List

AnnotationAttributes attributes) {

List

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;

/**

  • Return the class used by {@link SpringFactoriesLoader} to load configuration

  • candidates.

  • @return the factory class

protected Class getSpringFactoriesLoaderFactoryClass() {

return EnableAutoConfiguration.class;

getCandidateConfigurations方法中也会像上面说过的getSpringFactoriesInstances方法一样,调用SpringFactoriesLoader的loadFactoryNames方法来获取配置文件中的内容,只不过这次传入的参数是getSpringFactoriesLoaderFactoryClass(),即EnableAutoConfiguration.class。也就是说会将需要自动装配的类都拿到,通过EnableAutoConfiguration所对应的值的类名的集合。但这次并不会读取spring.factories配置文件,而是会从缓存中读取,因为之前已经调用过loadSpringFactories方法了。读取出的内容依然会使用反射来实例化,将实例化的结果返回回去并最终完成自动装配的全过程。这些自动装配的类会通过一些@ConditionalOnClass、@ConditionalOnMissingClass、@Import之类的注解来加载它们需要的资源。

以上就是Spring Boot完成自动装配的大致核心流程,总结起来就是利用了SpringFactoriesLoader这个类来实现的工厂加载机制,读取jar包下的META-INF目录下的spring.factories配置文件中的内容,然后将需要的类名反射实例化即可。

需要自动装配的类在以前的Spring时代都是需要我们自己写的,但现在Spring Boot的自动装配机制帮我们实现了,只需要引入相关的依赖即可。例如Redis的依赖就是spring-boot-starter-data-redis,引用它就可以了,同时在application.properties配置文件中做些简单的配置即可,就可以直接用起来了,不再需要自己写连接客户端、连接池之类的代码。在上面的spring.factories配置文件的示例中也出现了像RedisAutoConfiguration这样的Redis的自动配置类。

Original: https://www.cnblogs.com/zhuxiaopijingjing/p/12979071.html
Author: 幽暗森林之猪大屁
Title: Spring Boot自动装配原理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/540575/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Monitor(管程/监视器)详解

    说明 Monitor,直译为”监视器”,而操作系统领域一般翻译为”管程”。管程是指管理共享变量以及对共享变量操作的过程,让它们支持并…

    Java 2023年6月16日
    077
  • 27.多线程,分组服务端,1万客户端连接,每秒处理200万包

    问题:网络抖动,不能稳定在每秒钟,200万个包。波动比较大,80万—-300万之间波动。 服务端: CELLTimestamp.hpp DataHeader.hpp E…

    Java 2023年5月29日
    080
  • 线程安全,这词你懂了吗?

    前言 还记得你第一次遇到「 线程安全」这个词的时候吗? 我第一次遇到线程安全这个词是在学习多线程并发操作的时候,看到人家文章里出现这个词,还有说各种线程安全的类,但是一开始并不理解…

    Java 2023年6月10日
    081
  • SpringCloud Alibaba Nacos 服务治理中心

    一、什么是Nacos? 二、Nacos能干吗? 三、Nacos关键特性 四、Nacos中的基本概念 五、如何安装部署Nacos? 六、Nacos数据持久化 一、什么是Nacos? …

    Java 2023年6月5日
    083
  • 01第一章:【1】_MQTT简介

    一、前言 物联网是新一代信息技术的重要组成部分,也是”信息化”时代的重要发展阶段。其英文名称是:”Internet of things(IoT)…

    Java 2023年5月29日
    084
  • mybatis 3.2.3 maven dependency pom.xml 配置

    Original: https://www.cnblogs.com/lihaozy/p/3396008.htmlAuthor: kkmmTitle: mybatis 3.2.3 m…

    Java 2023年5月30日
    071
  • nacos配置中心文件(bootstrap.properties)不生效问题解决

    springcloud整合nacos作为配置中心时,配置文件不生效的问题在这个问题处卡了一天多,在网上各种搜索。大多数解决方案都是在bootstrap.properties文件中配…

    Java 2023年6月7日
    097
  • Spring Ioc源码分析系列–Ioc源码入口分析

    Spring Ioc源码分析系列–Ioc源码入口分析 本系列文章代码基于Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源码分析系列&…

    Java 2023年6月8日
    074
  • BaseServlet抽取以及UserServlet和页面路径改写

    BaseServlet抽取 优化Servlet 减少Servlet的数量,现在是一个功能一个Servlet,将其优化为一个模块一个Servlet, 相当于在数据库中一张表对应一个S…

    Java 2023年6月6日
    0106
  • 【微服务】- 服务调用-OpenFeign

    服务调用 – OpenFeign 😄生命不息,写作不止🔥 继续踏上学习之路,学之分享笔记👊 总有一天我也能像各位大佬一样🏆 一个有梦有戏的人 @怒放吧德德🌝分享学习心得…

    Java 2023年6月16日
    082
  • Spring5.0源码学习系列之Spring AOP简述

    前言介绍 附录:Spring源码学习专栏 在前面章节的学习中,我们对Spring框架的IOC实现源码有了一定的了解,接着本文继续学习Springframework一个核心的技术点A…

    Java 2023年5月30日
    0131
  • SpringMvc异常处理

    异常处理器 编写异常处理器 @RestControllerAdvice //&#x7528;&#x4E8E;&#x6807;&#x8BC6;&amp…

    Java 2023年6月7日
    093
  • Docker 常用操作

    .Docker的基本操作 1.镜像操作 1.1.镜像名称 首先来看下镜像的名称组成: 镜名称一般分两部分组成:[repository]:[tag]。 在没有指定tag时,默认是la…

    Java 2023年6月7日
    095
  • SpringCloud(二).Eureka注册服务中心与服务调用

    Eureka(服务注册中心),主要包括对服务功能的注册、调用、熔断、降级、负载等。 有了服务中心项目的关系有哪些变化呢,用几张图来解释一下(暂缺,后续找到好的画图软件补上): 这样…

    Java 2023年6月7日
    095
  • springboot整合activity

    地址:https://blog.csdn.net/apm800/article/details/106112994 此博客只是为了记忆相关知识点,大部分为网络上的文章,在此向各个文…

    Java 2023年5月30日
    082
  • Java全栈系列笔记

    Java全栈系列笔记 全部文档、项目、源码: github:https://github.com/name365/Blog-Java 码云:https://gitee.com/ya…

    Java 2023年5月29日
    085
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球