SpringBoot系列之自动装配原理详解

news/2024/4/28 2:54:24/文章来源:https://blog.csdn.net/hsuehgw/article/details/128435562

文章目录

  • 前言
  • 一、SpringBoot自动配置-Condition-1
    • 1、观察spring自动创建bean过程
    • 2、创建自定义bean对象
    • 3、根据条件创建自定义bean
  • 二、 SpringBoot自动配置-Condition-2
  • 三、SpringBoot自动配置-切换内置web服务器
    • 1、查看继承关系图
    • 2、shift+delete 排除Tomcat
  • 四、SpringBoot自动配置-Enable注解原理
  • 五、SpringBoot自动配置-@Import详解
    • ①导入Bean
    • ②导入配置类
    • ③导入 ImportSelector 实现类
    • ④导入 ImportBeanDefinitionRegistrar 实现类
  • 六、SpringBoot自动配置-@EnableAutoConfiguration详解
  • 总结


前言

Springboot目前是Java开发中最主流的框,因此在我们的工作和面试中都会经常用到,SpringBoot主要解决了传统spring的重量级xml配置Bean,实现了自动装配。接下来将从注解已经源码来学习SpringBoot自动装配原理。


一、SpringBoot自动配置-Condition-1

Condition是Spring4.0后引入的条件化配置接口,通过实现Condition接口可以完成有条件的加载相应的Bean。
@Conditional要配和Condition的实现类(ClassCondition)进行使用

接下来我们以下面例子来学习:
• 创建模块 springboot-condition

1、观察spring自动创建bean过程

改造启动类如下:

@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootConditionApplication.class, args);System.out.println(run);//只要引入Redis起步依赖,就有了RedisTemplate对象Object redisTemplate = run.getBean("redisTemplate");System.out.println(redisTemplate);}

启动:获取不到对象
在这里插入图片描述导入 redis起步依赖

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

再启动则可以获取到bean对象。
在这里插入图片描述

2、创建自定义bean对象

①新建user实体类com.lp.springbootcondition.pojo.User

package com.lp.springbootcondition.pojo;
public class User {int id;String name;int age;public User() {}public User(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';}
}

②新建配置类com.lp.springbootcondition.config.ConditionConfig

@Configuration
public class ConditionConfig {@Beanpublic User user() {return new User();}
}

③启动类获取。测试可以获取到

@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootConditionApplication.class, args);System.out.println(run);//只要引入Redis起步依赖,就有了RedisTemplate对象
//        Object redisTemplate = run.getBean("redisTemplate");
//        System.out.println(redisTemplate);User user = (User) run.getBean("user");System.out.println(user);}
}

在这里插入图片描述

3、根据条件创建自定义bean

创建ClassCondition 类com.lp.springbootcondition.condition.ClassCondition

public class ClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return false;
}
}

改造userConfig

@Configuration
public class ConditionConfig {@Bean@Conditional(ClassCondition.class)public User user(){return new User();}
}

启动起启动类,测试不能自动创建user这个bean
在这里插入图片描述

1、改造ClassCondition。根据是否导入redis来决定是否创建userBean

public class ClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {try {Class.forName("redis.clients.jedis.Jedis");return true;} catch (ClassNotFoundException e) {e.printStackTrace();return false;}}
}

测试。获取不到userBean

@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootConditionApplication.class, args);System.out.println(run);//只要引入Redis起步依赖,就有了RedisTemplate对象
//        Object redisTemplate = run.getBean("redisTemplate");
//        System.out.println(redisTemplate);User user = (User) run.getBean("user");System.out.println(user);}
}

在这里插入图片描述导入Redis依赖,再测试

<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version>
</dependency>

可以获取到userBean
在这里插入图片描述

二、 SpringBoot自动配置-Condition-2

需求:将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定。
1、创建自定义条件注解类ConditionClass

@Target(ElementType.TYPE) //可以用在哪些地方
@Documented//生成javadoc
@Retention(RetentionPolicy.RUNTIME) //运行时起作用
@Conditional(ClassCondition.class)
public @interface ConditionClass {String[] value();
}

2、改造ClassCondition类

public class ClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {try {//必须引入动态传来的包名,才生成对象Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes("com.lp.springbootcondition.condition.ConditionClass");System.out.println(annotationAttributes);if (annotationAttributes != null) {String[] values = (String[]) annotationAttributes.get("value");for (String value : values) {Class.forName(value);}}return true;} catch (ClassNotFoundException e) {e.printStackTrace();return false;}}
}

3、改造ConditionConfig
注意: 此处@ConditionOnClass为自定义注解

@Configuration
public class ConditionConfig {
//    @Bean
//    public User user() {
//        return new User();
//    }@Bean
//    @Conditional(ClassCondition.class)@ConditionOnClass({"redis.clients.jedis.Jedis"})public User user(){return new User();}

4、测试User对象的创建

@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootConditionApplication.class, args);System.out.println(run);//只要引入Redis起步依赖,就有了RedisTemplate对象
//        Object redisTemplate = run.getBean("redisTemplate");
//        System.out.println(redisTemplate);User user = (User) run.getBean("user");System.out.println(user);}
}

