揭秘Spring框架:模块装配的奥秘与实战技巧 【Spring|Java】

news2024/11/17 7:42:28

简单说两句

作者:后端小知识CSDN后端领域新星创作者|阿里云专家博主

CSDN个人主页:后端小知识

🔎GZH后端小知识

🎉欢迎关注🔎点赞👍收藏⭐️留言📝

揭秘Spring框架:模块装配的奥秘与实战技巧

文章目录

    • 揭秘Spring框架:模块装配的奥秘与实战技巧
      • 说在前面
      • 思维导图
      • 前言
      • 模块
      • 快速体会模块装配
        • 场景
        • 声明自定义注解
        • 声明老板类
        • 创建配置类
        • 编写测试类启动
      • 导入配置类
        • 声明调酒师类
        • 注册调酒师对象
        • 测试运行
      • 导入ImportSelector
        • 声明吧台类+配置类
        • 编写ImportSelector的实现类
        • 测试运行
        • ImportSelector的灵活性
      • 导入ImportBeanDefinitionRegistrar
        • 声明服务员类
        • 编写ImportBeanDefinitionRegistrar的实现类
        • 测试运行
      • DeferredImportSelector(扩展)
        • DeferredImportSelector执行时机

image-20231107004510265

说在前面

本系列文章是Spring学习笔记,参考书**《SpringBoot源码解读与原理分析》**

阅读的一个小要求:至少会使用SpringBoot或Spring或者天才型选手

本系列文章将会持续更新更新速度取决于各位读者大大的点赞收藏和留言

思维导图

为了方便读者了解文章整体内容,我给出了一张思维导图,希望有所帮助

image-20231107004253613

前言

Spring框架是一个轻量级的Java企业级应用开发框架,它提供了一种简化企业级应用开发的方法。Spring框架的核心是依赖注入(DI)和面向切面编程(AOP),这两个特性使得开发者可以更容易地构建和管理复杂的企业级应用。

在Spring框架中,模块装配主要是指将各个模块(组件)组合在一起,形成一个完整的应用。Spring框架提供了多种方式来实现模块装配,以下是一些常见的方法:

  1. XML配置文件:在早期的Spring版本中,开发者主要通过XML配置文件来装配模块。在XML文件中,开发者可以定义bean、bean之间的关系以及bean的属性等。这种方式虽然灵活,但随着项目规模的增大,XML配置文件可能会变得非常复杂,难以维护。
  2. 注解:从Spring 2.5版本开始,Spring框架引入了注解支持,使得开发者可以通过注解来简化模块装配。常见的注解有@Component(标识一个受Spring IOC容器管理的普通组件)、@Repository(标识一个受Spring IOC容器管理的持久化层组件)、@Service(标识一个受Spring IOC容器的业务逻辑层组件)和@Controller(标识一个受Spring IOC容器管理的表述层控制器组件)。通过使用这些注解,开发者可以直接在类上进行装配,而无需编写繁琐的XML配置文件。
  3. Java配置:从Spring 3.0版本开始,Spring框架引入了Java配置支持,允许开发者使用Java代码来定义bean和bean之间的关系。这种方式相较于XML配置更加简洁,易于阅读和维护。开发者可以使用@Configuration注解来定义配置类,然后在配置类中使用@Bean注解来定义bean。
  4. 自动装配:Spring框架提供了自动装配功能,可以根据类型或名称自动将bean装配到其他bean中。这可以大大简化模块装配的过程。开发者可以使用@Autowired、@Qualifier和@Value等注解来实现自动装配。

Spring框架提供了多种模块装配方式,开发者可以根据项目需求和团队习惯选择合适的方式进行模块装配。随着Spring框架的不断发展,模块装配的方式也在不断简化,使得开发者可以更加专注于业务逻辑的实现。

模块

在Spring框架中,模块(Module)通常是指一个功能独立的组件,它负责处理特定的业务逻辑或提供特定的功能。模块可以是一个类、一个服务接口、一个数据访问对象(DAO)等。模块之间可以通过依赖注入(DI)和面向切面编程(AOP)等技术进行组合和扩展,从而构建出一个完整的企业级应用。

