SpringBoot——自动装配之@Import

news2024/11/25 13:13:20

文章目录

  • 前言
  • @Import
    • @Import 的作用
      • 1、@Import(MyDemo1.class) 将某个对象加载至bean容器中
      • 2、@Import一个类 该类实现了ImportSelector, 重写selectImports方法该方法返回了String[]数组的对象,数组里面的类都会注入到spring容器当中
      • 3、@Import一个类,该类实现了ImportBeanDefinitionRegistrar接口,在重写的registerBeanDefinitions方法里面,能拿到BeanDefinitionRegistry bd的注册器,能手工往beanDefinitionMap中注册 beanDefinition
  • SpringBoot是如何实现其他框架的热插拔的?
  • 探究 DeferredImportSelector

前言

最近在琢磨为啥spring可以很优雅、很容易地并进其他项目的原理。刚好与springboot的自动装配有点关系,特此记录下。

@Import

说起自动装配,在SpringBoot中,通常会使用一个@Enable....的注解进行加入其他项目源码。比如常见的@EnableAutoConfiguration、还有分布式中的@EnableDiscoveryClient@EnableFeign等等。

其中都有@Import的使用,说到这里,这个注解主要是做什么用的呢?

@Import 的作用

在这里插入图片描述

1、@Import(MyDemo1.class) 将某个对象加载至bean容器中

如下例子所示,创建一个bean类,其中创造一个方法。

public class MyDemo1 {

    public void test() {
        System.out.println("MyDemo1。。。。。");
    }
}

创建一个测试类,注意该测试类可以被spring扫描到。本次使用AnnotationConfigApplicationContext进行验证,如下所示:

import cn.xj.bean1.MyDemo1;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Import;

@Import(MyDemo1.class) // import 注入某个对象
public class Test1 {

    public static void main(String[] args) {
    	// 注解扫描对应的bean 
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Test1.class);
        MyDemo1 bean = applicationContext.getBean(MyDemo1.class);
        // 调用对象的方法
        bean.test();
    }

}

运行这个类,查看控制台打印效果,如下所示:
在这里插入图片描述

2、@Import一个类 该类实现了ImportSelector, 重写selectImports方法该方法返回了String[]数组的对象,数组里面的类都会注入到spring容器当中

还是上面的那个MyDemo1 ,本次换一个方法进行bean的注入

实现 ImportSelector 并重写 selectImports

如下所示:

import cn.xj.bean1.MyDemo1;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyDemoConfig2 implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MyDemo1.class.getName()};
    }
}

创建一个配置类,将这个实现 ImportSelector 并重写 selectImports的类进行载入。

import cn.xj.bean1.MyDemo1;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Import;

@Import(MyDemoConfig2.class) // 导入的是一个实现 ImportSelector 并重写 selectImports 的类
public class MyDemoConfigTest2 {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = 
        		new AnnotationConfigApplicationContext(MyDemoConfigTest2.class);
        MyDemo1 bean = applicationContext.getBean(MyDemo1.class);
        bean.test();
    }
}

运行这个配置类,查看控制台日志打印,如下所示:
在这里插入图片描述

3、@Import一个类,该类实现了ImportBeanDefinitionRegistrar接口,在重写的registerBeanDefinitions方法里面,能拿到BeanDefinitionRegistry bd的注册器,能手工往beanDefinitionMap中注册 beanDefinition

在源码中,针对@ImportSelector的说明如下
在这里插入图片描述
依旧还是上面这个MyDemo1类,这次配置类用@ImportSelector的变种ImportBeanDefinitionRegistrar,代码如下所示:

import cn.xj.bean1.MyDemo1;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyDemoConfig3 implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition bd = new RootBeanDefinition();
        bd.setBeanClass(MyDemo1.class);
        registry.registerBeanDefinition("MyDemo1", bd); // 对这个bean起一个别名
    }
}

然后再将这个类,采取@Import的方式进行载入。如下所示:

import cn.xj.bean1.MyDemo1;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Import;

@Import(MyDemoConfig3.class)
public class MyDemoConfigTest3 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = 
        		new AnnotationConfigApplicationContext(MyDemoConfigTest3.class);
        MyDemo1 bean = (MyDemo1) applicationContext.getBean("MyDemo1"); // 因为是别名注入,所以采取别名获取bean
        bean.test();
    }
}

