⭐简单说两句⭐
作者:后端小知识,CSDN后端领域新星创作者|阿里云专家博主
CSDN个人主页:后端小知识
GZH:
后端小知识
欢迎关注点赞收藏⭐️留言
本系列文章是Spring学习笔记,参考书**《SpringBoot源码解读与原理分析》**
阅读的一个小要求:至少会使用SpringBoot或Spring或者天才型选手
本系列文章将会持续更新,更新速度取决于各位读者大大的点赞收藏和留言
为了方便读者了解文章整体内容,我给出了一张思维导图,希望有所帮助
Spring框架是一个轻量级的Java企业级应用开发框架,它提供了一种简化企业级应用开发的方法。Spring框架的核心是依赖注入(DI)和面向切面编程(AOP),这两个特性使得开发者可以更容易地构建和管理复杂的企业级应用。
在Spring框架中,模块装配主要是指将各个模块(组件)组合在一起,形成一个完整的应用。Spring框架提供了多种方式来实现模块装配,以下是一些常见的方法:
Spring框架提供了多种模块装配方式,开发者可以根据项目需求和团队习惯选择合适的方式进行模块装配。随着Spring框架的不断发展,模块装配的方式也在不断简化,使得开发者可以更加专注于业务逻辑的实现。
在Spring框架中,模块(Module)通常是指一个功能独立的组件,它负责处理特定的业务逻辑或提供特定的功能。模块可以是一个类、一个服务接口、一个数据访问对象(DAO)等。模块之间可以通过依赖注入(DI)和面向切面编程(AOP)等技术进行组合和扩展,从而构建出一个完整的企业级应用。
在Spring框架中,模块通常具有以下特点:
在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);
}
}
运行结果如图
看运行结果,我们可以发现使用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);
});
}
}
运行结果如图
通过看运行结果,可以发现控制台成功打印两个调酒师对象,说明注解配置类的装配正确完成。
【Tips】:注意,BartenderConfiguration配置类也被注册到了IOC容器中,并成为一个Bean
通过IDEA可以看到ImportSelector是一个接口,这个接口可以导入配置类,也可以导入普通类
声明吧台类
public class Bar {
}
BarConfiguration配置类中注册Bar
@Configuration
public class BarConfiguration {
@Bean
public Bar bbar(){
return new Bar();
}
}
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);
});
}
}
ImportSelector的核心是可以使开发者采用更灵活的声明式向IOC容器中注册bean,其重点是可以灵活的指定要注册的Bean的类,正因为传入的是全限定名的字符串,那么如果这些全限定名以配置文件的形式存放在项目可以读取的位置,是不是就可以避免导入组件的硬编码问题了?在SpringBoot自动装配中,底层就是利用了ImportSelector,实现从spring.factories文件中读取自动配置类。
如果说ImportSelector是以声明式导入组件,那么ImportBeanDefinitionRegistrar可以解释为以编程式向IOC容器中注册Bean对象,实际导入的是BeanDefinition(Bean的定义信息)
服务员类
public class Waiter {
}
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);
});
}
}
【Tips】: WaiterRegistrar没有被注册到IOC容器中
DeferredImportSelector 是 Spring 提供的一个接口(ImportSelector的子接口),用于在运行时动态导入额外的配置类。它与 AutoConfigurationImportSelector 的作用类似,但在应用程序初始化阶段不导入所有配置,而是在需要时才导入额外的配置。使用 DeferredImportSelector 需要创建实现该接口的类,并在应用程序中通过 @Import 注解引入。通过实现 selectImports 方法,可以根据当前应用程序环境和需求动态选择和导入额外的配置类。
实现 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 {
}
测试看控制的打印
可以发现,**DeferredImportSelector的执行时机比ImportSelector的晚,比ImportBeanDefinitionRegistrar早,**为什么要这么设计,可以看看后面的Condition条件装配
【都看到这了,点点赞点点关注呗,爱你们】
结语
谢谢你的阅读
,由于作者水平有限,难免有不足之处,若读者发现问题,还请批评,在留言区留言或者私信告知,我一定会尽快修改的。若各位大佬有什么好的解法,或者有意义的解法都可以在评论区展示额,万分谢谢。
写作不易,望各位老板点点赞,加个关注!
作者:后端小知识,CSDN后端领域新星创作者|阿里云专家博主
CSDN个人主页:后端小知识
GZH:后端小知识
欢迎关注点赞收藏⭐️留言