Spring之@Import注解

news2024/11/13 9:16:39

1. 前言

@Import 注解 在 Spring 中占据重要地位,是 Spring 的一个重要扩展点。这篇博文我们以案例、源码、应用相结合,来系统的学习一下这个注解

2. 案例演示

2.1 代码准备

2.1.1 创建配置类 AppConfig
@ComponentScan("com.ys")
public class AppConfig {
}
2.1.2 创建启动类 Main
public class Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    }
}

2.2 导入一个普通类

2.2.1 创建实体类 Dog
public class Dog {
}
2.2.2 修改配置类 AppConfig
@ComponentScan("com.ys")
@Import(Dog.class)
public class AppConfig {
}
2.2.3 运行 main 方法,查看运行结果

2.2.4 小结

导入一个普通类,相当于导入一个指定类型的 bean,需要注意的是 @Import 导入的普通类的 beanName 是类的全限定名

2.3 导入一个配置类

2.3.1 创建实体类 User
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private String name;

    private Integer age;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
2.3.2 创建配置类 UserConfig
@Configuration(proxyBeanMethods = false)
public class UserConfig {

    @Bean
    public User user() {
        return new User("anna", 18);
    }
}
2.3.3 修改配置类 AppConfig
@ComponentScan("com.ys.entity")
@Import(UserConfig.class)
public class AppConfig {
}

 PS:@ComponentScan 注解不扫描 UserConfig 所在包路径

2.3.4 运行 main 方法,查看运行结果

2.3.5 小结

导入一个配置类,它就会以配置类进行解析,会处理 @Bean、@ComponentScan 等注解

2.4 导入一个实现 ImportBeanDefinitionRegistrar 接口的类

2.4.1 创建实体类 Cat
public class Cat {
}
2.4.2 创建实体类 CatRegistrar
public class CatRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName("com.ys.entity.Cat");
        registry.registerBeanDefinition("cat", beanDefinition);
    }
}
2.4.3 修改配置类 AppConfig
@ComponentScan("com.ys")
@Import(CatRegistrar.class)
public class AppConfig {
}
2.4.4 运行 main 方法,查看运行结果
2.4.5 小结

导入一个实现 ImportBeanDefinitionRegistrar 接口的类,会在解析过程中执行其 registerBeanDefinitions 方法,一般会向 beanFactory 中注册 beanDefinition。beanDefinition是 bean 的建模对象,后期会根据 beanDefinition 的相关属性,按照指定方式实例化 bean

2.5 导入一个实现 ImportSelector 接口的类

2.5.1 创建实体类 RedCar BlackCar
public class RedCar {
}

public class BlackCar {
}
2.5.2 创建实体类 CarImportSelector
public class CarImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.ys.entity.RedCar", "com.ys.entity.BlackCar"};
    }
}
2.5.3 修改配置类 AppConfig
@ComponentScan("com.ys")
@Import(CarImportSelector.class)
public class AppConfig {
}
2.5.4 运行 main 方法,查看运行结果

2.5.5 小结

如果导入的类实现 ImportSelector 接口,则会在解析阶段执行 selectImports 方法,返回值是类的全限定名,可能有以下几种情况:

  • 类是一个普通类:按照案例 2.2 处理
  • 类是一个配置类:按照案例 2.3 处理
  • 类实现 ImportBeanDefinitionRegistrar 接口:按照案例 2.4 处理
  • 类实现 ImportSelector 接口:循环处理上述三中情况

因为 ImportSelector 的特性,可能会产生一些特殊情况,我们在下文中举例演示

2.6 ImportSelector 接口实现类的 selectImports 方法的返回值也是 ImportSelector 接口实现类

2.6.1 创建实体类 Monkey
public class Monkey {
}
2.6.2 创建实体类 FirstImportSelector SecondImportSelector
public class FirstImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.ys.entity.SecondImportSelector"};
    }
}

public class SecondImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.ys.entity.Monkey"};
    }
}
2.6.3 修改配置类 AppConfig
@ComponentScan("com.ys")
@Import(FirstImportSelector.class)
public class AppConfig {
}
2.6.4 运行 main 方法,查看运行结果

2.6.5 小结

