SpringBoot自动装配源码解析

Spring Boot 自动装配原理

  • 使用Spring Boot最方便的一点体验在于我们可以几零配置的搭建一个Spring Web项目,那么他是怎么做到不通过配置来对Bean完成注入的呢。这就要归功于Spring Boot的自动装配实现,他也是Spring Boot中各个Starter的实现基础,Spring Boot的核心。
  • 自动装配,就是Spring Boot会自动的寻找Bean并且装配到IOC容器中,如下,我们通过一个Spring Boot项目说明,案例如下:
  • 添加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.ljm</groupId><artifactId>spring-cloud-alibaba-learn</artifactId><version>1.0-SNAPSHOT</version><properties><java.version>8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--Spring Boot autoConfig start--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.2.2.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.2.2.RELEASE</version></dependency></dependencies><build><plugins><plugin><!-- 指定 JDK 版本 --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><skip>true</skip><source>${java.version}</source><target>${java.version}</target><compilerVersion>${java.version}</compilerVersion><encoding>${project.build.sourceEncoding}</encoding></configuration></plugin></plugins></build>
</project>
  • application.properties
spring.redis.host=localhost
spring.redis.port=6379
  • HelloController.java
package com.springcloud.mystart;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author liaojiamin* @Date:Created in 10:50 2022/3/7*/
@RestController
public class HelloController {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@GetMapping("helloRedis")public String helloRedis(){redisTemplate.opsForValue().set("helloRedis", "RedisTemplateAutoConfig");return "helloRedis";}
}
  • SpringBoot启动类 Application.java
package com.springcloud;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author liaojiamin* @Date:Created in 11:08 2022/3/7*/
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
  • Redis操作界面结果

在这里插入图片描述

  • 如上案例中,我们并没有通过XML文件的形式注入Redis Template到IOC容器中,但是HelloController中却可以直接用@Autowired注入Redis Template实例,说明,IOC容器中已经存在了Redis Template,这个是Spring Boot自动装配实现的自动加载机制。
  • 在针对Redis的配置以及jar来说,我们只添加了一个Start依赖,就完成了依赖组件相关的Bean自动注入,

自实现自动装配标签

  • 自动装配在Spring Boot中通过@EnableAutoConfiguration 注解来开启的,这个注解我们没有在项目中显示的声明,他是包含在@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 {......}
  • 在理解EnableAutoConfiguration之前,我们能想到之前在用SpringMvc的时候用到过一个@Enable注解,他的主要作用就是吧相关的组件Bean装配到IOC容器中。@Enable注解对JavaConfig的进一步的优化,目的是为了减少配置,其实Spring从3.X开始就一直在做优化,减少配置,降低上手的难度,比如常见的有@EnableWebMvc,@EnableScheduling,如下代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
  • 如果我们通过JavaConfig的方式来注入Bean的时候,离不来@Configuration和@Bean,@Enable就是对这两个注解的封装,比如在上面的@EnableWebMvc注解代码中我们能看到@Import,Spring会解析@Import倒入的配置类,通过对这个配置类的实现来找到需要装配的Bean。
EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";/*** Exclude specific auto-configuration classes such that they will never be applied.* @return the classes to exclude*/Class<?>[] exclude() default {};/*** Exclude specific auto-configuration class names such that they will never be* applied.* @return the class names to exclude* @since 1.3.0*/String[] excludeName() default {};}
  • 以上是@EnableAutoConfiguration的注解,在除了@Import之外还有一个@AutoConfigurationPackage

注解,作用在于用了该注解的类所在的包以及子包下所有的组件扫描到Spring IOC容器中

  • @Import注解倒入类一个Configuration结尾的配置类,和上面不同,AutoConfigurationImportSelector类就是本注解的特殊地方
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {......}
  • AutoConfigurationImportSelect 实现了DeferredImportSelector,他是ImportSelector的子类,其中有一个selectImports方法返回类一个String数组,这个数组中就是我们需要制定装配到IOC容器中的所有类
  • 也就是说他在ImportSelector 的实现类之后,将实现类中返回的class名称都装配进IOC容器里
  • 与@Configuration不同的是,ImportSelector是批量的,并且还可以通过逻辑处理来选择对于的Bean,那么我们用一个Demo来验证
  • 创建两个类:

/*** @author liaojiamin* @Date:Created in 11:49 2022/3/7*/
public class FilterFirstObj {
}
/*** @author liaojiamin* @Date:Created in 11:49 2022/3/7*/
public class FilterSecondObj {
}
  • 创建一个ImportSelect的实现类,直接返回我们新建的类的名字,如下:
/*** @author liaojiamin* @Date:Created in 11:49 2022/3/7*/
public class GpImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {return new String[]{FilterFirstObj.class.getName(), FilterSecondObj.class.getName()};}
}
  • 定义我们自己的注解,这个可以直接抄@EnableAutoConfiguration
/*** @author liaojiamin* @Date:Created in 11:52 2022/3/7*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({GpImportSelector.class})
public @interface EnableAutoImport {
}
  • 在之前Demo的启动类中从IOC容器中获取我们定义的类:
/*** @author liaojiamin* @Date:Created in 11:08 2022/3/7*/
@SpringBootApplication
@EnableAutoImport
public class Application {public static void main(String[] args) {ConfigurableApplicationContext ca = SpringApplication.run(Application.class, args);System.out.println(ca.getBean(FilterFirstObj.class));}
}

在这里插入图片描述

  • 以上的实现方式相比@Import(*Configuration.class)来说,好处在于灵活性更高,还可以实现批量的注入,我们还有在以上的Demo中GpImportSelector添加N个,由于一个Configuration表示某一个技术组件中的一批Bean,所以自动装配的过程只需要扫描置顶路径对于的配置类即可。

自动装配源码分析

  • 基于以上Demo的实现,我们找到AutoConfigurationImportSelector的实现,找到其中的selectImports方法,他是ImportSelector接口的实现,如下:
@Overridepublic 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());}
  • 如上源码中有两个功能:

    • 从META-INF/spring-autoconfigure-metadata.properties中加载自动装配的条件元数据
    • 手机所有符合条件的配置类,autoConfigurationEntry.getConfigurations(),完成自动装配
  • 在AutoConfigurationImportSelector 类中并不直接执行对呀selectImport方法,其中有一个process方法,这个方法最中还是会调用getAutoConfigurationEntry方法获取自动装配的所有配置类,我们可以看如下源码

  • process方法

@Overridepublic void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,() -> String.format("Only %s implementations are supported, got %s",AutoConfigurationImportSelector.class.getSimpleName(),deferredImportSelector.getClass().getName()));AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);this.autoConfigurationEntries.add(autoConfigurationEntry);for (String importClassName : autoConfigurationEntry.getConfigurations()) {this.entries.putIfAbsent(importClassName, annotationMetadata);}}
  • getAutoConfigurationEntry方法
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);}
  • 通过Debug代码可以看每个步骤得到的返回值,简单分析代码的作用

    • getAttributes 获取@EnableAutoCOnfiguration注解中的属性exclude, excludeName
    • getCandidateConfigurations获得所有自动装配的配置类。
    • removeDuplicates 用LinkedHashSet实现去重
    • getExclusions更具@EnableAutoConfiguration注解中配置的exclude等属性吧不需要自动装配的配置类移除
    • fireAutoConfigurationImportEvents广播事件
    • 最后返回经过过滤之后的配置类集合
  • 以上步骤中,核心在于获取自动装配的配置类getCandidateConfigurations,其他只是在最筛选等其他步骤,我们看该方法实现

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;}
  • 如上代码用到了SpringFactoriesLoader,他是Spring内部提供的一种类加载方式,类似java的SPI,他会扫描classpath目录下的META-INF/spring.factories文件,spring.factories文件中的数据以 key=value 的形式存储,也就是getCandidateConfigurations 方法的返回值

  • 如下图是spring-boot-autoconfiguration对应的jar包中文件,我们可以在jar中找到对应的spring.factories
    在这里插入图片描述

  • 内容如下

......
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
......
  • 里面包含了多种Spring支持的组件的加载,我们这以Redis为案例,通过Debug,我们查看getCandidateConfigurations所扫描到的所有类,如下图所示,其中就包括我们上图中找到的Redis的支持:

在这里插入图片描述

  • 我们打开RedisAutoConfiguration可以看到,他是一个基于JavaConfig形式的配置类,如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {@Bean@ConditionalOnMissingBean(name = "redisTemplate")public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);return template;}@Bean@ConditionalOnMissingBeanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}}
  • 除了基本的Configuration 和Bean两个注解,还有一个COnditionalOnClass,这个条件控制机制在这里用途是判断classpath下是否存在RedisOperations这个类,我们找一下这个类属于那个jar中,如下图