在这里插入图片描述查看Springboot条件注解源码
在这里插入图片描述我们会发现Springboot都已经帮我们写好了
SpringBoot 提供的常用条件注解:

ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean
ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean

三、SpringBoot自动配置-切换内置web服务器

如果我们需要切换内置web服务器可以按一下操作进行切换

1、查看继承关系图

在这里插入图片描述

2、shift+delete 排除Tomcat

在这里插入图片描述
pom文件中的排除依赖效果

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!--排除tomcat依赖--><exclusions><exclusion><artifactId>spring-boot-starter-tomcat</artifactId><groupId>org.springframework.boot</groupId></exclusion></exclusions></dependency><!--引入jetty的依赖-->
<dependency><artifactId>spring-boot-starter-jetty</artifactId><groupId>org.springframework.boot</groupId>
</dependency>

根据上面的例子,我们能够知道 为什么引入了starter-data-redis起步依赖,我们就能在项目中,直接拿redistemplate?
因为在springboot中的autoconfigure工程里把常用的对象的配置类都有了,只要工程中,引入了相关起步依赖,这些对象在我们本项目的容器中就有了。
在这里插入图片描述

四、SpringBoot自动配置-Enable注解原理

重要:SpringBootApplication 由三个注解组成
在这里插入图片描述

@SpringBootConfiguration 自动配置相关
@EnableAutoConfiguration
@ComponentScan 扫本包及子包

SpringBoot不能直接获取在其他工程中定义的Bean。
springboot-enable工程,编写主启动类

代码如下(示例):

/*** @ComponentScan 扫描范围:当前引导类所在包及其子包** //1.使用@ComponentScan扫描com.lp.springbootenableother.config包* //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器* //3.可以对Import注解进行封装。**/
package com.lp.springbootenable;import com.lp.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnableApplication.class, args);User user = (User) run.getBean("user");System.out.println(user);}
}

pom中引入springboot-enable-other

<dependency><groupId>com.lp</groupId><artifactId>springboot-enable-other</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>

新建springboot-enable-other工程
在这里插入图片描述编写User

public class User {}

编写UserConfig

@Configuration
public class UserConfig {@Beanpublic User user() {return new User();}
}

启动主启动类,确实,本工程中没有这个第三方jar包中的bean对象
在这里插入图片描述原因:@ComponentScan 扫描范围:当前引导类所在包及其子包
三种解决方案:
1.使用@ComponentScan扫描lp.config包
2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
3.可以对Import注解进行封装。


@SpringBootApplication
//@ComponentScan("com.lp.springbootenableother.config")
//@Import(UserConfig.class)
@EnableUser
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnableApplication.class, args);User user = (User) run.getBean("user");System.out.println(user);}
}

编写EnableUser注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}

启动,能够获取
![在这里插入图片描述](https://img-blog.csdnimg.cn/249b1a85258747c597aa1e8ebe81dad4.png

重点:Enable注解底层原理是使用@Import注解实现Bean的动态加载

五、SpringBoot自动配置-@Import详解

@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。

而@Import提供4中用法:

①导入Bean

。注意bean名字是全限定名(@Import(UserConfig.class))。

示例代码

@Import(User.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnableApplication.class, args);User user = (User) run.getBean("user");System.out.println(user);}
}

②导入配置类

示例代码

@Import(UserConfig.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnableApplication.class, args);User user = (User) run.getBean("user");System.out.println(user);}
}

③导入 ImportSelector 实现类

一般用于加载配置文件中的类

示例代码

public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{"com.lp.domain.User", "com.lp.domain.Role"};}
}

在这里插入图片描述

④导入 ImportBeanDefinitionRegistrar 实现类

@Import({MyImportBeanDefinitionRegistrar.class})

示例代码

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();registry.registerBeanDefinition("user", beanDefinition);}
}

在这里插入图片描述SpringbootEnableApplication测试代码

   Import4中用法:*  1. 导入Bean