如果 selectImports 方法的返回值也是 ImportSelector 接口实现类,则会继续处理,所以不存在该类型的 bean(本案例中指 SecondImportSelector

2.7 @Import 导入的类同时实现 ImportSelector、ImportBeanDefinitionRegistrar 接口

2.7.1 创建实体类 SelectorBean RegistrarBean 
public class SelectorBean {
}

public class RegistrarBean {
}
2.7.2 创建实体类 MergeImports
public class MergeImports implements ImportSelector, ImportBeanDefinitionRegistrar {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.ys.entity.SelectorBean"};
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName("com.ys.entity.RegistrarBean");
        registry.registerBeanDefinition("registrarBean", beanDefinition);
    }
}
2.7.3 修改配置类 AppConfig
@ComponentScan("com.ys")
@Import(MergeImports.class)
public class AppConfig {
}
2.7.4 运行 main 方法,查看运行结果

PS :  ImportSelector 方式的 beanName 为类的全限定名, ImportBeanDefinitionRegistrar 方式 的beanName 为手动设置的值

2.7.5 小结

ImportSelector 的优先级高于 ImportBeanDefinitionRegistrar,如果同时实现 ImportSelector、ImportBeanDefinitionRegistrar 接口,只执行 selectImports 方法

3. 源码解析

3.1 ConfigurationClassParser#processImports

private void processImports(ConfigurationClass configClass, ConfigurationClassParser.SourceClass currentSourceClass,
                            Collection<ConfigurationClassParser.SourceClass> importCandidates, Predicate<String> exclusionFilter,
                            boolean checkForCircularImports) {
    // 省略了一大段代码

    for (ConfigurationClassParser.SourceClass candidate : importCandidates) {
        // 处理ImportSelector接口
        if (candidate.isAssignable(ImportSelector.class)) {
            // 加载ImportSelector接口实现类
            Class<?> candidateClass = candidate.loadClass();
            // 实例化对象
            ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                    this.environment, this.resourceLoader, this.registry);
            // 构建过滤器
            Predicate<String> selectorFilter = selector.getExclusionFilter();
            if (selectorFilter != null) {
                exclusionFilter = exclusionFilter.or(selectorFilter);
            }
            if (selector instanceof DeferredImportSelector) {
                // 延迟处理,SpringBoot自动配置实现机制(AutoConfigurationImportSelector)
                this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
            } else {
                // 执行 ImportSelector#selectImports 方法
                String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                // 过滤一些不符合条件的类
                Collection<ConfigurationClassParser.SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                // 循环执行当前方法
                processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
            }
        } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
            // 加载ImportBeanDefinitionRegistrar接口实现类
            Class<?> candidateClass = candidate.loadClass();
            // 实例化对象
            ImportBeanDefinitionRegistrar registrar =
                    ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                            this.environment, this.resourceLoader, this.registry);
            // 给ConfigurationClass对象的相关属性赋值,后期再处理
            // 相关源码ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars
            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
        } else {
            // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
            // process it as an @Configuration class
            this.importStack.registerImport(
                    currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            // 将导入的类当成配置类处理
            processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
        }
    }
}

流程图展示如下:

3.2 通过源码分析案例 2.2、2.3、2.6、2.7

3.2.1 案例 2.2、2.3

案例 2.2、2.3 可以归位一类,不管类上是否存在 @Component 相关注解,都会被当成配置类解析。主要区别在于如果存在 @Component 相关注解,既可以被 @ComponentScan 注解处理,也可以被 @Import 注解处理,主要看解析顺序了。有兴趣的读者测试一下,将案例 2.3 UserConfig 类上的 @Configuration 注解注释掉,同样可以获取类型为 User 的 bean

3.2.2 案例 2.6

案例2.6 会循环执行 processImports 方法,直到 selectImports 方法返回的类是配置类(普通类)或者 ImportBeanDefinitionRegistrar 接口的实现类

3.2.3 案例 2.7

processImports 方法分为三个分支,实现 ImportSelector 接口优先级最高

4. @Import 注解的应用

4.1 SpringBoot 自动配置 (相关源码按照以下步骤切入)

  1. @SpringBootApplication
  2. @EnableAutoConfiguration
  3. @Import(AutoConfigurationImportSelector.class)
  4. AutoConfigurationImportSelector#selectImports

相关博文:SpringBoot 自动配置原理

