【小家Spring】Spring向容器注册Bean的高级应用:@Import、DeferredImportSelector、ImportBeanDefinitionRegistrar的使用(上)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 【小家Spring】Spring向容器注册Bean的高级应用:@Import、DeferredImportSelector、ImportBeanDefinitionRegistrar的使用(上)

前言


关于@Import在之前的文章里,也都零散的提到过多次,也支出了它的重要性,甚至它的一个解析过程。


但是由于@Import模式向容器导入Bean确实非常非常的重要,特别是在注解驱动的Spring项目中、@Enablexxx的设计模式中有大量的使用,在当下最流行的Spring Boot中,可以说作为设置是最重要的一种方式,来做底层抽象、组件式的设计。


比如我们熟悉的:@EnableAsync、@EnableAspectJAutoProxy、@EnableMBeanExport、@EnableTransactionManagement…等等统一采用的都是借助@Import注解来实现的


本篇文章旨在着眼于对@Import的使用上,以及结合ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar这三个接口的一些高级应用~


需要注意的是:ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar这三个接口都必须依赖于@Import一起使用,而@Import可以单独使用

基本环境如下



@ComponentScan(value = "com.fsx", excludeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
        //排除掉web容器的配置文件,否则会重复扫描
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {AppConfig.class}),
})
@Configuration
public class RootConfig {
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
    @Autowired
    private ApplicationContext applicationContext;
    @Test
    public void test1() {
        Arrays.stream(applicationContext.getBeanDefinitionNames())
                .filter(x -> !x.contains(".internal")) //过滤调用Spring内部给我们默认注册Bean,方便我们查看结果
                .forEach(System.out::println);
  // 当前输出两个Bean:rootConfig、helloServiceImpl
    }
}


说一句,采用SpringJUnit4ClassRunner为我们自动创建的容器,为GenericApplicationContext这个类型的容器。Bean工厂为DefaultListableBeanFactory。


关于GenericApplicationContext的使用,相当来说是都需要手动的,比如根据配置类加载Bean、刷新容器等等。。。这里面Spring的Spring-test包都帮我们把这些事做了~



目前这种容器(只能测试Service、Dao),不能测试Controller(web环境)下的Bean或者接口,因为junit这不是web环境,是不会启动web容器的。(毕竟缺少相关tomcat环境 jar包等等),若想测试web环境,请增加这么处理


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, AppConfig.class})
@WebAppConfiguration //创建web容器,这样就能初始化AppConfig配置类,也能记载进来web先关的Bean了(比如Spring MVC的九大组件等等)
public class TestSpringBean { ... }