*  2. 导入配置类
*  3. 导入ImportSelector的实现类。
*  4. 导入ImportBeanDefinitionRegistrar实现类*/
@SpringBootApplication
//@ComponentScan("com.lp.springbootenableother.config")
//@Import(User.class)
//@EnableUser
//@Import(MyImportSelector.class)
//@Import({MyImportBeanDefinitionRegistrar.class})
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnableApplication.class, args);
//        User user = (User) run.getBean("user");
//        System.out.println(user);Map<String, User> beansOfType = run.getBeansOfType(User.class);System.out.println(beansOfType);//        Jedis jedis = (Jedis) run.getBean("jedis");
//        System.out.println(jedis);
//        jedis.set("hello", "world");
//        String hello = jedis.get("hello");
//        System.out.println(hello);}
}

@EnableAutoConfiguration中使用的是第三种方式:@Import(AutoConfigurationImportSelector.class)

六、SpringBoot自动配置-@EnableAutoConfiguration详解

在这里插入图片描述

  • @EnableAutoConfiguration 注解内部使用
    @Import(AutoConfigurationImportSelector.class)来加载配置类。
  • 配置文件位置:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,该配置文件中定义了大量的配置类,当 SpringBoot应用启动时,会自动加载这些配置类,初始化Bean
  • 并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean

总结

SpringBoot中的主启动类@SpringBootApplication由三个注解组成,分别是@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan其中

@SpringBootConfiguration表示启动类为配置类;

@ComponentScan实现启动时扫描启动类所在的包以及子包下所有标记为bean的类由IOC容器注册为bean。

@EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)注解来加载AutoConfigurationImportSelector类,然后通过AutoConfigurationImportSelector中的selectImports方法去读取spring-boot-autoconfigure 下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的全类名,并根据一定规则过滤掉不符合的全类名,然后将剩余读取到的类全名集合返回给IOC容器并将这些组件注册为bean。

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

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

相关文章

12.20工作学习记录 力扣 罗马文转数字

每日一题:罗马文转数字 定义两个指针 不断后移 每一次让前一个指针的值累加为sum 最后返回sum 力扣https://leetcode.cn/problems/roman-to-integer/ 最长公共前缀 主要是subString方法 力扣https://leetcode.cn/problems/longest-common-prefix/solutions/现在分词与形容…

圣诞的荒诞小故事并记录互联网协议-五层模型

今天敲代码敲着敲着灵光乍现&#xff0c;突然一个荒诞的故事&#x1f4a1;映入脑海。 1.未来和过去&#xff1a; 人高度发达&#xff08;以下称之为渡&#xff09; 渡可以打开时空穿越过去&#xff08;以下称之为旧迹&#xff09;&#xff0c;并且可以进随心所欲的来去自如&a…

基于yolov5s实践国际象棋目标检测模型开发

在我前面的一篇文章中讲解实现了基于改进的yolov5s-spd模型实现了五子棋目标对象检测模型系统的设计开发&#xff0c;这里紧接前文&#xff0c;突发奇想&#xff0c;是否可以借鉴同样的思路实现象棋的检测模型开发呢&#xff1f;理论上面肯定是可以的&#xff0c;但是实际效果如…

详细介绍关于自定义类型:结构体、枚举、联合【c语言】

文章目录结构体结构体的声名特殊的声明结构成员的类型结构的自引用结构体变量的定义和初始化结构体内存对齐修改默认对齐数结构体变量访问成员结构体传参结构体实现位段&#xff08;位段的填充&可移植性&#xff09;位段的内存分配位段的跨平台问题枚举枚举类型的定义枚举的…

微信小程序入门

目录 一&#xff0c;简介 二&#xff0c;小程序开发环境搭建 1.申请账号 2.安装开发工具 3.小程序工具使用 三&#xff0c;目录结构以及json配置 1.目录结果 2.json配置 3.JSON 语法 4.WXML 5.wxss 6.JS 逻辑交互 四&#xff0c;小程序宿主环境 1.程序与页面 2.组件…

String 字符串

String 基本介绍 String 应该是 Java 中最常用的一个对象&#xff0c;他不是八种基本数据类型的其中之一&#xff0c;但是随便翻了一下项目代码&#xff0c;用 String 定义的变量超过百分之八十。 public final class Stringimplements java.io.Serializable, Comparable<…

自己整理的vue实现生成分享海报(含二维码),看着网上的没实现

大家好&#xff0c;我是雄雄。 前言 相信大家在许多的场景下&#xff0c;看到过这样的案例。 当我们在某购物app上看好一件商品&#xff0c;想分享给别人时&#xff0c;app会给我们生成一张海报&#xff0c;我们将其保存在手机里面转发给其他人达到分享。当我们逛CSDN的时候&…

12.25日周报

周报 代码行数&#xff1a; 周一 704 周二 481 周三 571 周四 589 周五 595 周六 520 周日 537 遇到的问题&#xff1a; 没用过的方法AtomicInteger Insert Proto currentTimeMillis RequestParam BufferedReader UriComponents RestTemplate OSS 不清楚在…