在这里插入图片描述

  • 如上图,所示,他在spring-data-redis中,而我们只引入了一个有关redis的jar就是那个redis-start,那么结论显而易见了,在Spring Boot自动装配的时候,他能扫描到所有支持的组件,但是他实际加载到IOC中的会依据每个组件的condition进行第一次筛选,只有找到对应的资源文件他才会去加载
  • @EnableConfigurationProperties注解也是我们需要关注的,他说有关属性配置的,也就是我们按照约定在application.properties中配置的Redis的参数,如下Redis.properties
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {/*** Database index used by the connection factory.*/private int database = 0;/*** Connection URL. Overrides host, port, and password. User is ignored. Example:* redis://user:password@example.com:6379*/private String url;/*** Redis server host.*/private String host = "localhost";/*** Login password of the redis server.*/private String password;/*** Redis server port.*/private int port = 6379;/*** Whether to enable SSL support.*/private boolean ssl;/*** Connection timeout.*/private Duration timeout;/*** Client name to be set on connections with CLIENT SETNAME.*/private String clientName;private Sentinel sentinel;private Cluster cluster;......}
  • 我们的properties中的配置样式的由来就是由此得出的
spring.redis.host=127.0.0.1
spring.redis.port=6379
  • 由此自动装配的基本原理就完结了,总结过程如下:
    • 通过@Import(AutoConfigurationImportSelector)实现配置类的导入,但是这里并不是传统意义上的单个配置类装配
    • AutoConfigurationImportSelector实现了ImportSelector接口,重写了selectImports,他用来实现选择性批量配置类的装配
    • 通过Spring提供的SpringFactoriesLoader机制,扫描classpath路径下的META-INF/spring.factories读取自动装配的配置类
    • 通过筛选条件,吧不符合的配置类移除,最中完成自动装配

下一篇:SpringBoot中Bean按条件装配

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310261.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自制 .NET Core 路由调试中间件

点击上方蓝字关注“汪宇杰博客”导语本文教大家如何在 .NET Core 应用中使用中间件输出路由信息以便调试程序。背景在 .NET Framework 的上古时代&#xff0c;有个叫做 RouteDebugger 的神器&#xff0c;可以在 MVC 或 Web API 应用中输出当前页面的路由信息&#xff0c;也可查…

玉柴spn码故障对照表_后处理的故障不总是尿素泵故障,也有可能是这些原因

之前说到后处理故障&#xff0c;解决的都是柴油机尿素泵、喷嘴的&#xff0c;而SCR箱同样是一个重要的部件&#xff0c;它的作用就是将尿素液与尾气中的氮氧化物充分混合并发生化学反应的场所&#xff0c;目前重卡SCR箱集SCR催化器和发动机排气消声器与一体。整体材料为不锈钢&…

SpringBoot中Bean按条件装配

Conditional条件装配 Conditional是Spring Framework提供的一个核心功能注解&#xff0c;这个注解的作用是提供自动装配的条件限制&#xff0c;一般我们在用Configuration&#xff0c;Bean的时候使用它。也就是我们在自定义Bean的注入的时候&#xff0c;我们可以通过Condition…

定义一个手表_华米Amazfit Pop评测:一款功能全面的“性价比”手表

目前的智能手表虽然品牌、型号众多&#xff0c;但基本可以分为二种类型&#xff1a;第一种为入门级智能手表&#xff0c;其功能单一与智能手环差不多&#xff0c;但胜在屏幕大、能够带来更好的观感且价格便宜&#xff1b;第二种为旗舰级智能手表&#xff0c;功能全面、硬件水准…

[Java基础]复制文件的异常处理try...catch...finally的做法