至于Controller层的接口怎么通过url方式去请求测试 ,可以结合MockMvc来mock测试。具体的使用方式,这里就不多介绍了~(Junit不会有父子容器的概念。。。

@Import注解


这里讲述单独使用@Import的例子,使用它有一个非常方便的地方在于:它可以导入Jar包里面的类(因为我们的@ComponentScan是不会扫描jar包的),可以看看下面这个例子:

//@ComponentScan 部分省略,下同
@Configuration
@Import({Parent.class,
        // 这是Spring-code包里面的Bean,我随便找的一个
        AntPathMatcher.class})
public class RootConfig {
}


打印输出如下: 我们成功的向容器内注入了这些Bean,并且BeanName为全类名


rootConfig
helloServiceImpl
com.fsx.bean.Parent
org.springframework.util.AntPathMatcher


那么,若我不把它放在@Configuration上,而是放在一个普通的@Component组件上呢?比如我们放在HelloServiceImpl上:


@Service
@Import({Parent.class, AntPathMatcher.class})
public class HelloServiceImpl implements HelloService { ... }

我们发现,效果是相同的。(其实如果你阅读过之前的@Configuration的解析过程,就能知道不管是Lite模式还是Full模式,这里导入Bean方面都是一样的)

参见ConfigurationClassParser#parse/processConfigurationClass方法


虽然放哪个组件都行,但在实际开发中,我们一般约定都放在@Configuration配置文件里

ImportSelector和DeferredImportSelector


使用@Import的时候,它的类可以是实现了ImportSelector或者DeferredImportSelector接口的类。

Spring容器会实例化这个实现类,并且执行其selectImports方法(执行时机,下面会源码分析)


我们先来看一个ImportSelector的Demo Show:

public class MyImportSelector implements ImportSelector
        //虽然不能@Autowired,但是实现了这些接口是可以感知到的,下面看源码会发现,Spring会给他注入进去
        // 这样我们就可以根据特定的条件,来决定某些Bean能注入,有些Bean不能注入了
        //,BeanClassLoaderAware,BeanFactoryAware,EnvironmentAware,ResourceLoaderAware
{
    // 备注:这里各种@Autowired的注入都是不生效的,都是null
    // 了解Spring容器刷新过程的时候就知道,这个时候还没有开始解析@Autowired,所以肯定是不生效的
    @Autowired
    private HelloService helloService;
    /**
     * 容器在会在特定的时机,帮我们调用这个方法,向容器里注入Bean信息
     *
     * @param importingClassMetadata 包含配置类上面所有的注解信息,以及该配置类本身
     *                               若有需要,可以根据这些其它注解信息,来判断哪些Bean应该注册进去,哪些不需要
     * @return 返回String数组,注意:都必须是类的全类名,才会被注册进去的(若你返回的全类名不存在该类,容器会抛错)
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        System.out.println("this MyImportSelector...");
        //return new String[]{"com.fsx.bean.Child"};
        // 一般建议这么玩 用字符串写死的方式只是某些特殊场合(比如这个类不一定存在之类的。。。)
        return new String[]{Child.class.getName()};
    }
}


输出如下:


rootConfig
helloServiceImpl
com.fsx.bean.Parent
org.springframework.util.AntPathMatcher
com.fsx.bean.Child


这里我提供提一个Spring的默认实现AdviceModeImportSelector(它通过解析注解信息,选择合适的Bean加入),大家可以供以参考。 这个类在后续分析事物的原理以及AOP的原理的时候,会再次见面~


再来一个DeferredImportSelector的Demo Show:


public class MyDeferredImportSelector implements DeferredImportSelector {
    // 同样的,它也只需要实现这个方法即可 但是它还提供了一些更高级的功能
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        System.out.println("this MyDeferredImportSelector...");
        // 这里面若容器里已经有名为`com.fsx.bean.Child`的Bean,就不会再注册进去了的
        return new String[]{"com.fsx.bean.Child"};
    }
}


我们发现使用方式几乎一样,真的一样吗?其实容器启动的时候还有一个细节输出:


this MyImportSelector...
this MyDeferredImportSelector...


从现象已经和名字中,我们能够更加直观的看出来:DeferredImportSelector显然是属于延迟加载、靠后加载的,那到底有多延迟,他们执行时机都是啥时候呢? 这就是我们接下来讨论的重点


再次强调一次:实现此接口的Bean必须是放在@Import进去的才会生效,而不能直接@Bean加入进去


ImportSelector和DeferredImportSelector的区别:


看了看DeferredImportSelector类的JavaDoc,得到如下信息:


  1. DeferredImportSelector是ImportSelector的一个扩展


  1. ImportSelector实例的selectImports方法的执行时机,是在@Configguration注解中的其他逻辑被处理**之前**,所谓的其他逻辑,包括对@ImportResource、@Bean这些注解的处理(注意,这里只是对@Bean修饰的方法的处理,并不是立即调用@Bean修饰的方法,这个区别很重要!);


  1. DeferredImportSelector实例的selectImports方法的执行时机,是在@Configguration注解中的其他逻辑被处理**完毕之后*


  1. DeferredImportSelector的实现类可以用Order注解,或者实现Ordered接口来对selectImports的执行顺序排序(ImportSelector不支持)


  1. ImportSelector是Spring3.1提供的,DeferredImportSelector是Spring4.0提供的


  1. Spring Boot的自动配置功能就是通过DeferredImportSelector接口的实现类EnableAutoConfigurationImportSelector做到的(因为自动配置必须在我们自定义配置后执行才行)


分析Spring源码中对此两个接口的处理


结合这篇博文【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)分析,public void parse(Set<BeanDefinitionHolder> configCandidates){ ... }的最最最后一步,才去处理实现了DeferredImportSelector接口的类,因此是非常滞后的(此事已经处理好了@Bean、@ComponentScan、@ImportResource等等事宜)


ImportSelector 被设计成其实和@Import注解的类同样的导入效果,但是实现 ImportSelector的类可以条件性地决定导入哪些配置。

DeferredImportSelector 的设计目的是在所有其他的配置类被处理后才处理。这也正是该语句被放到本函数最后一行的原因。


看看前面做了些什么,我们直接来到核心处理方法doProcessConfigurationClass:(此处稍微详细点)

相关文章
|
1月前
|
安全 Java 网络安全
当网络安全成为数字生活的守护者:Spring Security,为您的应用筑起坚不可摧的防线
【9月更文挑战第2天】在数字化时代,网络安全至关重要。本文通过在线银行应用案例,详细介绍了Spring Security这一Java核心安全框架的核心功能及其配置方法。从身份验证、授权控制到防御常见攻击,Spring Security提供了全面的解决方案,确保应用安全。通过示例代码展示了如何配置`WebSecurityConfigurerAdapter`及`HttpSecurity`,帮助开发者有效保护应用免受安全威胁。
55 4
|
10天前
|
Java 对象存储 开发者
故障隔离与容错处理:Hystrix在Spring Cloud和Netflix OSS中的应用
故障隔离与容错处理:Hystrix在Spring Cloud和Netflix OSS中的应用
26 3
|
16天前
|
Kubernetes Cloud Native Java
当 Quarkus 遇上 Spring Boot,谁才是现代云原生应用的终极之选?究竟哪款能助你的应用傲视群雄?
Quarkus 和 Spring Boot 均为构建现代云原生应用的热门框架,旨在简化开发流程并提升性能。Spring Boot 依托庞大的 Spring 生态系统,提供开箱即用的体验,适合快速搭建应用。Quarkus 由红帽发起,专为 GraalVM 和 HotSpot 设计,强调性能优化和资源消耗最小化,是云原生环境的理想选择。
15 3
|
1月前
|
IDE Java 开发工具
还在为繁琐的配置头疼吗?一文教你如何用 Spring Boot 快速启动,让开发效率飙升,从此告别加班——打造你的首个轻量级应用!
【9月更文挑战第2天】Spring Boot 是一款基于 Spring 框架的简化开发工具包,采用“约定优于配置”的原则,帮助开发者快速创建独立的生产级应用程序。本文将指导您完成首个 Spring Boot 项目的搭建过程,包括环境配置、项目初始化、添加依赖、编写控制器及运行应用。首先需确保 JDK 版本不低于 8,并安装支持 Spring Boot 的现代 IDE,如 IntelliJ IDEA 或 Eclipse。
87 5
|
2月前
|
安全 NoSQL Java
JeecgBoot应用Spring Authorization Server
Spring Authorizaiton Server, 简称 sas,是一个授权服务器框架,提供 OAuth2.1 与 Open Connect 1.0 认证规范及其他规范的实现,它建立在 Spring Security 之上,为构建 OpenID Connect 1.0 Identity Provider 和 OAuth2 授权服务器产品提供了一个安全、轻量级和可定制的基础
38 2
|
2月前
|
Java Spring 监控
Spring Boot Actuator:守护你的应用心跳,让监控变得触手可及!
【8月更文挑战第31天】Spring Boot Actuator 是 Spring Boot 框架的核心模块之一,提供了生产就绪的特性,用于监控和管理 Spring Boot 应用程序。通过 Actuator,开发者可以轻松访问应用内部状态、执行健康检查、收集度量指标等。启用 Actuator 需在 `pom.xml` 中添加 `spring-boot-starter-actuator` 依赖,并通过配置文件调整端点暴露和安全性。Actuator 还支持与外部监控工具(如 Prometheus)集成,实现全面的应用性能监控。正确配置 Actuator 可显著提升应用的稳定性和安全性。
55 0
|
2月前
|
测试技术 Java Spring
Spring 框架中的测试之道:揭秘单元测试与集成测试的双重保障,你的应用真的安全了吗?
【8月更文挑战第31天】本文以问答形式深入探讨了Spring框架中的测试策略,包括单元测试与集成测试的有效编写方法,及其对提升代码质量和可靠性的重要性。通过具体示例,展示了如何使用`@MockBean`、`@SpringBootTest`等注解来进行服务和控制器的测试,同时介绍了Spring Boot提供的测试工具,如`@DataJpaTest`,以简化数据库测试流程。合理运用这些测试策略和工具,将助力开发者构建更为稳健的软件系统。
39 0
|
2月前
|
Java Spring UED
Spring框架的异常处理秘籍:打造不败之身的应用!
【8月更文挑战第31天】在软件开发中,异常处理对应用的稳定性和健壮性至关重要。Spring框架提供了一套完善的异常处理机制,包括使用`@ExceptionHandler`注解和配置`@ControllerAdvice`。本文将详细介绍这两种方式,并通过示例代码展示其具体应用。`@ExceptionHandler`可用于控制器类中的方法,处理特定异常;而`@ControllerAdvice`则允许定义全局异常处理器,捕获多个控制器中的异常。
38 0
|
2月前
|
Java Spring 监控
危机时刻,Spring框架如何拯救你的应用?深入探讨健康检查与自我修复功能
【8月更文挑战第31天】在现代软件架构中,应用的稳定性和可用性至关重要。本文介绍Spring框架中的健康检查与自我修复机制,通过Spring Boot Actuator的`/health`端点监控应用状态,并结合Spring Cloud Hystrix实现服务容错和断路器功能,提高应用健壮性。借助这些工具,开发者能轻松监控应用健康状况并在发现问题时自动采取措施,确保服务高可用性。要实现完善的机制,需根据具体应用架构和需求进行配置和扩展。
40 0
|
2月前
|
Java Spring 开发者
Java Web开发新潮流:Vaadin与Spring Boot强强联手,打造高效便捷的应用体验!
【8月更文挑战第31天】《Vaadin与Spring Boot集成:最佳实践指南》介绍了如何结合Vaadin和Spring Boot的优势进行高效Java Web开发。文章首先概述了集成的基本步骤,包括引入依赖和配置自动功能,然后通过示例展示了如何创建和使用Vaadin组件。相较于传统框架,这种集成方式简化了配置、提升了开发效率并便于部署。尽管可能存在性能和学习曲线方面的挑战,但合理的框架组合能显著提升应用开发的质量和速度。
33 0
下一篇
无影云桌面