细说SpringBoot的自动装配原理
ztj100 2025-01-07 17:23 15 浏览 0 评论
1.什么是SpringBoot?
??对于spring框架,我们接触得比较多的应该是spring mvc、和spring。而spring的核心在于IOC(控制反转对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系)和DI(依赖注入IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道)。而这些框架在使用的过程中会需要配置大量的xml,或者需要做很多繁琐的配置。
??springboot框架是为了能够帮助使用spring框架的开发者快速高效的构建一个基于spirng框架以及spring生态体系的应用解决方案。它是对“约定优于配置”这个理念下的一个最佳实践。因此它是一个服务于框架的框架,服务的范围是简化配置文件。
2.初步认识Spring Boot
??我们可以使用 https://start.spring.io
@SpringBootApplication
public class SpringBootStudyApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootStudyApplication.class, args);
}
}
??为了让大家看到效果,我们使用spring mvc来构建一个web应用,而springboot帮我们简化了非常多的逻辑使得我们非常容易去构建一个web项目。
??springboot提供了spring-boot-starter-web自动装配模块
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1234
??在当前项目下运行mvn spring-boot:run 或者直接运行main方法就可以启动一个使用了嵌入式tomcat服务请求的web应用,但是我们没有提供任何服务web请求的controller,所以访问任何路径都会返回一个springboot默认的错误页面(whitelabel error page)
??所以,我们可以创建一个Controller来实现请求
@RestController
public class HelloController {
@GetMapping("/say")
public String sayHello(){
return "hello Mic";
}
}
??访问http://localhost:8080/say 就可以获得一个请求结果。 这样就完成了一个非常简单的web应用。 springboot是一个约定优于配置的产物,所以在快速构建web应用的背后,其实有很多的约定。
- 项目结构层面,静态文件和页面模版的存放位置变成了src/main/resources对应的子目录下
- 自动嵌入tomcat作为web容器对外提供http服务,默认使用8080端口监听
- 自动装配springmvc必要的组件
3.Spring Boot四大核心
EnableAutoConfiguration 自动装配
Starter组件, 开箱即用
Actuator 监控
Spring Boot Cli 为Spring Cloud 提供了Spring Boot 命令行功能
??那今天主要给大家讲讲Enable*这个注解
4.Enable* 注解的作用
??Enable是启用的意思,相当于开启某一个功能
??仍然是在spring3.1版本中,提供了一系列的@Enable开头的注解,Enable主机应该是在JavaConfig框架上更进一步的完善,是的用户在使用spring相关的框架是,避免配置大量的代码从而降低使用的难度
??比如常见的一些Enable注解:EnableWebMvc,(这个注解引入了MVC框架在Spring 应用中需要用到的所有bean);
??比如说@EnableScheduling,开启计划任务的支持;
??找到EnableAutoConfiguration,我们可以看到每一个涉及到Enable开头的注解,都会带有一个@Import的注解。
5.深入分析Spring Boot中的自动装配
??在Spring Boot中,不得不说的一个点是自动装配,它是starter的基础,也是Spring Boot的核心, 那什么叫自动装配?或者什么叫装配呢?
??回顾一下Spring Framework,它最核心的功能是IOC和AOP, IoC容器的主要功能是可以管理对象的生命周期。也就是bean的管理。我们把Bean对象托管到Spring Ioc容器的这个过程称为装配,那什么是自动装配呢?我们慢慢来介绍
??首先大家看下这张图,我们先不解释。等把今天的内容讲完,我们再回头来通过这张图来总结~
??自动装配在SpringBoot是基于EnableAutoConfiguration来实现的。那么在深入分析EnableAutoConfiguration之前,我们来看一下传统意义上的装配方式。
5.1简单分析@Configuration
??Configuration这个注解大家应该有用过,它是JavaConfig形式的基于Spring IOC容器的配置类使用的一种注解。因为SpringBoot本质上就是一个spring应用,所以通过这个注解来加载IOC容器的配置是很正常的。所以在启动类里面标注了@Configuration,意味着它其实也是一个IoC容器的配置类。
public class DemoClass {
public void say(){
System.out.println("sya hello ... ");
}
}
@Configuration
@Import(UserClass.class)
public class DemoConfiguration {
@Bean
public DemoClass getDemoClass(){
return new DemoClass();
}
}
public class DemoConfigurationMain {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(DemoConfiguration.class);
DemoClass bean = ac.getBean(DemoClass.class);
bean.say();
}
}
??这种形式就是通过注解的方式来实现IoC容器,传统意义上的spring应用都是基于xml形式来配置bean的依赖关系。然后通过spring容器在启动的时候,把bean进行初始化并且,如果bean之间存在依赖关系,则分析这些已经在IoC容器中的bean根据依赖关系进行组装。
??直到Java5中,引入了Annotations这个特性,Spring框架也紧随大流并且推出了基于Java代码和Annotation元信息的依赖关系绑定描述的方式。也就是JavaConfig。
??从spring3开始,spring就支持了两种bean的配置方式,一种是基于xml文件方式、另一种就是JavaConfig
Configuration的本质
??如果大家细心一点就会发现Configuration注解的本质是一个Component注解,这个注解会被AnnotationConfigApplicationContext加载,而AnnotationConfigApplicationContext是ApplicationContext的一个具体实现,表示根据配置注解启动应用上下文。
??因此我们在Main方法中通过AnnotationConfigApplicationContext去加载JavaConfig后,可以得到Ioc容器中的bean的实例
??JavaConfig的方式在前面的代码中已经演示过了,任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。而在这个配置类中,任何标注了@Bean的方法,它的返回值都会作为Bean定义注册到Spring的IOC容器,方法名默认成为这个bean的id
5.2简单分析@ComponentScan
??ComponentScan这个注解是大家接触得最多的了,相当于xml配置文件中的<context:component-scan>。 它的主要作用就是扫描指定路径下的标识了需要装配的类,自动装配到spring的Ioc容器中。
??标识需要装配的类的形式主要是:@Component、@Repository、@Service、@Controller这类的注解标识的类。
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(DemoConfiguration.class);
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
System.out.println("********************************");
for (String beanName:beanDefinitionNames
) {
System.out.println(beanName);
}
}
5.3 Import注解
??import 注解是什么意思呢? 联想到xml形式下有一个<import resource/> 形式的注解,就明白它的作用了。import就是把多个分开的容器配置合并在一个配置中。在JavaConfig中所表达的意义是一样的。为了能更好的理解后面讲的EnableAutoConfiguration,我们详细地和大家来介绍下import注解的使用
方式一:直接填class数组
??我们先在不同的两个package下创建对应的bean。
??比如:
??那么DemoConfigurationMain中执行的代码应该是加载不到demo2中的UserClass的
??这时我们可以通过@import来直接加载
或者
方式二:ImportSelector方式【重点】
??第一种方式如果导入的是一个配置类,那么该配置类中的所有的都会加载,如果想要更加的灵活,动态的去加载的话可以通过Import接口的第二种使用方式,也就是ImportSelector这种方式,我们来看看怎么使用。
LogService
public class LogService {
}
CacheService
public class CacheService {
}
GpDefineImportSelector
public class GpDefineImportSelector implements ImportSelector {
/**
* AnnotationMetadata 注解元数据
* @param annotationMetadata
* @return
* 要被IOC容器加载的bean信息
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 我们可以基于注解元数据信息来动态的返回要加载的bean信息
annotationMetadata
.getAllAnnotationAttributes(EnableDefineService.class.getName(),true)
.forEach((k,v)->{
System.out.println(annotationMetadata.getClassName());
System.out.println("---> " + k+":" + String.valueOf(v));
});
return new String[]{CacheService.class.getName()};
}
}
EnableDefineService
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(GpDefineImportSelector.class)
public @interface EnableDefineService {
String[] packages() default "";
}
EnableDemoTest
@EnableDefineService()
@SpringBootApplication
public class EnableDemoTest {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(EnableDemoTest.class);//SpringApplication.run(EnableDemoTest.class,args);
System.out.println(ac.getBean(CacheService.class));
System.out.println(ac.getBean(LogService.class));
}
}
方式三:ImportBeanDefinitionRegistrar方式
??这种方式和第二种方式很相似,同样的要实现 ImportBeanDefinitionRegistrar 接口
public class GpImportDefinitionRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 指定bean的定义信息
RootBeanDefinition beanDefinition = new RootBeanDefinition(CacheService.class);
RootBeanDefinition beanDefinition1 = new RootBeanDefinition(LogService.class);
// 注册一个bean
beanDefinitionRegistry.registerBeanDefinition("cacheService1111",beanDefinition);
beanDefinitionRegistry.registerBeanDefinition("cacheService2222",beanDefinition1);
}
}
5.4 深入分析EnableAutoConfiguration原理
??了解了ImportSelector和ImportBeanDefinitionRegistrar后,对于EnableAutoConfiguration的理解就容易一些了
??它会通过import导入第三方提供的bean的配置类:AutoConfigurationImportSelector
AutoConfigurationImportSelector
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
try {
// 加载META-INF/spring-autoconfigure-metadata.properties 下的元数据信息
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取候选加载的配置信息 META-INF/spring.factories
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去掉重复的配置信息
configurations = this.removeDuplicates(configurations);
// 排序
configurations = this.sort(configurations, autoConfigurationMetadata);
// 获取 注解中配置的 exclusion 信息
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 检查
this.checkExcludedClasses(configurations, exclusions);
// 移除需要排除的信息
configurations.removeAll(exclusions);
// 过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
configurations = this.filter(configurations, autoConfigurationMetadata);
// 广播事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回要被加载的类数组
return (String[])configurations.toArray(new String[configurations.size()]);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
1234567891011121314151617181920212223242526272829303132
??本质上来说,其实EnableAutoConfiguration会帮助springboot应用把所有符合@Configuration配置都加载到当前SpringBoot创建的IoC容器,而这里面借助了Spring框架提供的一个工具类SpringFactoriesLoader的支持。以及用到了Spring提供的条件注解@Conditional,选择性地针对需要加载的bean进行条件过滤
5.5 SpringFactoriesLoader
??为了给大家补一下基础,我在这里简单分析一下SpringFactoriesLoader这个工具类的使用。它其实和java中的SPI机制的原理是一样的,不过它比SPI更好的点在于不会一次性加载所有的类,而是根据key进行加载。
??首先,SpringFactoriesLoader的作用是从classpath/META-INF/spring.factories文件中,根据key来加载对应的类到spring IoC容器中。接下来带大家实践一下
??通过mybatis整合来看
??引入mybatis的依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
12345
??查看 MybatisAutoConfiguration 里面的源码,发现在其中加载了SqlSessionFactory等信息。
通过实际案例来实现
??创建一个新的maven项目,引入相关的依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
</dependencies>
1234567
创建一个bean类
public class GpCoreService {
public void crudService(){
System.out.println("gupao core service run ...");
}
}
以及对应的配置类
@Configuration
public class GpConfiguration {
@Bean
public GpCoreService gpCoreService(){
return new GpCoreService();
}
}
然后打包
在另一个项目中导入该jar包
<dependency>
<groupId>org.gupao.edu</groupId>
<artifactId>Gp-Core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
12345
??通过下面代码获取依赖包中的属性
??运行结果会报错,原因是GuPaoCore并没有被Spring的IoC容器所加载,也就是没有被EnableAutoConfiguration导入
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
String[] beanDefinitionNames = run.getBeanDefinitionNames();
System.out.println(run.getBean(GpCoreService.class));
}
解决方案
??在GuPao-Core项目resources下新建文件夹META-INF,在文件夹下面新建spring.factories文件,文件中配置,key为自定配置类EnableAutoConfiguration的全路径,value是配置类的全路径
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gupao.edu.GpConfiguration
??重新打包,重新运行SpringBootStudyApplication这个类。
??可以发现,我们编写的那个类,就被加载进来了。
5.6深入理解条件过滤
??在分析AutoConfigurationImportSelector的源码时,会先扫描spring-autoconfiguration-metadata.properties文件,最后在扫描spring.factories对应的类时,会结合前面的元数据进行过滤,为什么要过滤呢? 原因是很多的@Configuration其实是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以,通过这种条件过滤可以有效的减少@configuration类的数量从而降低SpringBoot的启动时间。
??修改Gupao-Core
??在META-INF/增加配置文件,spring-autoconfigure-metadata.properties。
com.gupao.edu.GpConfiguration.ConditionalOnClass=com.gupao.edu.service.GpTestService
格式:自动配置的类全名.条件=值
上面这段代码的意思就是,如果当前的classpath下存在TestClass,则会对GuPaoConfig这个Configuration进行加载
演示过程(spring-boot)
1.沿用前面spring-boot工程的测试案例,直接运行main方法,发现原本能够被加载的GuPaoCore,发现在ioc容器中找不到了。
2.在当前工程中指定的包com.gupaoedu下创建一个TestClass以后,再运行上面这段代码,程序能够正常执行
好了~到此如果跟着一块动手的话相信你对于SpringBoot的自动装配应该有了不一样的理解
相关推荐
- Vue 技术栈(全家桶)(vue technology)
-
Vue技术栈(全家桶)尚硅谷前端研究院第1章:Vue核心Vue简介官网英文官网:https://vuejs.org/中文官网:https://cn.vuejs.org/...
- vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)
-
前言《vue基础》系列是再次回炉vue记的笔记,除了官网那部分知识点外,还会加入自己的一些理解。(里面会有部分和官网相同的文案,有经验的同学择感兴趣的阅读)在开发时,是不是遇到过这样的场景,响应...
- vue3 组件初始化流程(vue组件初始化顺序)
-
学习完成响应式系统后,咋们来看看vue3组件的初始化流程既然是看vue组件的初始化流程,咋们先来创建基本的代码,跑跑流程(在app.vue中写入以下内容,来跑流程)...
- vue3优雅的设置element-plus的table自动滚动到底部
-
场景我是需要在table最后添加一行数据,然后把滚动条滚动到最后。查网上的解决方案都是读取html结构,暴力的去获取,虽能解决问题,但是不喜欢这种打补丁的解决方案,我想着官方应该有相关的定义,于是就去...
- Vue3为什么推荐使用ref而不是reactive
-
为什么推荐使用ref而不是reactivereactive本身具有很大局限性导致使用过程需要额外注意,如果忽视这些问题将对开发造成不小的麻烦;ref更像是vue2时代optionapi的data的替...
- 9、echarts 在 vue 中怎么引用?(必会)
-
首先我们初始化一个vue项目,执行vueinitwebpackechart,接着我们进入初始化的项目下。安装echarts,npminstallecharts-S//或...
- 无所不能,将 Vue 渲染到嵌入式液晶屏
-
该文章转载自公众号@前端时刻,https://mp.weixin.qq.com/s/WDHW36zhfNFVFVv4jO2vrA前言...
- vue-element-admin 增删改查(五)(vue-element-admin怎么用)
-
此篇幅比较长,涉及到的小知识点也比较多,一定要耐心看完,记住学东西没有耐心可不行!!!一、添加和修改注:添加和编辑用到了同一个组件,也就是此篇文章你能学会如何封装组件及引用组件;第二能学会async和...
- 最全的 Vue 面试题+详解答案(vue面试题知识点大全)
-
前言本文整理了...
- 基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时
-
今天给大家分享的是Vue3聊天实例中的朋友圈的实现及登录验证和倒计时操作。先上效果图这个是最新开发的vue3.x网页端聊天项目中的朋友圈模块。用到了ElementPlus...
- 不来看看这些 VUE 的生命周期钩子函数?| 原力计划
-
作者|huangfuyk责编|王晓曼出品|CSDN博客VUE的生命周期钩子函数:就是指在一个组件从创建到销毁的过程自动执行的函数,包含组件的变化。可以分为:创建、挂载、更新、销毁四个模块...
- Vue3.5正式上线,父传子props用法更丝滑简洁
-
前言Vue3.5在2024-09-03正式上线,目前在Vue官网显最新版本已经是Vue3.5,其中主要包含了几个小改动,我留意到日常最常用的改动就是props了,肯定是用Vue3的人必用的,所以针对性...
- Vue 3 生命周期完整指南(vue生命周期及使用)
-
Vue2和Vue3中的生命周期钩子的工作方式非常相似,我们仍然可以访问相同的钩子,也希望将它们能用于相同的场景。...
- 救命!这 10 个 Vue3 技巧藏太深了!性能翻倍 + 摸鱼神器全揭秘
-
前端打工人集合!是不是经常遇到这些崩溃瞬间:Vue3项目越写越卡,组件通信像走迷宫,复杂逻辑写得脑壳疼?别慌!作为在一线摸爬滚打多年的老前端,今天直接甩出10个超实用的Vue3实战技巧,手把...
- 怎么在 vue 中使用 form 清除校验状态?
-
在Vue中使用表单验证时,经常需要清除表单的校验状态。下面我将介绍一些方法来清除表单的校验状态。1.使用this.$refs...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- Vue 技术栈(全家桶)(vue technology)
- vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)
- vue3 组件初始化流程(vue组件初始化顺序)
- vue3优雅的设置element-plus的table自动滚动到底部
- Vue3为什么推荐使用ref而不是reactive
- 9、echarts 在 vue 中怎么引用?(必会)
- 无所不能,将 Vue 渲染到嵌入式液晶屏
- vue-element-admin 增删改查(五)(vue-element-admin怎么用)
- 最全的 Vue 面试题+详解答案(vue面试题知识点大全)
- 基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)