在Spring框架中,模块通常具有以下特点:

  1. 功能独立:模块应该具有明确的功能边界,负责处理特定的业务逻辑或提供特定的功能。这有助于降低模块之间的耦合度,提高代码的可维护性和可扩展性。
  2. 可重用性:模块应该具有良好的可重用性,可以在不同的项目或应用中进行复用。这有助于减少代码的重复编写,提高开发效率。
  3. 可测试性:模块应该具有良好的可测试性,可以方便地进行单元测试和集成测试。这有助于确保模块的正确性和稳定性。
  4. 可扩展性:模块应该具有良好的可扩展性,可以通过继承、组合等方式进行扩展。这有助于应对业务需求的变化,提高应用的灵活性。

在Spring框架中,模块通常通过依赖注入(DI)和面向切面编程(AOP)等技术进行组合和扩展。依赖注入允许模块之间通过依赖关系进行通信,而面向切面编程则允许在模块之间添加横切关注点,如日志记录、性能监控等。这些技术使得模块之间的组合和扩展变得更加灵活和方便。

快速体会模块装配

场景

假设一个场景:使用代码模拟构建一个酒馆,酒馆里面有吧台、调酒师、服务员和老板4种不同的实体元素,在该场景中,酒馆可以看作ApplicationContext,吧台、调酒师、服务员和老板可以看作组件,使用代码模拟实现的最终目的,可以通过一个注解,把以上元素全部填充到酒馆中

【Tips】:假设的场景仅配合代码完成演示

声明自定义注解

声明一个注解:@EnableTavern

EnableTavern代码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EnableTavern {
}
声明老板类
public class Boss {
}

声明好了后,我们在EnableTavern中写上@Import注解并填入Boss类,如下代码所示,这就意味着如果一个配置类上标注了@EnableTavern注解的话,就会触发@Import的效果,向容器中导入一个Boss类的Bean

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({Boss.class})
public @interface EnableTavern {
}
创建配置类

注解驱动的测试离不开配置类,下面声明一个TavernConfiguration配置类,并标注@Configuration和@EnableTavern注解

@Configuration
@EnableTavern
public class TavernConfiguration {
}
编写测试类启动
public class TavernAppTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
        Boss bean = ctx.getBean(Boss.class);
        System.out.println("bean = " + bean);
    }
}

运行结果如图

image-20231103010650347

看运行结果,我们可以发现使用getBean能够正常提取Boss对象,说明Boss类已经被注册到IOC容器中了,并且创建了一个对象,到这里就完成了最简单的模块装配

导入配置类

到这里可能有读者会产生疑惑,原本通过@Configuration加@Bean注解就能完成的工作。换用@Import注解后代码量却增加了。这不是徒增功耗吗?如果你也有这种疑问请,不要着急,仔细观察@Import的value属性允许传入的类。可以发现普通类似最简单的方式,而其余几种类型更为重要。

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

声明调酒师类

调酒师类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Bartender {
    private String name;
}
注册调酒师对象

通过注解配置类的方式,可以一次性注册多个相同的bean对象,下面编写一个配置类BartenderConfiguration,并用@Bean注册两个不同的Bartender类

注册调酒师的配置类 BartenderConfiguration

@Configuration
public class BartenderConfiguration {

    @Bean
    public Bartender getBartender(){
        return new Bartender("调酒师-01");
    }

    @Bean
    public Bartender getBartender02(){
        return new Bartender("调酒师-02");
    }
}

在@EnableTavern 注解中添加BartenderConfiguration配置类

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({Boss.class, BartenderConfiguration.class})
public @interface EnableTavern {
}
测试运行
public class TavernAppTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
        System.out.println("================================");
        Map<String, Bartender> beansOfType = ctx.getBeansOfType(Bartender.class);
        beansOfType.forEach((name,bartender) ->{
            System.out.println("name = " + name);
            System.out.println("bartender = " + bartender);
        });
    }
}

运行结果如图

image-20231103012517338

通过看运行结果,可以发现控制台成功打印两个调酒师对象,说明注解配置类的装配正确完成。

【Tips】:注意,BartenderConfiguration配置类也被注册到了IOC容器中,并成为一个Bean

导入ImportSelector

通过IDEA可以看到ImportSelector是一个接口,这个接口可以导入配置类,也可以导入普通类

声明吧台类+配置类

