首页 > 其他分享 >SpringBoot自动装配原理解析

SpringBoot自动装配原理解析

时间:2024-11-12 19:18:52浏览次数:3  
标签:装配 SpringBoot args class SPI 注解 解析 StarterDemoApplication public

什么是Spring Boot自动装配

Spring Boot自动装配是指在Spring Boot应用启动时,根据类路径下的jar包依赖、Bean定义、各种配置文件等信息,自动配置Spring应用上下文的Bean。

这种机制极大地简化了配置工作,使得开发者可以更加专注于业务逻辑的实现。

在深入自动装配原理前,我们先看下 SPI 机制

SPI 机制

SPI(Service Provider Interface)是一种动态替换服务提供者的机制。它允许一个服务接口有多个服务提供者,并且在程序运行时动态选择一个服务提供者。

SPI又分为 JDK SPISpring SPI

JDK SPI

在 Java 平台上,SPI 通常是通过 java.util.ServiceLoader 类实现的,这种机制在Java标准库中广泛应用,如JDBC驱动的管理。

SPI可以很灵活的让接口和实现分离,让服务提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展。

工作原理

Java SPI 的工作原理基于以下几个步骤:

  • 定义服务接口:首先定义一个服务接口(或抽象类),作为服务的规范。
  • 提供服务实现:编写接口的具体实现类。
  • 注册服务实现:在 META-INF/services 目录下创建一个以接口全限定名为名的文件,文件内容为接口实现类的全限定名。
  • 加载服务实现:使用 java.util.ServiceLoader 来加载并使用这些实现。

举例说明一下:

(1)创建一个 DemoDAO 的接口

public interface DemoDao {}

(2)分别创建两个实现类

public class MysqlDao implements DemoDao {}
public class OracleDao implements DemoDao {}

(3)在resources下新建META-INF/services/目录,在该目录下新建接口全限定名的文件com.study.spring.z_spi.DemoDao,文件内容是上面那两个实现类。

com.study.spring.z_spi.MysqlDao
com.study.spring.z_spi.OracleDao

(4)使用 JDK 提供的ServiceLoader来测试下

public class JdkSpiApplication {
    public static void main(String[] args) {
        ServiceLoader<DemoDao> demoDaos = ServiceLoader.load(DemoDao.class);
        demoDaos.iterator().forEachRemaining(t -> {
            System.out.println(t);
        });
    }
}

输出结果 :
com.study.spring.z_spi.MysqlDao@6e8cf4c6
com.study.spring.z_spi.OracleDao@12edcd21

JDBC DriverManager

Java SPI 机制在JDBC驱动管理中的应用主要体现在JDBC 4.0及以上版本的驱动自动发现和加载上。

在JDBC4.0之前,连接数据库的时候,通常会用Class.forName(“com.mysql.jdbc.Driver”)先加载数据库相关的驱动,然后再进行获取连接等的操作。
而JDBC4.0之后不需要用Class.forName(“com.mysql.jdbc.Driver”)来加载驱动,直接获取连接就可以了,这种方式就是使用了Java的SPI扩展机制来实现。

定义服务接口

在JDBC中,服务接口是由Java平台定义的java.sql.Driver接口,所有的JDBC驱动都必须实现这个接口,以提供与数据库建立连接的能力。

提供服务实现

数据库厂商(如MySQL、Oracle等)为它们的数据库提供JDBC驱动程序,这些驱动程序实现了java.sql.Driver接口。

注册服务提供者

JDBC驱动的注册是通过在驱动程序的JAR包中的META-INF/services目录下创建一个名为java.sql.Driver的文件来完成的。这个文件包含了实现java.sql.Driver接口的驱动类的全限定名。当JVM启动时,它会查找这个文件,并加载其中指定的驱动类。

在这里插入图片描述

加载服务提供者

在JDBC 4.0及以上版本中,DriverManager类在初始化时会使用Java的SPI机制自动加载所有在META-INF/services/java.sql.Driver文件中指定的驱动类。
这是通过ServiceLoader类实现的,它会查找并加载所有可用的JDBC驱动实现。

在这里插入图片描述

连接管理

当应用程序尝试通过DriverManager.getConnection()方法连接数据库时,DriverManager会遍历所有已加载的驱动实例,尝试建立连接。一旦某个驱动成功建立连接,它就会返回这个连接,并且不会继续尝试其他的驱动实例。

SpringBoot SPI 机制

Spring Boot 对 SPI 机制进行了扩展,以支持其自动配置和模块化架构。

Spring Boot 利用 spring.factories (从 SpringBoot 2.7 起自动配置不推荐使用 /META-INF/spring.factories 文件,而是在/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)文件,这个文件列出了与自动配置相关的接口及其实现类,Spring Boot 启动时会加载这些配置。

spring.factories