4.2 @EnableAspectJAutoProxy(相关源码按照以下步骤切入)

  1. @EnableAspectJAutoProxy
  2. @Import(AspectJAutoProxyRegistrar.class)
  3. AspectJAutoProxyRegistrar#registerBeanDefinitions

4.3 小结

很多 @EnableXxx 这样的注解,都是依靠 @Import 来扩展了,流程基本都一样。还有一个套路就是最后注入的 bean 有一个类型是 BeanPostProcessor,该 BeanPostProcessor 可以对符合特征的 bean 生效,比如说 @EnableTransactionManagement、@EnableConfigurationProperties 注解。

BeanPostProcessor 相关博文:Spring之BeanPostProcessor

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

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

相关文章

实战项目:贪吃蛇游戏的实现(上)

前言 Hello, 今天我们来一起完成一个实战项目&#xff1a;贪吃蛇。 相信大家都不会对这个游戏感到陌生&#xff0c;贪吃蛇游戏是久负盛名的游戏&#xff0c;他和俄罗斯方块&#xff0c;扫雷游戏等游戏位列世界经典游戏之列。这次我们旨在通过实战项目贪吃蛇的实现&#xff0c…

opencv2.4.9源码在Windows下VS2019的编译

1、opencv2.4.9解压后根目录下建立build文件夹 2、采用CMake-gui进行编译 记得把上面两个√去掉&#xff0c;用老版本的opencv再用cuda完全没有意义&#xff0c;我们只是验证算法用。 把这个√也去掉。 重新Configure和Generate&#xff0c;如下图&#xff1a; 然后&#xff1…

大模型汇总:文心一言大模型、腾讯混元大模型、通义千问大模型、字节豆包大模型、智普清言大模型、KIMI 大模型、紫东太初大模型、讯飞星火大模型

文心一言大模型 作为百度自主研发的大型语言模型&#xff0c;具有显著的特点、广泛的应用场景以及独特的优势。以下是对文心一言特点、应用、优势的详细介绍&#xff1a; 特点&#xff1a;知识增强&#xff1a; 文心一言通过持续学习技术&#xff0c;不断吸收海量数据和知识…

Postman接口测试基础教程--2024最新版

文章目录 一、Postman 简介二、Postman 功能概览三、Postman 安装说明1. 下载与安装2. 界面导航说明3. 发送第一个请求 四、Postman 基础功能1. 常见类型的接口请求查询参数的接口请求表单类型的接口请求上传文件的表单请求JSON 类型的接口请求 2. 接口响应数据解析3. 接口管理…

基于GA遗传算法的拱桥静载试验车辆最优布载matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于GA遗传算法的拱桥静载试验车辆最优布载matlab仿真。主要是为了实现桥梁静载试验自动化布载&#xff08;确定车辆位置使得满足加载效率ηq的要求&#xff0c;0…

初级python代码编程学习---- 简单记事本小程序

简单记事本小程序 提供一个基于Python的简单记事本小程序示例。这个小程序将允许用户添加、查看和删除记事条。 # Simple Note Pad Mini-Application # 记事本数据结构 notes [] def add_note(note): """添加一条新的记事条""" …

可重用性功能 在 Jira 中扩展测试管理

在当今动态的软件开发环境中&#xff0c;高效且可扩展的测试管理对于确保软件产品的质量和可靠性至关重要。Jira 是一种流行的项目管理工具&#xff0c;它通过与Zephyr Scale的集成为测试管理提供了强大的解决方案。 在这篇博文中&#xff0c;我们将探讨可重用性功能在测试管理…

信创教育:培养未来科技创新的生力军

随着全球数字化转型的加速&#xff0c;信息技术应用创新&#xff08;简称“信创”&#xff09;产业作为推动国家信息技术自主可控和产业升级的关键领域&#xff0c;正迎来前所未有的发展机遇。信创教育&#xff0c;作为培养未来科技创新生力军的重要阵地&#xff0c;其重要性和…

智慧卫生间环境传感器有哪些?智慧卫生间的特点@卓振思众

随着科技的进步和人们对生活品质的要求不断提高&#xff0c;智慧卫生间作为现代化设施的代表&#xff0c;越来越受到关注。智慧卫生间不仅仅是在外观设计上做文章&#xff0c;更在于其背后强大的智能系统&#xff0c;特别是环境传感器的应用&#xff0c;让厕所的管理和使用变得…

第36课 Scratch入门篇:画正多边形

