深入理解 Spring @Import 不同方式注册 Bean

简介: 深入理解 Spring @Import 不同方式注册 Bean

每日一言


苦,是人生的必经过程。人生就是一个 "享受" 痛苦和磨难的过程,这个过程是值得体会和拥有的

前言


Spring 在 3.0 版本之前都是通过 .xml 配置文件的形式来描述配置信息

在配置文件中显示声明 bena 标签或者扫描特定包下的类来注册 IOC 容器 Bean 对象等操作

@Import 是 Spring 3.0 之后通过 JavaConfig 方式提供注册 IOC Bean 的注解

需要配合 @Configuration 注解共同使用才能起作用,因为只有这样才会被 Spring 加载时扫描到

小伙伴阅读文章将收获:

  1. @Import 日常使用方法
  2. ImportBeanDefinitionRegistrar 配合 @Import 注册 Bean
  3. ImportSelector 配合 @Import 注册 Bean
  4. JavaConfig 方式引入 spring.xml 配置文件注册 Bean

@Import 使用


先来看一下源码里是如何介绍这个小可爱的

@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();

}

源码类注释太长就不贴出来水字数了, 通过贴心的翻译软件,结论如下:

  1. 表示要导入一个或多个普通、配置类(@Configuration ),将导入对象转换为 Spring IOC Bean
  2. 支持 ImportBeanDefinitionRegistrar、ImportSelector 针对需要注册的 Bean 做更细致的填充
  3. 如果使用的是 .xml 配置文件的形式注册 Bean,那么可以使用 @ImportResource
  4. 通过导入注册的 Bean 对象可以正常被 @Autowired 注入
微信搜索【源码兴趣圈】,关注龙台,回复【资料】领取涵盖 GO、Netty、SpringCLoud Alibaba、Seata、开发规范、面试宝典、数据结构等电子书 or 视频学习资料!

先来简单看一下如何使用 @Import 来注册 Bean

@Configuration
@Import(ImportBeanTest.ServiceBean.class)
public class ImportBeanTest implements ApplicationContextAware, BeanFactoryPostProcessor {
    
  private ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("注册 ServiceBean 对象 :: " + applicationContext.getBean(ServiceBean.class));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    static class ServiceBean {}
}

上面程序为了偷懒,直接使用了两个 Spring 接口,简单介绍下各自的作用

  • ApplicationContextAware:为了获取 ApplicationContext 上下文对象,用来应该被注册的 Bean 实例
  • BeanFactoryPostProcessor:后置处理器,程序用在查询 Bean 是否被注册 IOC 容器

这个使用了 @Import 注解的程序流程如下:

  1. 类标记 @Configuration 表示是配置类,并通过 @Import 导入 ServiceBean
  2. 通过 ApplicationContextAware 接口获取 ApplicationContext 上下文
  3. 在后置处理器中,查看 ServiceBean 是否被注册成功

如果项目启动后能够正常打印对象信息,即注册成功

@Configuration


小伙伴这次可以看到,@Import 中导入的类是被 @Configuration 所修饰的,也就意味着导入了一个配置类,同时其下包含多个 @Bean 方法

配置类中相关的 Bean 同时也会被加载

@Configuration
@Import(ImportConfigurationTest.ImportConfiguration.class)
public class ImportConfigurationTest implements ApplicationContextAware, BeanFactoryPostProcessor {

    private ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("注册 ServiceBeanA 对象 :: " + applicationContext.getBean(ServiceBeanA.class));
        System.out.println("注册 ServiceBeanB 对象 :: " + applicationContext.getBean(ServiceBeanB.class));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Configuration
    static class ImportConfiguration {

        @Bean
        public ServiceBeanA getServiceBeanA() {return new ServiceBeanA();}

        @Bean
        public ServiceBeanB getServiceBeanB() {return new ServiceBeanB();}
    }

    static class ServiceBeanA {} static class ServiceBeanB {}
}

