@EnableXXX注解+@Import轻松实现SpringBoot的模块装配

news2024/10/7 18:30:50

文章目录

    • 前言
    • 原生手动装配
    • 模块装配概述
    • 模块装配的四种方式
      • 准备工作
        • 声明自定义注解
      • 导入普通类
      • 导入配置类
      • 导入ImportSelector
      • 导入ImportBeanDefinitionRegistrar
    • 总结
    • TODO后续--条件装配

在这里插入图片描述

前言

最早我们开始学习或接触过 SSH 或者 SSM 的框架整合,大家应该还记得那些配置文件有多烦吧,又多又不好记真的很让人头大。在处理配置文件的同时,大家是否有想过:如果能有一种方式,可以使用很少的配置,甚至不配置就可以完成一个功能的装载,那岂不是省了很多事?

这个疑问在 SpringBoot 中得以解决,也就是我们常说的自动装配,而这个自动装配的核心技术就是模块装配 + 条件装配。

今天我们这里主要讲解模块装配,条件装配我们后续再讲解!

原生手动装配

通常我们使用 @Configuration + @Bean 注解组合,或者 @Component + @ComponentScan 注解组合,可以实现编程式 / 声明式的手动装配。这两种方式相信大家都肯定会了。

不过,我们思考一个问题:如果使用这两种方式,如果要注册的 Bean 很多,要么一个一个的 @Bean 编程式写,要么就得选好包进行组件扫描,而且这种情况还得每个类都标注好 @Component 或者它的衍生注解才行。面对数量很多的 Bean ,这种装配方式很明显会比较麻烦,需要有一个新的解决方案。

那就是我们接下来要讲的模块装配!

模块装配概述

SpringFramework 3.0 的发布,全面支持了注解驱动开发,随之而来的就是快速方便的模块装配。

模块通常就是一个功能单元,而模块装配就可以理解为把一个模块需要的核心功能组件都装配好,当然如果能有尽可能简便的方式那最好。

SpringFramework 中的模块装配,是在 3.1 之后引入大量 @EnableXXX 注解,来快速整合激活相对应的模块。

在 3.1.5 节中,它有介绍 @EnableXXX 注解的使用,并且它还举了不少例子,这里面不乏有咱可能熟悉的:

  • EnableTransactionManagement :开启注解事务驱动
  • EnableWebMvc :激活 SpringWebMvc
  • EnableAspectJAutoProxy :开启注解 AOP 编程
  • EnableScheduling :开启调度功能(定时任务)

另外比如:我们常用的@SpringBootApplication注解中用于开启自动注入的@EnableAutoConfiguration,开启异步方法的@EnableAsync,开启将配置文件中的属性以bean的方式注入到IOC容器的@EnableConfigurationProperties等。

其实 @Enable*注解很简单,随便找一个注解,点进去一看就能恍然大悟,它的所有核心 都在@Import 注解当中。 所有真正核心的 是@Import注解,由它去加载它自己对应的配置类,然后启动他的功能。

比如我们上面的EnableAsync,它会将AsyncConfigurationSelector放入容器中,当Spring启动,会执行selectImports(AnnotationMetadata annotationMetadata)方法,在这个方法中我们做了某些处理,使得和 @Enable*搭配使用的注解生效。

...
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
    Class<? extends Annotation> annotation() default Annotation.class;

    boolean proxyTargetClass() default false;

    AdviceMode mode() default AdviceMode.PROXY;

    int order() default Integer.MAX_VALUE;
}

那有人会说@Import注解就可以了,还要Enable*注解干嘛,我直接使用@Import注解去加载就好了,这不是多此一举吗? 看看下面的好处就知道了

  • 除了桥梁的作用,它还可以携带上一些参数:在解析处理这个 注解的时候,可以从这里拿到一些 自定义配置的参数,去做相关的操作。

  • 启用这个功能可能需要更多的 类加载,还有要其它注解去配和,如果不将其包装到 @Enable*中,那对开发者来说,配置起来又相对麻烦了许多,将其包装到一起,只需要记住使用这一功能记住这个注解即可。极大的方面!!。

  • 将功能做组建抽离开来,降低耦合性。


模块装配的四种方式

先记住使用模块装配的核心原则:自定义注解 + @Import 导入组件。
在这里插入图片描述

准备工作

声明自定义注解

我们自定义一个注解用来是否允许来进行日志记录

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface EnableLog {
  
}

模块装配需要一个最核心的注解是 @Import ,它要标注在 @EnableLog 上。不过这个 @Import 中需要传入 value 值,点开看一眼它的源码吧:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 */
	Class<?>[] value();
}