声明吧台类

public class Bar {
}

BarConfiguration配置类中注册Bar

@Configuration
public class BarConfiguration {
    @Bean
    public Bar bbar(){
        return new Bar();
    }
}

编写ImportSelector的实现类

BarImportSelector实现ImportSelector接口

public class BarImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        System.out.println("BarImportSelector invoke ...");
        return new String[]{Bar.class.getName(), BarConfiguration.class.getName()};
    }
}

写好后,在@EnableTavern注解中添加BarImportSelector

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class})
public @interface EnableTavern {
}
测试运行
public class TavernAppTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
        System.out.println("================================");
        Map<String, Bartender> beansOfType = ctx.getBeansOfType(Bartender.class);
        beansOfType.forEach((name,bartender) ->{
            System.out.println("name = " + name);
            System.out.println("bartender = " + bartender);
        });
    }
}

image-20231106234621568

ImportSelector的灵活性

ImportSelector的核心是可以使开发者采用更灵活的声明式向IOC容器中注册bean,其重点是可以灵活的指定要注册的Bean的类,正因为传入的是全限定名的字符串那么如果这些全限定名以配置文件的形式存放在项目可以读取的位置,是不是就可以避免导入组件的硬编码问题了在SpringBoot自动装配中,底层就是利用了ImportSelector,实现从spring.factories文件中读取自动配置类。

导入ImportBeanDefinitionRegistrar

如果说ImportSelector是以声明式导入组件,那么ImportBeanDefinitionRegistrar可以解释为以编程式向IOC容器中注册Bean对象,实际导入的是BeanDefinition(Bean的定义信息)

声明服务员类

服务员类

public class Waiter {
}
编写ImportBeanDefinitionRegistrar的实现类

WaiterRegistrar实现ImportBeanDefinitionRegistrar

public class WaiterRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        System.out.println("WaiterRegistrar invoke ....");
        registry.registerBeanDefinition("waiter", new RootBeanDefinition(Waiter.class));
    }
}

在@EnableTavern注解中添加ImportBeanDefinitionRegistrar的方式

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class, WaiterRegistrar.class})
public @interface EnableTavern {
}
测试运行

测试代码

public class TavernAppTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
        System.out.println("================================");
        Map<String, Bartender> beansOfType = ctx.getBeansOfType(Bartender.class);
        beansOfType.forEach((name,bartender) ->{
            System.out.println("name = " + name);
            System.out.println("bartender = " + bartender);
        });
    }
}

image-20231107000338828

【Tips】: WaiterRegistrar没有被注册到IOC容器中

DeferredImportSelector(扩展)

DeferredImportSelector 是 Spring 提供的一个接口(ImportSelector的子接口),用于在运行时动态导入额外的配置类。它与 AutoConfigurationImportSelector 的作用类似,但在应用程序初始化阶段不导入所有配置,而是在需要时才导入额外的配置。使用 DeferredImportSelector 需要创建实现该接口的类,并在应用程序中通过 @Import 注解引入。通过实现 selectImports 方法,可以根据当前应用程序环境和需求动态选择和导入额外的配置类。

实现 DeferredImportSelector 可以提高应用程序的启动速度和效率,因为只在需要时加载额外的配置。这对于特定的应用程序场景是非常有用的,例如,当您需要根据不同的环境或配置选项加载额外的配置时,或在某些情况下懒惰加载配置以提高启动速度。

DeferredImportSelector 还可以通过实现排序接口,在导入额外的配置时按照特定的顺序进行排序,以便确保额外的配置在正确的顺序中加载。总的来说,DeferredImportSelector 是一种强大的工具,可以提高应用程序的性能和可维护性,并使您能够更加灵活地管理和导入额外的配置。

DeferredImportSelector执行时机

WaiterDeferredImportSelector导入服务员类

public class WaiterDeferredImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        System.out.println("WaiterDeferredImportSelector invoke ....");
        return new String[]{Waiter.class.getName()};
    }
}

写好后,同样在@EnableTavern注解中的@Import上添加WaiterDeferredImportSelector的导入

EnableTavern注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class, WaiterRegistrar.class, WaiterDeferredImportSelector.class})
public @interface EnableTavern {
}

测试看控制的打印

image-20231107001224904