导入 @Configuration 和导入普通 Java Class 对象作用是一致的,相比于有两点好处:

  1. 创建 Bean 时定义对应的方法以及属性值等操作
  2. 将同一类型的 Bean 归结到一起,方便代码编写

ImportSelector


ImportSelector 是一个接口,通过 @Import 进行导入,和 @Configuration 修饰类相似

两者作用域都是将多个类进行注入 IOC 容器,不同的是 ImportSelector 是将类的全限定名当作 IOC 容器的 ID

@Configuration
@Import(ImportSelectorTest.RegistryConfig.class)
public class ImportSelectorTest implements ApplicationContextAware, BeanFactoryPostProcessor {

    private ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("注册 ServiceBeanA 对象 :: " + applicationContext.getBean(ServiceBeanA.class));
        System.out.println("注册 ServiceBeanB 对象 :: " + applicationContext.getBean(ServiceBeanB.class));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    static class RegistryConfig implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{ServiceBeanA.class.getName(), ServiceBeanB.class.getName()};
        }
    }

    static class ServiceBeanA {} static class ServiceBeanB {}
}

selectImports 方法中的入参,存放的都是 类元信息,通过已序列化的 JSON 串看一下

另外通过后置处理器中,我们可以看到相关类已被注册,ID即是类全限定名称

ImportBeanDefinitionRegistrar


ImportBeanDefinitionRegistrar 是一个接口,@Import 中可以加入此接口的实现

@Configuration
@Import(ImportBeanDefinitionRegistrarTest.RegistrarConfig.class)
public class ImportBeanDefinitionRegistrarTest implements ApplicationContextAware, BeanFactoryPostProcessor {

    private ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("注册 ServiceBean 对象 :: " + applicationContext.getBean(ServiceBean.class));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    static class RegistrarConfig implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            BeanDefinition beanDefinition = BeanDefinitionBuilder
                    .genericBeanDefinition(ServiceBean.class)
                    .getBeanDefinition();
            registry.registerBeanDefinition("serviceBean", beanDefinition);
        }
    }

    static class ServiceBean {}
}

可以看到 registerBeanDefinitions 方法入参中不仅有类元信息,同时还包含 Bean 注册接口

同时可以为属性值、构造方法参数值以及更多实现信息进行赋值,不仅仅是示例代码中那么简单

@ImportResource


如果说,你在用 Spring 或 SpringBoot 项目,需要使用 JavaConfig 这种方式来导入 .xml 文件运行

@ImportResource 绝对是救命稻草,但是作者认为 SpringBoot 项目更适合 JavaConfig 的方式进行配置

registry.xml

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="serviceBean"
          class="cn.machen.study.springstudy.bean.importx.ImportResourceTest.ServiceBean" />

</beans>

ImportResourceTest.java

@Configuration
@ImportResource(locations = "classpath:registry.xml")
public class ImportResourceTest implements ApplicationContextAware, BeanFactoryPostProcessor {

    private ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("注册 ServiceBean 对象 :: " + applicationContext.getBean(ImportResourceTest.ServiceBean.class));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    static class ServiceBean {}
}

结言


文章从最开始的 @Import 开始,讲述了注册普通 Java 类、@Configuration 修饰类、ImportSelector、ImportBeanDefinitionRegistrar 等方式以 JavaConfig 的形式注册 IOC Bean,同时也介绍了通过 @ImportResource 导入 .xml 配置文件的方式配置,希望在看的小伙伴都有所收获

推荐阅读:

  1. 【强烈推荐】谨慎使用 JDK 8 新特性并行流 ParallelStream
  2. 【强烈推荐】一文快速掌握 Redisson 如何实现分布式锁原理
  3. 【大厂面试真题】JDK 线程池中如何不超最大线程数快速消费任务
  4. 【大厂面试真题】JDK 线程池如何保证核心线程不被销毁
相关文章
|
11天前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
1月前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
66 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
29天前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
1月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
73 1
|
1月前
|
Java Spring
获取spring工厂中bean对象的两种方式
获取spring工厂中bean对象的两种方式
38 1
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
163 2
|
3月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
9天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
20 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
5天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
17 2