执行后,控制台输出如下日志信息:
在这里插入图片描述

SpringBoot是如何实现其他框架的热插拔的?

在spring的源码中,对于类创建<bean>实例于容器中,采取的是IOC控制反转的思想,其主要的逻辑流程如下所示:
在这里插入图片描述
spring在启动时,会加载xml配置文件config 类注解扫描import注解等方式进行bean的扫描、创建和载入容器。假设现在有一个另外的框架,springboot能够很简单的将其并入其中,并进行其他框架bean的加载项,配置项的加载项等。如:分布式微服务模块嵌入feign。

在本篇文章@Import的描述中,就说到一个东西。

@Enable....实现其他框架的载入。

其原理也很简单,就拿@SpringBootApplication注解来说,springboot在启动的时候,就会去识别启动类上的各项注解信息,其中会有一个@EnableAutoConfiguration的注解,点击进入后,可以发现其实是一个实现 DeferredImportSelector 接口的 类

上文就说到@Import可以将一个类加载至spring容器中

观察AutoConfigurationImportSelector类,可以发现,他的核心代码就是重写了DeferredImportSelector中的两个方法,分别是selectImportsgetImportGroup

DeferredImportSelectorImportSelector 的子类。
相对ImportSelector做了额外的扩展,最主要的就是getImportGroup()组的概念。

【疑问】这两个方法的作用是什么?

回答这个问题之前,首先需要需要知道ImportSelector提供的方式为什么不可行。

在上面的验证中,一个类实现ImportSelector并重写selectImports后,在其中返回需要加载的类的全路径数组信息。的确可以进行一个bean的创建与注入容器中。但是紧接着有一个更严重的问题产生。比如下面这种情况:

springboot-jdbc中,默认就会有一个数据库的连接池配置。
但是在实际开发中,通常的配置项采取的是自定义的配置。
如果使用的是ImportSelector进行类的加载与创建,项目框架怎么选择自定义的连接池还是默认的连接池作为首要的配置呢?

【扩充】知识点:

框架本身就有一个默认的配置类,如果开发者进行了自定义的配置,那么在项目中使用的是自定义的配置项,默认的并未进行加载。

【扩充】知识点:

也可能有人会说,在spring的注解中,有一个@ConditionalOnClass的注解,也能实现类似的功能。
如:@ConditionalOnBean(MyDemo2.class)表示如果存在一个MyDemo2的bean,则不会加载@ConditionalOnBean修饰的bean

但是针对这种问题,如果spring依旧还是使用ImportSelector接口的实现类进行大批量的指定bean的加载操作,则可能出现两个bean同时加载的问题。

spring针对bean的加载顺序,并不能保证。
如果先加载@ConditionalOnBean 修饰的bean ,会判断无指定的bean,则会进行创建;
导致后执行的bean也被加载进入容器。

此时,就需要使用到一个ImportSelector的变种配置类DeferredImportSelector。追加了一个分组的概念。

探究 DeferredImportSelector

编写一个测试类,该类实现DeferredImportSelector并重写selectImportsgetImportGroup方法,如下所示:

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.ArrayList;
import java.util.List;

/**
 * spring在解析 bean定义 时,会通过该方法判断是否 getImportGroup() 有返回值
 */
public class MyDeferredImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"cn.xj.bean2.MyDemo1"};
    }

    /**
     * 如果返回的是null,则会直接调用 cn.xj.test.MyDeferredImportSelector#selectImports 加载对应的bean;
     *
     * 如果返回的是一个自定义的 group 对象,则会加载自定义对象中的 selectImports 进行加载
     * @return
     */
    @Override
    public Class<? extends Group> getImportGroup() {
        return MyGroup.class;
    }

    /**
     * 自定义 group
     */
    private static class MyGroup implements DeferredImportSelector.Group{

        AnnotationMetadata metadata;

        @Override
        public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
            this.metadata = metadata;
        }

        @Override
        public Iterable<Entry> selectImports() {
            List<Entry> lists = new ArrayList<>();
            lists.add(new Entry(this.metadata,"cn.xj.bean2.MyDemo2"));
            return lists;
        }

    }
}

