草庐IT

SpringBoot 自动装配的原理分析

ziyou——Java极客技术 2023-04-17 原文

关于 SpringBoot 的自动装配功能,相信是每一个 Java 程序员天天都会用到的一个功能,但是它究竟是如何实现的呢?今天阿粉来带大家看一下。

自动装配案例

首先我们通过一个案例来看一下自动装配的效果,创建一个 SpringBoot 的项目,在 pom 文件中加入下面的依赖。

   <dependency>
	  <groupId>org.springframework.boot</groupId>
	  <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

其中 web 的依赖表示我们这是一个 web 项目,redis 的依赖就是我们这边是要验证的功能依赖。随后在 application.properties 配置文件中增加 redis 的相关配置如下

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123456

再编写一个 ControllerService 类,相关代码如下。

package com.example.demo.controller;

import com.example.demo.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

  @Autowired
  private HelloService helloService;
  
  @GetMapping(value = "/hello")
  public String hello(@RequestParam("name") String name) {
    return helloService.sayHello(name);
  }

}

service 代码如下

package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class HelloService {

  @Autowired
  RedisTemplate<String, String> redisTemplate;

  public String sayHello(String name) {
    String result = doSomething(name);
    redisTemplate.opsForValue().set("name", result);
    result = redisTemplate.opsForValue().get("name");
    return "hello: " + result;
  }

  private String doSomething(String name) {
    return name + " 欢迎关注 Java 极客技术";
  }

}

启动项目,然后我们通过访问 http://127.0.0.1:8080/hello?name=ziyou,可以看到正常访问。接下来我们再通过 Redis 的客户端,去观察一下数据是否正确的写入到 Redis 中,效果跟我们想象的一致。

自动装配分析

看到这里很多小伙伴就会说,这个写法我天天都在使用,用起来是真的爽。虽然用起来是很爽,但是大家有没有想过一个问题,那就是在我们的 HelloService 中通过 @Autowired 注入了一个 RedisTemplate 类,但是我们的代码中并没有写过这个类,也没有使用类似于@RestController,@Service 这样的注解将 RedisTemplate 注入到 Spring IoC 容器中,那为什么我们就可以通过 @Autowired 注解从 IoC 容器中获取到 RedisTemplate 这个类呢?这里就是常说的自动装配的功能了。

首先我们看下项目的启动类,

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(value = "com.example.demo.*")
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

在启动类上面有一个 @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 注解,正是因为有了这样一个注解,我们才得以实现自动装配的功能。继续往下面看。

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

可以看到 @EnableAutoConfiguration 注解中有一个 @Import({AutoConfigurationImportSelector.class}),导入了一个 AutoConfigurationImportSelector 类,该类间接实现了 ImportSelector 接口,实现了一个 String[] selectImports(AnnotationMetadata importingClassMetadata); 方法,这个方法的返回值是一个字符串数组,对应的是一系列主要注入到 Spring IoC 容器中的类名。当在 @Import 中导入一个 ImportSelector 的实现类之后,会把该实现类中返回的 Class 名称都装载到 IoC 容器中。

一旦被装载到 IoC 容器中过后,我们在后续就可以通过 @Autowired 来进行使用了。接下来我们看下 selectImports 方法里面的实现,当中引用了 getCandidateConfigurations 方法 ,其中的 ImportCandidates.load 方法我们可以看到是通过加载 String location = String.format("META-INF/spring/%s.imports", annotation.getName()); 对应路径下的 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,其中就包含了很多自动装配的配置类。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
        ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

我们可以看到这个文件中有一个 RedisAutoConfiguration 配置类,在这个配置中就有我们需要的 RedisTemplate 类的 Bean,同时也可以看到,在类上面有一个 @ConditionalOnClass({RedisOperations.class}) 注解,表示只要在类路径上有 RedisOperations.class 这个类的时候才会进行实例化。这也就是为什么只要我们添加了依赖,就可以自动装配的原因。

通过 org.springframework.boot.autoconfigure.AutoConfiguration.imports 这个文件,我们可以看到有很多官方帮我们实现好了配置类,这些功能只要我们在 pom 文件中添加对应的 starter 依赖,然后做一些简单的配置就可以直接使用。