文档注释已经写得非常明白了:它可以导入配置类ImportSelector 的实现类ImportBeanDefinitionRegistrar 的实现类,或者普通类。咱这里先来快速上手,所以我们先选择使用普通类导入。

导入普通类

注意只有在Spring 4.2之后,@Import可以直接指定实体类,加载这个类定义到context中。

注意我们的MyLog是没有任何注解的

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

public calss MyLog{...}

这样就代表,如果标注了 @EnableLog 注解,就会触发 @Import 的效果,向容器中导入一个 MyLog 类型的 Bean


如何进行生效?

在使用自定义的Enable注解需要搭配Spring原生的 @Configuration 进行使用,单纯只有@EnableLog是不行的

如果是SpringBoot项目,可以直接放在@SpringBootApplication注解的类上面,有人会问为什么放在这里可以呢?原因就是 @SpringBootConfiguration注解上面配置了 @Configuration 注解。@SpringBootApplication就相当于一个@Configuration注解,所以我们自定义的Enable注解可以直接放在@SpringBootApplication,当然也可以自定义一个用@Configuration修饰的类上面。

下面的例子都需要这个MyLogConfiguration 配置类,后面就不进行赘述了。

@Configuration
@EnableLog
public class MyLogConfiguration {
    
}

经过这样,运行发现我们的spring容器中已经有了MyLog类

public class LogApplication {
    
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyLogConfiguration.class);
        Boss boss = ctx.getBean(MyLog.class);
        System.out.println(boss);
    }
}

导入配置类

如果需要直接导入一些现有的配置类,使用 @Import 也可以直接加载进来。

@Configuration
public class LogBeanConfiguration {
    
    @Bean
    public MyLog myLog() {
        return new MyLog();
    }
    
    @Bean
    public LogUtil logUtil() {
        return new LogUtil();
    }
    
}

然后只需要在 @EnableTavern@Import 中把这个配置类加上即可:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyLog.class,LogBeanConfiguration.class)
public @interface EnableLog {
    
}

运行发现,我们容器打印出两个MyLog类:注意LogBeanConfiguration 配置类也被注册到 IOC 容器成为一个 Bean 了。

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyLogConfiguration.class);
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
        System.out.println("--------------------------");
        Map<String, MyLog> myLogs = ctx.getBeansOfType(MyLog.class);
        myLogs.forEach((name, myLog) -> System.out.println(myLog));
    }

导入ImportSelector

注意,我们可能还会看到DeferredImportSelector这个注解,这个注解其实是继承了ImportSelector
他们算是一类接口,只是执行的时间不同而已。看Deferred这个单词意思就知道了,Deferred只是进行延迟了。

我们的 ImportSelector 可以导入普通类和配置类:

注意,selectImports 方法的返回值是一个 String 类型的数组,它这样设计的目的是什么呢?咱来看看 selectImports 方法的文档注释:

根据导入的 @Configuration 类的 AnnotationMetadata 选择并返回要导入的类的类名。也就是可以根据AnnotationMetadata注解条件在进行匹配

public class MyLogImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {MyLog.class.getName(), LogBeanConfiguration.class.getName()};
    }
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyLog.class,LogBeanConfiguration.class,MyLogImportSelector.class)
public @interface EnableLog {
    
}

运行后我们会发现,有4个MyLog.class类,MyLog.class一个,LogBeanConfiguration.class一个,MyLogImportSelector.class两个

导入ImportBeanDefinitionRegistrar

如果说 ImportSelector 更像声明式导入的话,那 ImportBeanDefinitionRegistrar 就可以解释为编程式向 IOC 容器中导入 Bean 。不过由于它导入的实际是 BeanDefinition ( Bean 的定义信息)。我们对 ImportBeanDefinitionRegistrar 有一个快速的使用入门即可。

简单解释下,这个 registerBeanDefinition 方法传入的两个参数,第一个参数是 Bean 的名称(id),第二个参数中传入的 RootBeanDefinition 要指定 Bean 的字节码( .class )。

public class MyLogRegistrar implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("myLog", new RootBeanDefinition(MyLog.class));
    }
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyLog.class,LogBeanConfiguration.class,MyLogImportSelector.class,MyLogRegistrar.class)
public @interface EnableLog {
    
}

运行后我们会发现,有5个MyLog.class类,MyLog.class一个,LogBeanConfiguration.class一个,MyLogImportSelector.class两个,MyLogRegistrar.class一个

注意这里 MyLogRegistrar 本身没有注册到 IOC 容器中。