可以发现,**DeferredImportSelector的执行时机比ImportSelector的晚,比ImportBeanDefinitionRegistrar早,**为什么要这么设计,可以看看后面的Condition条件装配

【都看到这了,点点赞点点关注呗,爱你们】😚😚

抽象工厂  引导关注

结语

谢谢你的阅读,由于作者水平有限,难免有不足之处,若读者发现问题,还请批评,在留言区留言或者私信告知,我一定会尽快修改的。若各位大佬有什么好的解法,或者有意义的解法都可以在评论区展示额,万分谢谢。
写作不易,望各位老板点点赞,加个关注!😘😘😘

💬

作者:后端小知识CSDN后端领域新星创作者|阿里云专家博主

CSDN个人主页:后端小知识

🔎GZH后端小知识

🎉欢迎关注🔎点赞👍收藏⭐️留言📝

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

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

相关文章

Cocos 进度条物体跟随效果

话不多说上代码&#xff0c;记录一下初学cocos解决的问题&#xff0c;实用小功能。 import { _decorator, Button, Component, Node, ProgressBarComponent, Sprite, UITransform, Vec3 } from cc; const { ccclass, property } _decorator;ccclass(game_scene1) export clas…

容联七陌携手岚时科技,解决医美机构回访3大痛点

近日&#xff0c;岚时科技研发中心联合容联七陌发布了全新的智能呼叫中心系统&#xff0c;5大功能模块解决了医美机构回访过程中的3大难题&#xff1a;客户资产保全困难、客户回访技术被卡脖子、回访人员&#xff08;客服、咨询&#xff09;效率管理困难。 “智能呼叫中心”通过…

ZKP8.1 Polynomial-IOP and Polynomial Commitment Schemes

ZKP学习笔记 ZK-Learning MOOC课程笔记 Lecture 8: FRI-based Polynomial Commitments and Fiat-Shamir (Justin Thaler) 8.1 Polynomial-IOP and Polynomial Commitment Schemes Recall: build an efficient SNARK Recall: Polynomial-IOP P’s first message in the pro…

Freeswitch代码

1.引入依赖 Freeswitch依赖版本 <dependency><groupId>org.freeswitch.esl.client</groupId><artifactId>esl-client</artifactId><version>0.10.1</version> </dependency> 2.代码 import org.freeswitch.esl.client.inbound…

分享导致vcruntime140_1.dll丢失的原因,以及vcruntime140_1.dll的解决办法

电脑中缺失vcruntime140_1.dll文件的问题&#xff0c;是常有的问题&#xff0c;不仅仅是vcruntime140_1.dll文件&#xff0c;还会有很多的其他的dll文件都会出现这样的问题。今天就和大家聊聊vcruntime140_1.dll文件丢失的情况&#xff0c;和什么原因导致的丢失vcruntime140_1.…

【波形图】在波形图上显示相对时间并将原点设置为零

默认情况下&#xff0c;波形图有一个定义的窗口来显示 X 轴。当X值达到该窗口的上限时&#xff0c;图表开始滚动以显示最新值的窗口。为了防止这种情况&#xff0c;请通过右键单击图表并选择X Scale Autoscale X将 X 轴设置为自动缩放。此操作还将增加窗口的大小以显示从 0 到…

基于SSM的智慧作业试题管理系统(有报告)。Javaee项目。

演示视频&#xff1a; 基于SSM的智慧作业试题管理系统&#xff08;有报告&#xff09;。Javaee项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Sprin…

样式问题解决

