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

文章目录

    • 前言
    • 原生手动装配
    • 模块装配概述
    • 模块装配的四种方式
      • 准备工作
        • 声明自定义注解
      • 导入普通类
      • 导入配置类
      • 导入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 。但是有时候我们需要根据某些条件进行判断呢?这就需要我们的条件装配了,具体篇幅有限,后续在进行讲解

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

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

相关文章

RabbitMQ基础知识

一.什么是RabbitMQ RabbitMQ是一个开源的、高性能的消息队列系统&#xff0c;用于在应用程序之间实现异步通信。它实现了AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;协议&#xff0c;可以在分布式系统中传递和存储消息。 消息队列是一种将消息发送者和接收…

微服务雪崩问题及解决方案

雪崩问题 微服务中&#xff0c;服务间调用关系错综复杂&#xff0c;一个微服务往往依赖于多个其它微服务。 微服务之间相互调用&#xff0c;因为调用链中的一个服务故障&#xff0c;引起整个链路都无法访问的情况。 如果服务提供者A发生了故障&#xff0c;当前的应用的部分业务…

剑指offer

1、排序算法 0、排序算法分类 1、直接插入排序 基本思想 直接插入排序的基本思想是&#xff1a;将数组中的所有元素依次跟前面已经排好的元素相比较&#xff0c;如果选择的元素比已排序的 元素小&#xff0c;则交换&#xff0c;直到全部元素都比较过为止。 算法描述 1、从…

ubuntu系统没有网络图标的解决办法

参考文章:https://blog.csdn.net/qq_56922632/article/details/132309643 1. 执行关闭网络服务的命令&#xff0c;关闭网络服务sudo service NetworkManager stop2. 删除网络的状态文件sudo rm /var/lib/NetworkManager/NetworkManager.state3. 修改网络的配置文件sudo vi /etc…

每日一题(LeetCode)----二叉树--二叉树的层平均值

每日一题(LeetCode)----二叉树–二叉树的层平均值 1.题目&#xff08;637. 二叉树的层平均值&#xff09; 给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。 示例 1&#xff1a; 输入&#xff1a;root […

【Math】重要性采样 Importance sample推导【附带Python实现】

【Math】重要性采样 Importance sample推导【附带Python实现】 文章目录 【Math】重要性采样 Importance sample推导【附带Python实现】1. Why need importance sample?2. Derivation of Discrete Distribution3. Derivation of Continuous Distribution3. An Example 笔者在学…

k8s的声明式资源管理(yaml文件)

1、声明式管理的特点 &#xff08;1&#xff09;适合对资源的修改操作 &#xff08;2&#xff09;声明式管理依赖于yaml文件&#xff0c;所有的内容都在yaml文件当中 &#xff08;3&#xff09;编辑好的yaml文件&#xff0c;还是要依靠陈述式的命令发布到k8s集群当中 kubect…

基于PCA-WA(Principal Component Analysis-weight average)的图像融合方法 Matlab代码及示例

摘要&#xff1a; 高效地将多通道的图像数据压缩&#xff08;如高光谱、多光谱成像数据&#xff09;至较低的通道数&#xff0c;对提高深度学习&#xff08;DL&#xff09;模型的训练速度和预测至关重要。本文主要展示利用PCA降维结合weight-average的图像融合方法。文章主要参…

【Leetcode】466. 统计重复个数

文章目录 题目思路代码 题目 466. 统计重复个数 思路 题目要求找出一个最大整数 m&#xff0c;使得经过 n2 个字符串 s2 组成的字符串能够被经过 n1 个字符串 s1 组成的字符串完全包含的次数。使用动态规划来记录每个位置匹配的情况&#xff0c;并通过循环节的分析来计算最…

leetcode刷题日记:222. Count Complete Tree Nodes(完全二叉树的节点个数)

这一道题&#xff0c;我们可以选择直接进行二叉树的遍历&#xff0c;将所有结点遍历一遍就能得到完全二叉树的结点个数&#xff0c;时间复杂度为O(n)。 代码如下&#xff1a; int countNodes(struct TreeNode* root) {if(rootNULL){return 0;}return countNodes(root->left…

(NeRF学习)NeRFStudio安装win11

参考&#xff1a; 【深度学习】【三维重建】windows11环境配置tiny-cuda-nn详细教程nerfstudio介绍及在windows上的配置、使用NeRFStudio官网githubRuntimeError: PytorchStreamReader failed reading zip archive: failed finding central directory原因及解决 目录 requireme…

element-ui table-自定义表格某列的表头样式或者功能

自带表格 自定义表格某列的表头样式或者功能 <el-table><el-table-column :prop"date">//自定义表身每行数据<template slot-scope"scope">{{scope.row[scope.column.label] - ? - : scope.row[scope.column.label]}}</template>…

使用Gitea搭建自己的git远程仓库

Gitea 为什么需要自建仓库 原因只有一个&#xff1a;折腾。其实国内的码云加上github已经足够用了。 官方原话 Gitea 的首要目标是创建一个极易安装&#xff0c;运行非常快速&#xff0c;安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言&#xff0c;这使我们…

计算机毕业设计 SpringBoot的乡村养老服务管理系统 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

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

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

使用Wireshark进行网络流量分析

目录 Wireshark是什么&#xff1f; 数据包筛选 筛选指定ip 使用逻辑运算符筛选 HTTP模式过滤 端口筛选 协议筛选 包长度筛选 数据包搜索 数据流分析 数据包导出 Wireshark是什么&#xff1f; 通过Wireshark&#xff0c;我们可以捕获和分析网络数据包&#xff0c;查看…

用ChatGPT方式编程!GitHub Copilot Chat全面开放使用

全球著名开源分享平台GitHub在官网宣布&#xff0c;经过几个月多轮测试的GitHub Copilot Chat&#xff0c;全面开放使用&#xff0c;一个用ChatGPT方式写代码的时代来啦&#xff01; 据悉&#xff0c;Copilot Chat是基于OpenAI的GPT-4模型&#xff0c;再结合其海量、优质的代码…

GitHub Copilot 最佳免费平替:阿里通义灵码

之前分享了不少关于 GitHub Copilot 的文章&#xff0c;不少粉丝都评论让我试试阿里的通义灵码&#xff0c;这让我对通义灵码有了不少的兴趣。 今天&#xff0c;阿七就带大家了解一下阿里的通义灵码&#xff0c;我们按照之前 GitHub Copilot 的顺序分享通义灵码在相同场景下的…

【Linux 内核源码分析】GPIO子系统软件框架

Linux内核的GPIO子系统是用于管理和控制通用输入输出&#xff08;GPIO&#xff09;引脚的软件框架。它提供了一套统一的接口和机制&#xff0c;使开发者能够方便地对GPIO进行配置、读写和中断处理。 主要组件&#xff1a; GPIO框架&#xff1a;提供了一套API和数据结构&#x…

【深度学习-基础学习】Self-Attention 自注意力机制 笔记

本篇文章学习总结 李宏毅 2021 Spring 课程中关于 Self-Attention 自注意力 机制相关的内容。课程链接以及PPT&#xff1a;李宏毅Spring2021ML 关于 Self-Attention 机制想要解决的问题 通常来说&#xff0c; 我们的模型的输入会是一个vector&#xff0c;然后输出可能是 一个数…