总结

  • Enable类型的注解从Spring原生的和自己拓展的来看,相当于一个开关。增加这个注解就开启了一个功能,需要搭配其他的注解来使用,例如:@EnableAsync搭配@Async注解,@EnableTransactionManagement搭配@Transactional注解使用
  • Enable类型注解生效需要搭配@Configuration注解
  • Enable类型注解的实现需要搭配注解@Import导入,可以导入普通类、配置类,而更高级一点的功能实现需要实现DeferredImportSelector、ImportSelector、ImportBeanDefinitionRegistrar三个接口中一个。

TODO后续–条件装配

通过模块装配,咱可以通过一个注解,一次性导入指定场景中需要的组件和配置。那么只靠模块装配的内容,就可以把这些装配都考虑到位吗?

比如只要配置类中声明了 @Bean 注解的方法,那这个方法的返回值就一定会被注册到 IOC 容器成为一个 Bean 。但是有时候我们需要根据某些条件进行判断呢?这就需要我们的条件装配了,具体篇幅有限,后续在进行讲解

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

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

相关文章

Python图像处理:批量添加水印的优雅实现与进阶技巧

1. 简介 在日常图像处理中&#xff0c;为图片添加水印是一项常见任务。有多种方法和工具可供选择&#xff0c;而今天我们将专注于使用Python语言结合PIL库批量添加水印。 需要注意的是&#xff0c;所选用的图片格式不应为JPG或JPEG&#xff0c;因为这两种格式的图片不支持透明…

vscode中增加参数的一个方法

1 在settings.json 文件中增加参数 2. 在参数中配置 这里也是ok的

Python爬虫系列-有道批量翻译英文单词-注音标版

爬虫系列更新-第二篇文章——《Python爬虫系列-有道批量翻译英文单词-注音标版》 之前发布计算机英文单词时研究了下,怎么把一个含有大量英文单词的txt文件翻译成如下格式&#xff1a; 如上图,左边图片是需要翻译的txt文本,右边图片是翻译后的txt文本。 运行的实际界面效果。 …

电脑如何屏幕录制?轻松录制高清视频

在当今信息化的时代&#xff0c;电脑已经成为工作和生活的重要工具。无论是在进行演示、教学还是记录重要操作步骤时&#xff0c;屏幕录制都是非常有用的。可是电脑如何屏幕录制呢&#xff1f;本篇文章将介绍三种常见的电脑屏幕录制方法&#xff0c;通过学习这些方法&#xff0…

Vue中的计算属性与监听器

聚沙成塔每天进步一点点 ⭐ 专栏简介 Vue学习之旅的奇妙世界 欢迎大家来到 Vue 技能树参考资料专栏!创建这个专栏的初衷是为了帮助大家更好地应对 Vue.js 技能树的学习。每篇文章都致力于提供清晰、深入的参考资料,让你能够更轻松、更自信地理解和掌握 Vue.js 的核心概念和技…

centos7部署minio单机版

一、目标 在centos7上部署minio单机版 二、centos7部署minio 1、下载minio mkdir /usr/local/minio cd /usr/local/minio wget https://dl.minio.io/server/minio/release/linux-amd64/minio chmod x minio 2、新建minio存储数据的目录 mkdir -p /data/minio/data3、新建…

航芯ACM32G103开发板评测 02-GPIO输入输出

航芯ACM32G103开发板评测 02-GPIO输入输出 航芯ACM32G103开发板评测 GPIO输入输出应用 软硬件平台 ACM32G103 Board开发板 MDK-ARM Keil GPIO输出典型应用——点灯 GPIO输入典型应用——按键 GPIO 功能概述 GPIO 是通用输入/输出&#xff08;General Purpose I/O&#x…

Mysql的基本用法(上)非常详细、快速上手

上篇结束了java基础&#xff0c;本篇主要对Mysql中的一些常用的方法进行了总结&#xff0c;主要对查询方法进行了讲解&#xff0c;包括重要的多表查询用到的内连接和外连接等&#xff0c;以下代码可以直接复制到可视化软件中&#xff0c;方便阅读以及练习&#xff1b; SELECT *…

CRM市场营销管理功能,如何进行客户细分和数据分析?

CRM管理系统中的营销管理模块&#xff0c;它的锋芒常被销售管理所掩盖&#xff0c;但对于企业的业务来说同样重要。营销部门虽然不像销售人员一样直接面对客户&#xff0c;却是挖掘线索、商机的重要角色。CRM在市场营销领域的关键功能包括&#xff1a;营销漏斗、客户细分、营销…