这个文件里面使用键值对的格式列出了多种服务类型及其对应的实现类,常见的服务类型包括:

  • org.springframework.boot.autoconfigure.EnableAutoConfiguration:用于自动配置。

  • org.springframework.context.ApplicationListener:用于应用事件监听器。

  • org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider:用于模板引擎的可用性判断。

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

不使用SPI

现在我们先来看下,不使用 SPI 机制,怎么实现 bean 的配置。

新建一个项目,将新类MyAppDemo注入 IOC 容器。

在这里插入图片描述

在另一个项目 demo 中,引入 myApp 的依赖,并测试调用 test 方法。

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

@SpringBootTest
class StarterDemoApplicationTests {

	@Autowired
	private ApplicationContext applicationContext;

	@Test
	void contextLoads() {
		MyAppDemo demo = applicationContext.getBean(MyAppDemo.class);
		demo.test();
	}
}

测试发现找不到该 bean 对象

在这里插入图片描述

为什么引入的第三方依赖包中的 bean 没有生效呢?

  • 原因是因为,在类上添加@Component注解来声明bean对象时,还需要保证@Component注解能被Spring的组件扫描到。
  • SpringBoot项目中的@SpringBootApplication注解,具有包扫描的作用,但是它只会扫描启动类所在的当前包以及子包。
  • 当前包:com.starter.demo, 第三方依赖中提供的包:com.myapp.demo(扫描不到)

所以,有两种方案可以解决

  • @ComponentScan 组件扫描第三方依赖的包路径;
  • @Import 导入(使用@Import导入的类会被Spring加载到IOC容器中)。
@ComponentScan 组件扫描
@SpringBootApplication
@ComponentScan(basePackages = {"com.starter","com.myapp"})
public class StarterDemoApplication {

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

缺点:当需要引入大量的第三方依赖,就需要在启动类上配置大量要扫描的包,这种方式会很繁琐。

Import 导入
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 */
	Class<?>[] value();
}

从源码可以看到,导入形式有以下几种

  • 普通类
  • 配置类
  • ImportSelector的实现类
  • ImportBeanDefinitionRegistrar的实现类
导入普通类
@SpringBootApplication
@Import(MyAppDemo.class)
public class StarterDemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(StarterDemoApplication.class, args);
	}
}
导入配置类

去掉MyAppDemo类上的@Component注解,新建一个MyAppConfig配置类。

@Configuration
public class MyAppConfig {
    @Bean
    public MyAppDemo myAppDemo() {
        return new MyAppDemo();
    }
}

在启动类上导入配置类

@SpringBootApplication
@Import(MyAppConfig.class)
public class StarterDemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(StarterDemoApplication.class, args);
	}
}
导入ImportSelector的实现类

新建 ImportSelector的实现类

public class MyAppImportSelector implements ImportSelector {

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {"com.myapp.demo.MyAppDemo"};
    }
}

在启动类上导入MyAppImportSelector

@SpringBootApplication
@Import(MyAppImportSelector.class)
public class StarterDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(StarterDemoApplication.class, args);
	}
}
导入ImportBeanDefinitionRegistrar的实现类

新建 ImportBeanDefinitionRegistrar 的实现类

public class MyAppImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {

        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(MyAppDemo.class);


        registry.registerBeanDefinition("myAppDemoTest", builder.getBeanDefinition());
    }
}

在启动类上导入MyAppImportBeanDefinitionRegistrar

@SpringBootApplication
@Import(MyAppImportBeanDefinitionRegistrar.class)
public class StarterDemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(StarterDemoApplication.class, args);
	}
}
模块装配

通过@Import 注解,我们可以导入第三方依赖包中的配置类。

但是基于上面的方式,我们在引入第三方依赖时,还要知道第三方依赖中有哪些配置类和哪些Bean对象?相当麻烦!

而第三方依赖自己最清楚自己有哪些配置类、有那些 Bean 对象,它提供一个注解,通过这个注解,外部系统可以引入自己所需要的 Bean 对象。

这个注解一般都以@EnableXxx开头,注解中封装的就是@Import注解,外部系统在使用时只需要加上@EnableXxxxx注解即可。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyAppConfig.class)
public @interface EnableMyApp {}
@SpringBootApplication
@EnableMyApp
public class StarterDemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(StarterDemoApplication.class, args);
	}
}

spring 中的模块装配,是在3.1 之后引入了大量的@EnableXXX 注解,来快速整合激活相对应的模块。
如:
● EnableTransactionManagement :开启注解事务驱动
● EnableWebMvc :激活 SpringWebMvc
● EnableAspectJAutoProxy :开启注解 AOP 编程
● EnableScheduling :开启调度功能(定时任务)
模块装配的核心原则:自定义注解+@Import 导入组件

使用SPI

即使是采用@EnableXXX注解,还是觉得麻烦怎么办?