代码如下: package ErrorOperatorPack;import java.io.FileReader; import java.io.FileWriter; import java.io.IOException;public class CopyFileDemo01 {public static void main(String[] args){}private static void method() {FileReader fr null;FileWriter fw null…

有哪些你踏入社会才明白的道理

不知不觉已经工作10多年&#xff0c;从一个懵懂的大学生到被社会无情毒打&#xff0c;终于成长一个职场老鸟。最近几天在胡思乱想&#xff0c;这10多年不少认知和感悟&#xff0c;如果10年前有人能告诉我&#xff0c;我会不会少走很多很多弯路&#xff1f;读书的时候&#xff0…

手把手教你 git revert merge

开发中git分支管理 研发流程 从develop分支切出一个新分支&#xff0c;根据是功能还是bug&#xff0c;命名为id-xxx 或 id-fixbug-*。开发者完成开发&#xff0c;提交分支到远程仓库。开发者发起merge请求&#xff0c;将新分支请求merge到develop分支&#xff0c;并提醒code r…

如何把自己的经历写成小说_古天乐的经历教会我们:如何在被欺骗以后改善自己的心理状态...

众所周知&#xff0c;这个只有太阳能黑他的男人&#xff0c;早年未发迹时曾干过泊车小弟等工作&#xff0c;后来作为模特经纪人接触娱乐圈&#xff0c;传闻某次模特迟到&#xff0c;古爷临时救场&#xff0c;算是正式踏入娱圈&#xff0c;出现在无数大牌歌星的MV里&#xff0c;…

从GC的SuppressFinalize方法带你深刻认识Finalize底层运行机制

如果你经常看开源项目的源码&#xff0c;你会发现很多Dispose方法中都有这么一句代码&#xff1a; GC.SuppressFinalize(this); &#xff0c;看过一两次可能无所谓&#xff0c;看多了就来了兴趣&#xff0c;这篇就跟大家聊一聊。一&#xff1a;背景1. 在哪发现的相信现在Mysql在…

NIO工作方式浅析

java Socket 工作机制 Socket是描述计算机之前相互通信的一种抽象功能。通过基于TCP/IP的流套接字协议建立连接A机器B机器通信—建立Socket连接—通过TCP连接&#xff08;端口号指定唯一应用&#xff09;----IP寻址&#xff08;寻找唯一主机&#xff09;----最终找到唯一主机上…

bufferedimage生成的图片模糊_Kaptcha图片验证码工具

阅读文本大概需要3分钟。验证码的作用图片验证码自从诞生以来从未被抛弃&#xff0c;依然发出属于它所应有的光。验证码经常验证如下一些场景。1、用户登录&#xff0c;防止机器人登录2、论坛留言&#xff0c;防止恶意灌水3、短信验证码发送&#xff0c;防止盗刷短信Kaptcha 简…

[Java基础]对象(反)序列化流

对象序列化流: 代码如下: package ObjectOutputStreamPack;import java.io.Serializable;public class Student implements Serializable {private String name;private int age;public Student() {}public Student(String name, int age) {this.name name;this.age age;}pu…

C# 9 新特性:代码生成器、编译时反射

前言今天 .NET 官方博客宣布 C# 9 Source Generators 第一个预览版发布&#xff0c;这是一个用户已经喊了快 5 年特性&#xff0c;今天终于发布了。简介Source Generators 顾名思义代码生成器&#xff0c;它允许开发者在代码编译过程中获取查看用户代码并且生成新的 C# 代码参与…

I/O性能与可靠性

I/O调优 磁盘I/O优化 性能检测&#xff1a; 压力测试应用程序&#xff0c;观察系统I/O wait指标是否正常&#xff0c;例如有n个CPU&#xff0c;利息情况下I/O wait参数不超过25%&#xff0c;如果超过&#xff0c;就是这个程序的瓶颈就是在IO操作上了可以用iostat命令查看另外…

.NET开源工具类库:Masuit.Tools

【开源框架】| 通用工具类库这是恰童鞋骚年的第223篇原创文章本文介绍一个我的同事【懒得勤快】&#xff08;人称勤快哥&#xff0c;我们叫他骚哥&#xff09;写的一个.NET开源工具类库项目&#xff0c;包含一些常用的操作类&#xff0c;大都是静态类&#xff0c;加密解密&…

[Java基础]字节,字符打印流

代码如下&#xff1a; package PrintWriterPack;import java.io.FileNotFoundException; import java.io.PrintWriter;public class PrintWriterDemo {public static void main(String[] args) throws FileNotFoundException {PrintWriter pw new PrintWriter("D:\\Java…

javaI/O包中的包装模式

设计模式解析–适配器模式 对适配器模式功能比较好理解&#xff0c;就是讲一个类的接口换成客户端所能接受的另外一个接口&#xff0c;从而使两国接口不匹配而无法在一起工作的两个类能在一起工作。 适配器的结构 适配器UML图如下 Target&#xff08;目标接口&#xff09;&…

DevOps vs. Agile:它们有什么共同点?

导语DevOps与Agile有很多不同&#xff0c;但它们之间仍可发现很多共同点&#xff0c;这篇文章为读者揭晓。正文DevOps和Agile之间有着明显的关系。Agile是方法论&#xff0c;Scrum是框架&#xff0c;并DevOps随着看板也落在了Agile的“伞”下。精益&#xff0c;大规模的Scrum&a…