其中本质上自动装配的原理很简单,本质上都需要实现一个配置类,只不过这个配置类是官方帮我们创建好了,再加了一些条件类注解,让对应的实例化只发生类类路径存在某些类的时候才会触发。这个配置类跟我们平常自己通过 JavaConfig 形式编写的配置类没有本质的区别。

自动装配总结

从上面的分析我们就可以看的出来,之所以很多时候我们使用 SpringBoot 是如此的简单,全都是依赖约定优于配置的思想,很多复杂的逻辑,在框架底层都帮我们做了默认的实现。虽然用起来很爽,但是很多时候会让程序员不懂原理,我们需要做的不仅是会使用,而更要知道底层的逻辑,才能走的更远。

基于上面的分析,我们还可以知道,如果我们要实现一个自己的 starter 其实也很简单,只要安装上面的约定,编写我们自己的配置类和配置文件即可。后面的文章阿粉会带你手写一个自己的 starter 来具体实现一下。


更多优质内容欢迎关注公众号【Java 极客技术】,我准备了一份面试资料,回复【bbbb07】免费领取。希望能在这寒冷的日子里,帮助到大家。

有关SpringBoot 自动装配的原理分析的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  3. ruby-on-rails - 从应用程序中自定义文件夹内的命名空间自动加载 - 2

    我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty

  4. ruby-on-rails - 有没有一种工具可以在编码时自动保存对文件的增量更改? - 2

    我最喜欢的Google文档功能之一是它会在我工作时不断自动保存我的文档版本。这意味着即使我在进行关键更改之前忘记在某个点进行保存,也很有可能会自动创建一个保存点。至少,我可以将文档恢复到错误更改之前的状态,并从该点继续工作。对于在MacOS(或UNIX)上运行的Ruby编码器,是否有具有等效功能的工具?例如,一个工具会每隔几分钟自动将Gitcheckin我的本地存储库以获取我正在处理的文件。也许我有点偏执,但这点小保险可以让我在日常工作中安心。 最佳答案 虚拟机有些人可能讨厌我对此的回应,但我在编码时经常使用VIM,它具有自动保存功

  5. ruby - 在 ruby​​ 中使用自动创建插入数组 - 2

    我想知道是否可以通过自动创建数组来插入数组,如果数组不存在的话,就像在PHP中一样:$toto[]='titi';如果尚未定义$toto,它将创建数组并将“titi”压入。如果已经存在,它只会推送。在Ruby中我必须这样做:toto||=[]toto.push('titi')可以一行完成吗?因为如果我有一个循环,它会测试“||=”,除了第一次:Person.all.eachdo|person|toto||=[]#with1billionofperson,thislineisuseless999999999times...toto.push(person.name)你有更好的解决方案吗?

  6. 建模分析 | 平面2R机器人(二连杆)运动学与动力学建模(附Matlab仿真) - 2

    目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标

  7. 网站日志分析软件--让网站日志分析工作变得更简单 - 2

    网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.

  8. 【自动驾驶环境感知项目】——基于Paddle3D的点云障碍物检测 - 2

    文章目录1.自动驾驶实战:基于Paddle3D的点云障碍物检测1.1环境信息1.2准备点云数据1.3安装Paddle3D1.4模型训练1.5模型评估1.6模型导出1.7模型部署效果附录show_lidar_pred_on_image.py1.自动驾驶实战:基于Paddle3D的点云障碍物检测项目地址——自动驾驶实战:基于Paddle3D的点云障碍物检测课程地址——自动驾驶感知系统揭秘1.1环境信息硬件信息CPU:2核AI加速卡:v100总显存:16GB总内存:16GB总硬盘:100GB环境配置Python:3.7.4框架信息框架版本:PaddlePaddle2.4.0(项目默认框架版本为2.3

  9. ABB-IRB-1200运动学分析MATLAB RVC工具分析+Simulink-Adams联合仿真 - 2

    一、机器人介绍        此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接

  10. 关于Qt程序打包后运行库依赖的常见问题分析及解决方法 - 2

    目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'

随机推荐