创建一个测试类,使用@Import进行类的扫描与注入。逻辑如下所示:

import cn.xj.bean1.MyDemo1;
import cn.xj.bean2.MyDemo2;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Import;

@Import(MyDeferredImportSelector.class)
public class MyDeferredImportSelectorTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(MyDeferredImportSelectorTest.class);
        MyDemo2 bean2 = applicationContext.getBean(MyDemo2.class);
        bean2.test();

        // 异常测试  看拿不拿得到 MyDemo1 的实例
        MyDemo1 bean1 = applicationContext.getBean(MyDemo1.class);
        bean1.test();
    }
}

执行后,观察控制台打印效果。
在这里插入图片描述
从日志中很清楚的可以看到:

如果重写了getImportGroup(),并返回了一个自定义的 Group 类
在spring解析bean定义类时,会先进行getImportGroup的返回值判断。
如果返回的是一个null,则直接将cn.xj.test.MyDeferredImportSelector#selectImports的返回值进行解析。
如果返回的事一个自定义的group,则会直接去cn.xj.test.MyDeferredImportSelector.MyGroup#selectImports进行bean的解析。

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

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

相关文章

Premiere Pro 2023(23.5) Mac

Premiere Pro是一个业界领先的视频编辑软件&#xff0c;旨在帮助用户快速、高效地制作和编辑专业质量的视频内容。作为Creative Cloud套装中的一部分&#xff0c;Premiere Pro具有丰富的功能和工具&#xff0c;可应对各种视频编辑需求。 Premiere Pro的主要特点包括多个视频和音…

Unity游戏源码分享-Unity版本的经典斗地主游戏完整源码

Unity游戏源码分享-Unity版本的经典斗地主游戏完整源码 工程地址&#xff1a; https://download.csdn.net/download/Highning0007/88057828

css+js实现点击特效效果

话不多说&#xff0c;先上效果图 实现代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title…

详细进行wrnchAI 与OpenPose 的姿势检测估计系统的各项性能比较

本文将对wrnchAI 与OpenPose的性能进行比较。我们评估了人体姿势估计系统,并通过比较 wrnchAI 与 OpenPose 的以下参数来报告我们的发现: 准确性运算速度系统要求型号尺寸其他特性 移动支持跟踪支持。绿屏分割。支持应用程序开发。许可易于设置和使用披露:我们从 wrnch 收…

Java - 集合

开篇 数组和集合对比 数组的不足: 1、长度开始时必须指定&#xff0c;而且一旦指定&#xff0c;不能修改&#xff0c;灵活性不够 2、保存的必须为同一类型的元素 3、使用数组进行增删元素的方法比较麻烦 集合 1、可以动态的保存任意多个对象 2、提供一系列操作对象的方法…

基于ResNet50算法实现图像分类识别

概要 在本文中将介绍使用Python语言&#xff0c;基于TensorFlow搭建ResNet50卷积神经网络对四种动物图像数据集进行训练&#xff0c;观察其模型训练效果。 一、目录 ResNet50介绍 图片模型训练预测 项目扩展 二、ResNet50介绍 ResNet50是一种基于深度卷积神经网络&#xff…

智迪科技在创业板上市:市值约31亿元,谢伟明和黎柏松为实控人

7月17日&#xff0c;珠海市智迪科技股份有限公司&#xff08;下称“智迪科技”&#xff0c;SZ:301503&#xff09;在深圳证券交易所创业板上市。本次上市&#xff0c;智迪科技的发行价为31.59元/股&#xff0c;发行数量为2000万股&#xff0c;募资总额约为6.32亿元&#xff0c;…

多线程、高并发(理解一)

线程的简单了解 进程、线程、纤程创建线程的几种方式synchronizedvolatileAtomicXJUC 同步锁ReentrantLockCountDownLatch 倒数的门栓CyclicBarrier 复杂操作完成&#xff0c;再走后续逻辑Phaser 所有线程需要在屏障上等待才能进入下一步执行ReadWriteLockSemaphoreExchanger 线…

MQTT协议在物联网环境中的应用及代码实现解析(三)

三、使用C#PC端编程读取MQTT服务器上特定的主题信息 以下是使用C#编程利用MQTT协议接收MQTT服务器“mqtt.laobai.net”上的“laobai_topic001”主题上的订阅信息&#xff0c;并动态显示在多行文本框中的程序代码示例&#xff0c;包括完整的代码注释和编程说明&#xff1a; us…