详解静态网页数据获取以及浏览器数据和网络数据交互流程-Python

目录 前言 一、静态网页数据 二、网址通讯流程 1.DNS查询 2.建立连接 3.发送HTTP请求 4.服务器处理请求 5.服务器响应 6.渲染页面 7.页面交互 三、URL/POST/GET 1.URL 2.GET 形式 3.POST 形式 四.获取静态网页数据 1.requests库 点关注&#xff0c;防走丢&am…

C++上位软件通过Snap7开源库访问西门子S7-1200/S7-1500数据块的方法

前言 本人一直从事C上位软件开发工作较多&#xff0c;在之前的项目中通过C访问西门子PLC S7-200/S7-1200/S7-1500并进行数据交互的应用中一直使用的是ModbusTCP/ModbusRTU协议进行。Modbus上位开源库采用的LibModbus。经过实际应用发现Modbus开源库单次发送和接受的数据不能超过…

基于双闭环PI和SVPWM的PMSM控制器simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 双闭环PI控制器设计 4.2 SVPWM技术 4.3 控制系统实现 5.完整工程文件 1.课题概述 基于双闭环PI和SVPWM的PMSM控制器simulink建模与仿真。系统包括逆变桥、PMSM、park变换、clark变换、SVPWM、PI控…

了解长短期记忆 (LSTM) 网络:穿越时间和记忆的旅程

一、说明 在人工智能和机器学习的迷人世界中&#xff0c;长短期记忆 (LSTM) 网络作为一项突破性创新脱颖而出。LSTM 旨在解决传统循环神经网络 (RNN) 的局限性&#xff0c;尤其是在学习长期依赖性方面的局限性&#xff0c;彻底改变了我们在各个领域建模和预测序列的能力。本文深…

算法分析与设计 第一次课外作业

算法分析与设计 第一次课外作业 文章目录 算法分析与设计 第一次课外作业一. 单选题&#xff08;共8题&#xff0c;80分&#xff09;二. 判断题&#xff08;共2题&#xff0c;20分&#xff09; 一. 单选题&#xff08;共8题&#xff0c;80分&#xff09; (单选题)以下叙述中错误…

【模拟电路】模拟集成电路之神-NE555

一、集成电路NE555简介 二、功能框图与引脚说明 三、比较器&#xff08;运放&#xff09; 四、反相门&#xff08;非门&#xff09; 五、或非门 六、双稳态触发器 七、NE555的工作原理 集成电路NE555的芯片手册 C5157696 一、集成电路NE555简介 NE555起源于上个世纪70年代&a…

CCNP课程实验-03-Route_Path_Control_CFG

目录 实验条件网络拓朴需求 基础配置需求实现1.A---F所有区用Loopback模拟&#xff0c;地址格式为&#xff1a;XX.XX.XX.XX/32&#xff0c;其中X为路由器编号。根据拓扑宣告进对应协议。A1和A2区为特例&#xff0c;A1&#xff1a;55.55.55.0/24&#xff0c;A2&#xff1a;55.55…

java spring boot 获取resource目录下的文档

主要代码 String filePath"templates/test.xls" ClassPathResource classPathResource new ClassPathResource(filePath); InputStream inputStream classPathResource.getInputStream();目录 主要目录存放再这 代码案例 public void downloadTemplate( HttpS…

是否需要跟上鸿蒙(OpenHarmony)开发岗位热潮?

前言 自打华为2019年发布鸿蒙操作系统以来&#xff0c;网上各种声音百家争鸣。尤其是2023年发布会公布的鸿蒙4.0宣称不再支持Android&#xff0c;更激烈的讨论随之而来。 本文没有宏大的叙事&#xff0c;只有基于现实的考量。 通过本文&#xff0c;你将了解到&#xff1a; Har…

密码学上的经典瞬间:如果当时有Python!

提到“安全”&#xff0c;首先想到的一定是加密。 在如今的互联网环境中&#xff0c;信息加密无处不在&#xff0c;我们早已习惯&#xff0c;甚至毫无感觉。 比如&#xff0c;通过https协议访问的各个网站的内容&#xff0c;QQ&#xff0c;微信等聊天工具之间互相发送的信息等等…

前端开发_JavaScript基础

JavaScript介绍 JS是一种运行在客户端&#xff08;浏览器&#xff09;的编程语言&#xff0c;实现人机交互效果 作用&#xff1a; 网页特效 (监听用户的一些行为让网页作出对应的反馈) 表单验证 (针对表单数据的合法性进行判断) 数据交互 (获取后台的数据, 渲染到前端) 服…