1.深度样式选择器 1.vue2中 原生css >>> .el-card__header saas\scss ::v-deep .el-card__header less /deep/ .el-card__header 2.vue3中 :deep() { //styles } ::deep() { //styles } 2.修改element.style样式

【uniapp】解决在H5谷歌浏览器下 u-input 标签 设置只读后,click事件不生效

【问题描述】 谷歌浏览器更新后&#xff0c;h5模式下原本的input外层view中的click事件不触发了?? 但是更换浏览器后就可以&#xff0c;打包app也是正常可以触发的&#xff0c;本来是没打算兼容h5&#xff0c;既然遇到了就记录一下~ 【解决办法】 使u–input里写上readonly&…

python随机生成指定长度的字符串

需求&#xff1a;随机生成一个指定长度的字符串&#xff08;数字和小写字母&#xff09; 涉及到的python知识点 &#xff08;1&#xff09;python模块包&#xff1a;random random.choice(sequence)&#xff1a;从指定的序列中获取一个随机元素 random.choice(sequence)从序…

GoLong的学习之路(二十一)进阶,语法之并发(go最重要的特点)(协程的主要用法)

并发编程在当前软件领域是一个非常重要的概念&#xff0c;随着CPU等硬件的发展&#xff0c;我们无一例外的想让我们的程序运行的快一点、再快一点。Go语言在语言层面天生支持并发&#xff0c;充分利用现代CPU的多核优势&#xff0c;这也是Go语言能够大范围流行的一个很重要的原…

AI 视频 | 文本生视频工具又迎来重大更新,Runway Gen-2 到底有多强?Gen-2 怎么用(保姆级教程)

一、引言 不久前刚介绍了一个号称地表最强的文本生视频的工具 Moonvalley&#xff1a;免费的 AI 视频生成工具 Moonvalley 厉害了&#xff01;Moonvalley 怎么用&#xff08;保姆级教程&#xff09; 紧接着在 11 月 2 日&#xff0c;Runway 重磅发布了第 2 代文本到视频和图像…

unity打AB包,AssetBundle预制体与图集(三)

警告&#xff1a; spriteatlasmanager.atlasrequested wasn’t listened to while 条件一&#xff1a;图片打图集里面去了 条件二&#xff1a;然后图集打成AB包了 条件三&#xff1a;UI预制体也打到AB包里面去了 步骤一&#xff1a;先加载了图集 步骤二&#xff1a;再加载UI预…

测试面试题集锦(四)| Linux 与 Python 编程篇(附答案)

本系列文章总结归纳了一些软件测试工程师常见的面试题&#xff0c;主要来源于个人面试遇到的、网络搜集&#xff08;完善&#xff09;、工作日常讨论等&#xff0c;分为以下十个部分&#xff0c;供大家参考。如有错误的地方&#xff0c;欢迎指正。有更多的面试题或面试中遇到的…

四川思维跳动商务信息咨询有限公司可靠吗?

随着抖音等短视频平台的兴起&#xff0c;越来越多的商家开始利用这些平台进行带货。四川思维跳动商务信息咨询有限公司也提供抖音带货服务&#xff0c;那么&#xff0c;他们的服务可靠吗&#xff1f;本文将为你揭开真相&#xff0c;让你安心选择&#xff01; 一、公司简介 四川…

分布式任务调度(04)--自研

1 背景 兼容技术团队自研的RPC框架&#xff0c;技术团队不需要修改代码&#xff0c;RPC注解方法可以托管在任务调度系统中&#xff0c;直接当做一个任务来执行。 研读XXL-JOB&#xff0c;同时从阿里云分布式任务调度 SchedulerX 吸取。 SchedulerX 1.0 架构图 Schedulerx-co…

MCU常见通信总线串讲(二)—— RS232和RS485

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言一…

Word文档中书签使用注意事项

在协同编辑文档时&#xff0c;书签被广泛应用在各种场景中&#xff0c;当我们在编辑时有时候会搞不懂我们的哪些操作会导致书签更换段落或是删除&#xff0c;比如同样的某些操作在修订场景下书签就不会消失&#xff0c;但非修订状态就会消失。在这篇文章中我们就介绍一下我们的…

数据中心的防雷接地

在数据中心建设中&#xff0c;防雷接地是非常重要的一项工作&#xff0c;能有效地保护数据中心设备和系统免受雷击的影响。下面是一些关于数据中心防雷接地的常见做法&#xff1a; 接地系统设计&#xff1a;在数据中心的设计阶段&#xff0c;应考虑到接地系统的布置和规划。为了…

Halcon如何使用SaperaLT库连接dalsa相机

halcon安装好的时候&#xff0c;没有带SaperaLT的采集库&#xff0c;需要额外在Halcon官网下载此库。 以下是halcon官网下载此库的链接。官网需要注册才可以下载。 https://www.mvtec.com/downloads/interfaces?tx_mvtecproduct_extensiondownloadlist%5Bfilter%5D%5B0%5Dma…