javascript中过滤二维对象数组重复的字段并只保留唯一值(array.filter与Array.from)

javascript中过滤二维对象数组重复的字段并只保留唯一值 1.Array.filter过滤array.filter()方法 2.Array.from过滤Array.from方法 1.Array.filter过滤 在JavaScript中&#xff0c;你可以使用Array.filter()和Set数据结构来过滤二维对象数组中重复的字段&#xff0c;只保留唯一…

CSS三栏布局的几种自适应的排版方式

CSS三栏布局的几种方式&#xff0c;主要有 float、position、flex实现。 对红色、蓝色 box 设置 float&#xff0c;绿色不设。 <style>* {margin: 0;padding: 0;}.left {width: 200px;height: 300px;background-color: red;float:left;}.right {width: 200px;height: 300…

计算机图形硬件(二) 5 - 4 硬拷贝设备

我们可以使用几种格式来获得图像的硬拷贝输出。为了演示或存档&#xff0c;可以将图像文件传送到生成投影胶片、35 mm幻灯片或电影胶片的设备与服务部门。也可将图形输出到打印机或绘图仪上&#xff0c;从而在纸上打印出图像。 输出设备生成图片的质量依赖于可显示的点的大小和…

浅尝python 一

浅尝python一 一、注释二、指定数值精度三、算数运算符四、字符串格式化输出五、打印六、类型转换&#xff0c;不会改变原数据七、比较运算符综合练习 一、注释 1、单行注释&#xff0c;快捷键 command/ # 此行被注释掉 2、多行注释 本行被注释 本行被注释or""&quo…

react报错信息

报错信息 render函数里dom不能直接展示obj对象 取变量记得要有{} https://segmentfault.com/q/1010000009619339 这样在写的时候就已经执行方法了&#xff0c;所以此处用箭头函数&#xff08;&#xff09;》{}才会在点击时执行或者 遍历数据使用map来遍历&#xff0c;使用forea…

matplotlib用面积填充实现lmplot

文章目录 示例参数回归图 示例 在绘图时经常遇到类似区域填充的问题&#xff0c;比如对于 y sin ⁡ x y\sin x ysinx函数&#xff0c;想填充其与X轴所围成的区间&#xff0c;那么就可以使用fill_between函数。 import numpy as np import matplotlib.pyplot as pltx np.ara…

Java开发基础系列(八):泛型及反射

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; Java开发基础系列(八):泛型及反射 ⏱️ 创作时间&#xff1a; 2023年0…

ETLBox for .Net Crack

ETLBox for .Net Crack 为设计的轻量级ETL(提取转换负载)工具箱和数据集成库。NET。 ETL是现代商业智能应用程序的基础&#xff0c;是将分析与之前的所有其他组件分离的唯一方法。ETL是提取-加载、转换和转换的缩写&#xff0c;描述了一个由三个步骤组成的过程&#xff1a; 提取…

学生成绩管理系统|Python小应用练习

题目要求 实现学生成绩管理系统 输入学生成绩信息序列&#xff0c;获得成绩从高到低、从低到高、按某一门成绩的排列,相同成绩都按先录入排列在前的规则处理。 数据如下&#xff1a;(数据规则&#xff1a;学生姓名 高数成绩 英语成绩 大物成绩) SanZhang 70 80 61 SiLi 86 77 …

Java的五种数据类型解析

Java的五种数据类型解析 不知道大家对java的简单数据类型是否了解&#xff0c;下面针对Java的五种类型简单数据类型表示数字和字符&#xff0c;进行详细的讲解和分析。 一、简单数据类型初始化 在Java语言中&#xff0c;简单数据类型作为类的成员变量声明时自动初始化为默认值…

IDEA远程调试SpringBoot项目

IDEA远程调试SpringBoot项目 一、前提二、脚本三、运行远程项目四、本地配置五、使用场景六、扩展点 一、前提 保证线上的jar代码和本地代码一样 二、脚本 nohup java -Xdebug -Xnoagent -Djava.compilerNONE Xrunjdwp:transportdt_socket,servery,suspendn,address5001 -ja…