我想引入第三方依赖后,直接就去使用它,而不是再单独写一个什么什么注解。

下面我们来看看基于 Spring 的 SPI 机制怎么去实现。

在在resources目录下创建META-INF 目录,并新建 spring.factories文件,文件内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.myapp.demo.MyAppConfig
@Configuration
public class MyAppConfig {

    @Bean
    public MyAppDemo myAppDemo() {
        return new MyAppDemo();
    }
}

我们只是引入了第三方依赖包,并没有手动配置,也没有写什么注解啊,就可以通过IOC容器或DI依赖拿到bean对象了,这就是SpringBoot自动配置的强大之处。

自动装配源码

从 SpringBoot 核心注解说起

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

重点看@EnableAutoConfiguration,核心中的核心,重点中的重点。

在这里插入图片描述

点进去 AutoConfigurationImportSelector

在这里插入图片描述
可以看到 AutoConfigurationImportSelector 实现了 DeferredImportSelector,而DeferredImportSelector 又继承了ImportSelector

AutoConfigurationImportSelector类中重写了ImportSelector接口的selectImports()方法:

在这里插入图片描述

在这里插入图片描述
再点进去

在这里插入图片描述

可以看到这个getCandidateConfigurations()方法,就是去获取META-INF/spring.factories文件中配置类的集合。

再接着点点点
在这里插入图片描述
在这里插入图片描述

标签:装配,SpringBoot,args,class,SPI,注解,解析,StarterDemoApplication,public
From: https://blog.csdn.net/yqq962464/article/details/143706481

相关文章

  • flask基于SpringBoot框架的阳光二手书籍管理系统(毕设源码+论文)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景关于二手书籍管理系统的研究,现有研究多集中在商业二手书交易平台的运营和管理等方面,专门针对校园内的阳光二手书籍管理系统的研究较少......
  • flask基于Springboot恒生蔬菜库存管理系统(毕设源码+论文)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景关于蔬菜库存管理系统的研究,现有研究主要以通用的库存管理模式为主,专门针对蔬菜这种具有易腐性、季节性等特殊属性的库存管理系统研究......
  • flask基于SpringBoot和vue的酒吧运营系统(毕设源码+论文)
    校园二手货物交易平台m1a2o本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景关于酒吧运营系统的研究,现有研究主要以酒吧的营销策略、服务体验等为主,专门针对酒吧运营系统全面功能架构......
  • 基于springboot的汽车租赁管理系统的设计与实现
    项目描述临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下,你想解决的问题,今天给大家介绍一篇基于springboot的汽车租赁管理系统的设计与实......
  • SpringBoot棋牌室管理系统的设计与实现
     作者简介:Java领域优质创作者、CSDN博客专家、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验,被多个学校常年聘为校外企业导师,指导学生毕业设计并参与学生毕业答辩指导,有较为丰富的相关经验。期待与各位高校教师、企......
  • zuul 验证,重写返回报文,解析gzip压缩response,使用案例
    业务是调用另一个平台API,用他们的接口能力实现一些功能。真正请求前的filter,我把一些请求前的验证和日志入库放在了这里。importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.JSONObject;importcom.alibaba.fastjson2.util.DateUtils;importcom.iMagine.iMagi......
  • Python科学计算的利器:Scipy库深度解析
    Python科学计算的利器:SciPy库深度解析在数据科学、工程计算和数学建模领域,Python的SciPy库是不可或缺的强大工具。SciPy以NumPy为基础,提供了丰富的函数和算法,用于数值积分、优化、线性代数、信号处理等科学计算任务。本文将详细介绍SciPy库的核心模块和功能,帮助你深入理解......
  • 域名解析线路类型有哪几种
    在网络世界中,域名解析是将域名转换为IP地址的关键环节,而域名解析线路类型的不同则为域名解析提供了多样化的策略,以满足不同用户和网络环境的需求。以下是几种常见的域名解析线路类型。电信线路电信线路解析主要是针对中国电信网络用户的优化策略。在我国,电信网络拥有庞大的用户......
  • 第一章springboot开发入门
    学习目标:对于掌握和熟悉的东西需要背下来1.1SpringBoot概述1.1.1SpringBoot简介springboot的历史(也可以另外看文字,了解历史有助于你提高对框架的学习和理解):spring框架虽然已经是轻量级的啦,但是配置文件是重量级的,随着生产中敏捷开发的需要,在spring中使用注解开发逐......
  • ResumeSDK简历解析库编程案例
    目录1、软件概述2、编程案例2.1、官网案例(阿里云)2.2、优化案例3、解析结果1、软件概述ResumeSDK简历解析是北京无奇科技有限公司研发,业界领先的智能简历解析和人岗匹配算法厂商,提供专业的AI招聘技术服务,致力于人力资源行业智能化这一进程。并已经上线阿里云或腾讯云,......