Windows和Mac系统实现本地部署WebPageTest工具

在项目开发或者测试的过程中&#xff0c;由于没有上线&#xff0c;我们在公网上无法访问我们的网站&#xff0c;但同时我们又需要查看浏览器性能&#xff0c;这样我们就需要在本地部署WebPageTest工具以协助进行性能测试 具体实现步骤&#xff1a; Windows系统&#xff1a; …

【高级篇04】MySQL逻辑架构

文章目录第四章&#xff1a;逻辑架构逻辑架构SQL执行流程数据库缓冲池第四章&#xff1a;逻辑架构 逻辑架构 第一层&#xff1a;连接层。客户端访问MySQL服务器&#xff0c;首先建立TCP连接&#xff0c;经过三次握手建立连接成功后&#xff0c;MySQL服务器对TCP传输过来的账号…

PHP开发工具PhpStorm v2022.3——完全支持PHP 8.2

PhpStorm是一个轻量级且便捷的PHP IDE&#xff0c;其旨在提高用户效率&#xff0c;可深刻理解用户的编码&#xff0c;提供智能代码补全&#xff0c;快速导航以及即时错误检查。可随时帮助用户对其编码进行调整&#xff0c;运行单元测试或者提供可视化debug功能。 PhpStorm v20…

excel数据处理技巧:组合函数统计产品批号

这是一个看似普通的编号问题&#xff0c;可竟然动用了TEXT和SUMPRODUCT两个重量级的函数共同出手才得以解决。以往遇到的编号问题&#xff0c;大多数都是COUNTIF的拿手好戏&#xff0c;但是今天这个问题COUNTIF完全插不上手&#xff0c;来看看模拟的数据吧。 如图所示&#xff…

循环神经网络-基础篇Basic-RNN

循环神经网络-基础篇Basic-RNN 我们把全连接网络也叫做稠密网络DNN&#xff0c;其中X1到X8是不同样本的特征 而本文介绍的循环神经网络RNN主要处理的是具有序列关系的输入数据&#xff0c;即前面的输入和后面的输入是有关系的。例如天气&#xff0c;股市&#xff0c;金融数据和…

ES6 模块化、webpack、@ 代表src目录的设置

文章目录webpackSource Map 代表src目录的设置ES6 模块化要求默认 导出默认导入按需 导出、导入混合使用直接导入 并执行模块中的代码webpack 默认 约定&#xff1a; 自定义 打包的 入口与出口 const path require(path) // 导入node.js中 专门操作路径的模块 module.expor…

哺乳时宝宝一边吃奶,另一边却自动流出来,这是怎么回事?

别人眼中的母乳喂养只是简单地把宝宝抱在怀里&#xff0c;让宝宝吃饱&#xff0c;超级简单。事实上&#xff0c;有很多母乳喂养。“麻烦事”比如母乳不足、堵奶、乳腺炎等&#xff0c;甚至更多“简单”漏奶会让宝宝头疼。有些妈妈很幸运&#xff0c;不知道什么是漏奶&#xff0…

基于Java+SpringBoot+vue等疫情期间网课管理系统详细设计和实现

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取联系&#x1f345;精彩专栏推荐订阅收藏&#x1f447;&…

Git Bash Here和RStudio软件的问题解决

Git Bash Here和RStudio软件的问题解决 文章目录Git Bash Here和RStudio软件的问题解决0、 写在前面1、Git软件在任务栏图标空白2、RStudio软件2.1 警告信息InormalizePath(path.expand(path),winslash,mustWork)2.2 incomplete final line found by readTableHeader on报错3、…

为啥devc++程序运行正确返回不为0?而返回了一个特别大的数,详解。

例如运行以下程序: #include #include typedef char ElemType; typedef struct BiTNode{ char data; struct BiTNode *lchild; struct BiTNode *rchild; int DescNum;}BiTNode ,*BiTree; void CreateBiTree(BiTree *T) { char ch; scanf("%c",&ch); if(ch ){…

力扣(LeetCode)207. 课程表(C++)

拓扑排序 根据示例看出&#xff0c;课程表是否存在环&#xff0c;是问题的关键。这题的环&#xff0c;和数组、链表的环不一样&#xff0c;不好判&#xff0c;要转化成图判拓扑序列。 考虑向右和向左的方向&#xff0c;拓扑序列的所有边可以指向同一方向。 无环图进行重排序…

第一章:绪论

一、数据库系统概述 1、【单选题】记录内有结构&#xff0c;整体无结构&#xff0c;属于计算机发展过程的哪一阶段 正确答案&#xff1a; C 2、【单选题】数据库系统最小访问单位是 正确答案&#xff1a; C 3、【多选题】数据库管理系统提供的数据控制功能包括 正确答案&…