画正多边形 故事背景: 绘制一个正多边形! 程序原理: 我们前面学习了绘制三角形(3 条边,旋转 120 度),正方形(4 条边,旋转 90 度),我们可以归纳一个公式,针对正多边形,有n条边,那么旋转的角度就是 360/n。通过程序实现就是 开始编程 1、隐藏预设的猫咪角色,…

【C#】 接口 继承

简介 继承是面向对象编程的核心特性之一&#xff0c;它允许我们创建一个类&#xff08;称为子类&#xff09;来继承另一个类&#xff08;称为基类&#xff09;的属性和方法。 作用 这样&#xff0c;我们可以重用代码&#xff0c;减少重复&#xff0c;并使我们的代码更加模块…

无限金币版《废土世界》安卓手机游戏下载,游戏分享

《废土世界》&#xff08;JunkWorld&#xff09;是由IRONHIDE游戏工作室开发的一款塔防游戏&#xff0c;它将玩家带入一个荒凉、贫瘠的后末日世界&#xff0c;玩家需要带领一队拾荒者穿越沙漠和放射性沼泽&#xff0c;进行生存战斗。游戏以其战略深度和丰富的塔防元素为特色&am…

RabbitMQ高级特性 - 消息和队列TTL、死信队列

文章目录 消息和队列TTL概述实战开发 死信队列概述实战开发 消息和队列TTL 概述 a&#xff09;TTL&#xff08;Time To Live 过期时间&#xff09;&#xff0c;RabbitMQ 可以对消息和队列设置 TTL. 当消息到达存活时间之后&#xff0c;还没有被消费&#xff0c;就会被自动清除…

fmql之Linux移植

先了解以下linux移植的大致流程&#xff0c;以及需要的资料、软件等。 《领航者ZYNQ之嵌入式Linux开发指南_V2.0》第十八章 Linux内核移植 (amobbs.com 阿莫电子技术论坛) 前言 复旦微&#xff08;他人经验&#xff09; 复旦微fmql的操作指南来了&#xff1a; 复旦微 FMQL L…

算法:排序(前言)

所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。排序算法&#xff0c;就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视&#xff0c;尤其是在大量数据的处理方面。一个优秀的…

还在担心Android功能不会用吗?Intro Showcase View助你快速实现功能引导

还在担心Android功能不会用吗?Intro Showcase View助你快速实现功能引导 1. 引言 在现代应用开发中,如何有效引导用户快速上手并掌握应用的核心功能,是提升用户体验的重要一环。功能引导不仅帮助用户理解复杂功能,还能提高用户留存率,减少因操作复杂度带来的用户流失。随…

将Excel数据导入到SQL Server数据库

1.找到SQLServer导入导出工具&#xff0c;有32位和64位 2.打开后点击 下一步 3.选择数据源、Excel文件&#xff0c;自动匹配Excel的版本&#xff0c;根据实际情况&#xff0c;勾选首行是否是列名 4.选择目标&#xff0c;如图 5.点击属性&#xff0c;设置要导入的目标数据库…

zookeeper+kafka群集

一 :消息队列 1:什么是消息队列 消息(Message)是指在应用间传送的数据。消息可以非常简单&#xff0c;比如只包含文本字符串&#xff0c;也可以更复杂&#xff0c;可能包含嵌入对象。 消息队列(Message Queue)是一种应用间的通信方式&#xff0c;消息发送后可以立即返回由消息…

【Datawhale X 魔搭 】AI夏令营第四期大模型方向,Task2:头脑风暴会,巧灵脑筋急转弯(持续更新)

队伍名称&#xff1a;巧灵脑筋急转弯 队伍技术栈&#xff1a;python&#xff0c;LLM&#xff0c;RAG&#xff0c;大模型&#xff0c;nlp&#xff0c;Gradio&#xff0c;Vue&#xff0c;java 队友&#xff1a;知唐&#xff08;队长&#xff09;&#xff0c;我真的敲不动…

RCE绕过技巧

目录 EVAL长度限制突破技巧 1.使用反引号 2.file_put_contents写入文件 3.php5.6变长参数usort回调后门 命令长度限制突破技巧 1.拼接文件名 无字母数字的webshell命令执行 1.取反码 2.上传临时文件 EVAL长度限制突破技巧 分析代码&#xff1a;首先传递一个param参数&…