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

news2024/12/25 12:38:38

文章目录

  • 前言
  • 一、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;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

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

@Configuration
public class ConditionConfig {
    @Bean
    public 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 {
    @Override
    public 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 {
    @Override
    public 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 {
    @Override
    public 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 {

    @Bean
    public 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 {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.lp.domain.User", "com.lp.domain.Role"};
    }
}

在这里插入图片描述

④导入 ImportBeanDefinitionRegistrar 实现类

@Import({MyImportBeanDefinitionRegistrar.class})

示例代码

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public 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.coloradmin.cn/o/114992.html

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

相关文章

Win10启动Pycharm报错

Win10启动Pycharm报错报错信息解决方法报错信息 Internal error. Please report to http://jb.gg/ide/critical-startup-errors java.net.BindException: Address already in use: bind at java.base/sun.nio.ch.Net.bind0(Native Method) at java.base/sun.nio.ch.Net.bind(U…

如何在3个月内写出博士论文

在阅读本文之前&#xff0c;请注意&#xff1a;我花了三年半的时间进行全职研究&#xff0c;为我的博士论文收集数据&#xff1b;这三个月只涉及写作&#xff0c;我在最后很快就完成了。我并不是说每个人都能写得那么快&#xff0c;如果你没有做过研究&#xff0c;那是不可能的…

全国各省368个地级市河流密度数据(工具变量)

数据来源&#xff1a;国家基础地理信息中心 时间跨度&#xff1a;-- 区域范围&#xff1a;全国各省市 指标说明&#xff1a; 根据河流矢量和中国城市行政边界矢量地理信息&#xff0c;计算每个城市河流的总长度&#xff1b;根据各城市的行政区划面积&#xff0c;计算中国各城…

第三章:关系数据库标准语言SQL

一、sql概述和数据定义 1、【单选题】 create user A identified by B default tablespace C temporary tablespace D&#xff1b; 上述oracle数据库查询语句中A、B、C、D分别代表&#xff1a; 正确答案&#xff1a; A 2、【单选题】下表为患者缴费记录&#xff0c;现需…

2023跨年烟花3D最炫烟花,html最酷炫动态烟花源码分享,点击即可直接运行

&#x1f4cb; 前言 &#x1f5b1; 博客主页&#xff1a;在下马农的碎碎念✍ 本文由在下马农原创&#xff0c;首发于CSDN&#x1f4c6; 首发时间&#xff1a;2022/12/25&#x1f4c5; 最近更新时间&#xff1a;2022/12/25&#x1f935; 此马非凡马&#xff0c;房星本是星。向前…

lambda表达式,函数式接口,链式编程,Stream流式计算

新时代的程序员&#xff1a;lambda表达式&#xff0c;函数式接口&#xff0c;链式编程&#xff0c;Stream流式计算 函数式接口 函数式接口&#xff1a;只有一个方法的接口(简化编程模型&#xff0c;在新版本框架底层中大量应用&#xff01;) 只要是 函数型接口 就可以使用lambd…

iOS 16.2 在 SwiftUI 子视图中无法关闭弹出的(sheet)导航视图(NavigationView)之解决

问题现象 iOS 16.2 中,若在 SwiftUI 4.0 里弹出(sheet)一个导航视图 A,则不能在 A 的子视图中将 A 关闭(dismiss): 如上图所示:上面的按钮直接放在导航视图 A 中,点击它可以直接关闭 A;而下面的按钮放在一个子视图中,点击它想要关闭 A 却没有任何反应。 那么,该如…

可转债网格交易策略回测

什么是网格交易策略&#xff1a;基于股票波动高抛低吸策略&#xff0c;自动化反复买卖赚取差价。投资者借助条件单&#xff0c;把资金分成多份&#xff0c;从基准价开始&#xff0c;每跌x%就自动买入一份&#xff0c;每涨y%就自动卖掉一份。股价越波动高抛低吸的机会越多 什么…

Java项目:springboot基于java+mysql+springboot的社区养老医疗综合服务平台

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目分为管理员、医生、病人三种角色&#xff0c; 管理员的功能包含如下&#xff1a; 个人信息&#xff1a;个人资料、修改密码 系统管理&…

Python入门学习之字符串与比较运算符

Python字符串 字符串或串(String)是由数字、字母、下划线组成的一串字符。 一般记为 : 1 s"a1a2an"(n>0) 它是编程语言中表示文本的数据类型。 python的字串列表有2种取值顺序: 从左到右索引默认0开始的&#xff0c;最大范围是字符串长度少1从右到左索引默认-1开…

【C++】各种排序涉及到的选择小题合集(每日小细节009)

昨天更新完各种排序之后今天来检验一下是否真的全部掌握了呢&#xff1f; 今天的合集里面包括各种排序的综合选择题和一些解题技巧 花一两分钟看一下真的很有帮助哦 &#xff08;最好能先认真复习一下各种排序&#xff09; 1. 冒泡排序就是相邻元素的两两比较所以依次写出来就…

Java项目:Springboot体育器材管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 体育器材管理系统主要包含以下功能&#xff1a; 登录注册&#xff1b; 体育器材管理&#xff1a;显示器材表、显示价目表、显示供应商表&#x…

通达信逐笔接口怎么查询下单明细?

通达信逐笔接口一般会根据实盘交易系统的模式来开发&#xff0c;然后股票量化交易接口端也能很快的获取用户数据&#xff0c;也就是你的账户下单的数据信息&#xff0c;在市场交易期间能实时与多家证券公司对接&#xff0c;实现委托买入卖出操作。那么&#xff0c;像这么方便的…

IO流Properties配置文件

目录 输入流和输出流 File文件增删 常用IO流类及其分类 FileInputStream/FileOutputStream ObjectInputStream/ObjectOutputStream BufferedInputStream/BufferedOutputStream FileReader/FileWriter BufferedReader/BufferedWriter InputStreamReader/InputStreamWri…

为啥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 ){…

时序预测 | MATLAB实现IWOA-LSTM和LSTM时间序列预测(改进的鲸鱼算法优化长短期记忆神经网络)

时序预测 | MATLAB实现IWOA-LSTM和LSTM时间序列预测(改进的鲸鱼算法优化长短期记忆神经网络) 目录时序预测 | MATLAB实现IWOA-LSTM和LSTM时间序列预测(改进的鲸鱼算法优化长短期记忆神经网络)预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 MATLAB实现IWOA-LSTM和…

使用React做一个音乐播放器

目录介绍先决条件和设置环境依赖关系播放器.js进口播放和暂停音频使用音频的当前时间和持续时间添加音频时间线输出附加的功能结论介绍 任何正在学习 React 并想使用 React 构建项目的人。有各种博客和文章可以为开发人员指导此类项目。我确实浏览过这些文章&#xff0c;但其中…

Java集合框架【三Map接口、Iterator送代器、Collections工具类】

文章目录双例模式一 Map接口简介1.1 常用方法1.2 演示二 HashMap的存储结构简介三 TreeMap容器类3.1 TreeMap的比较规则3.2 元素自身实现比较规则3.3 通过比较器实现比较规则四 Iterator迭代器4.1 Iterator送代器接口介绍4.2 栗子五 Collections工具类5.1 Collections工具类简介…

关于node代码如何丝滑执行多条命令行这件事

最近写脚本比较多&#xff0c;然后经常写命令行相关的代码&#xff0c;记录一下以备万一。 首先&#xff0c;node使用命令行依赖于child_process&#xff0c;这个是node原生支持的,我用的最多就是exec。 按顺序执行多个命令 因为写脚本需要执行多个语句&#xff0c;所以写了…

[Python图像识别] 五十一.水书图像识别之利用数据增强扩充图像数据集

该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门、OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子、图像增强技术、图像分割等,后期结合深度学习研究图像识别、图像分类应用。希望文章对您有所帮助,如果有不足之处,还请海涵~ 上一篇文章…