组合Java配置

在XML中,我们可以使用<import/>标签,在一个XML文件中引入另一个XML文件,在Java类中,我们同样可以在一个配置类中用@Import引入另一个配置类,被引入的配置类中的@Bean也会加载到spring容器。代码如下:

  1. @Configuration
  2. public class ConfigA {
  3.  
  4. @Bean
  5. public A a() {
  6. return new A();
  7. }
  8. }
  9.  
  10. @Configuration
  11. @Import(ConfigA.class)
  12. public class ConfigB {
  13.  
  14. @Bean
  15. public B b() {
  16. return new B();
  17. }
  18. }

  

我们将ConfigB作为配置类传给AnnotationConfigApplicationContext进行spring容器初始化,我们不但可以从spring容器中获得A和B所对应的bean,还可以得到ConfigA和ConfigB所对应的bean:

  1. public static void main(String[] args) {
  2. ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
  3.  
  4. A a = ctx.getBean(A.class);
  5. B b = ctx.getBean(B.class);
  6. ConfigA configA = ctx.getBean(ConfigA .class);
  7. ConfigB configB = ctx.getBean(ConfigB.class);
  8. }

  

事实上,@Import并不要求我们引入一个配置类,我们也可以引入一个普通的类,spring容器同样会帮我们把这个类初始化成一个bean:

  1. package org.example.config;
  2.  
  3. import org.example.beans.D;
  4. import org.springframework.context.annotation.Import;
  5.  
  6. @Import(D.class)
  7. public class MyConfig3 {
  8. }
  9.  
  10. package org.example.beans;
  11.  
  12. public class D {
  13. }

  

测试用例:

  1. @Test
  2. public void test10() {
  3. AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig3.class);
  4. System.out.println(ac.getBean(D.class));
  5. }

  

运行结果:

  1. org.example.beans.D@134593bf

  

利用@Import,我们可以把一些第三方的插件引入spring容器初始化为bean,当然,我们也可以在一个方法里返回插件的实例,方法用@Bean注解标注。不过,笔者认为,用@Import或者@Bean来引入bean还是分情况的,如果是引入一个类似DataSource的数据源的bean,我们要在方法里指定数据源的地址、用户名、密码,那么我们可以用@Bean,如果我们需要引入的第三方插件不需要设置额外的属性,则可以用@Import。

我们还可以用@Import来引入一个ImportBeanDefinitionRegistrar接口的实现,spring容器会帮我们回调ImportBeanDefinitionRegistrar接口的实现,在ImportBeanDefinitionRegistrar接口的实现内,我们可以往spring容器注册一个BeanDefinition,spring容器会根据BeanDefinition生成一个bean,BeanDefinition用来描述一个bean的class对象,比如这个bean的class是什么?作用域是单例还是原型?是否懒加载?等等。几乎可以认为,只要是存在于spring容器中的bean,都会有一个对应的BeanDefinition来描述这个bean。我们先来看下BeanDefinition接口的部分方法:

  1. package org.springframework.beans.factory.config;
  2. ……
  3. public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
  4. ……
  5. //设置bean所对应的class
  6. void setBeanClassName(@Nullable String beanClassName);
  7. //获取bean所对应的class
  8. String getBeanClassName();
  9. //设置bean的作用域
  10. void setScope(@Nullable String scope);
  11. //获取bean的作用域
  12. String getScope();
  13. //设置bean是否懒加载
  14. void setLazyInit(boolean lazyInit);
  15. //判断bean是否懒加载
  16. boolean isLazyInit();
  17. //设置bean在初始化前需要的依赖项
  18. void setDependsOn(@Nullable String... dependsOn);
  19. //获取bean在初始化前需要的依赖项
  20. String[] getDependsOn();
  21. ……
  22. }

  

ImportBeanDefinitionRegistrar可以让我们重写两个方法,从方法名可以看出,注册BeanDefinition,第一个方法仅比第二个多一个参数importBeanNameGenerator,为beanName生成器。

  1. public interface ImportBeanDefinitionRegistrar {
  2. default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
  3. BeanNameGenerator importBeanNameGenerator) {
  4.  
  5. registerBeanDefinitions(importingClassMetadata, registry);
  6. }
  7.  
  8. default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  9. }
  10. }

  

我们在MyImportBeanDefinitionRegistrar里重写了registerBeanDefinitions,由于BeanDefinition是接口,所以我们用spring编写好的实现类GenericBeanDefinition进行注册,我们声明了一个GenericBeanDefinition后并设置其对应的属性,然后将beanName和BeanDefinition注册进registry:

  1. package org.example.beans;
  2.  
  3. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  4. import org.springframework.beans.factory.support.GenericBeanDefinition;
  5. import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
  6. import org.springframework.core.type.AnnotationMetadata;
  7.  
  8. public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  9. @Override
  10. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  11. GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
  12. beanDefinition.setBeanClass(D.class);
  13. registry.registerBeanDefinition("d", beanDefinition);
  14. }
  15. }
  16.  
  17. package org.example.beans;
  18.  
  19. public class D {
  20. }

  

我们在MyConfig4中@Import进MyImportBeanDefinitionRegistrar,并在后面的测试用例中把MyConfig4传入应用上下文进行初始化。

  1. package org.example.config;
  2.  
  3. import org.example.beans.MyImportBeanDefinitionRegistrar;
  4. import org.springframework.context.annotation.Import;
  5.  
  6. @Import(MyImportBeanDefinitionRegistrar.class)
  7. public class MyConfig4 {
  8. }

  

测试用例中我们根据在MyImportBeanDefinitionRegistrar注册的beanName获取bean,从运行结果可以看到,spring会根据我们传入的BeanDefinition创建bean。

  1. @Test
  2. public void test11() {
  3. AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig4.class);
  4. System.out.println(ac.getBean("d"));
  5. }

  

运行结果:

  1. org.example.beans.D@4b53f538

  

关于上面说的BeanDefinition如果看不懂的话可以先略过,后面还会再详讲BeanDefinition。

@DependsOn

有时候我们存在这样的需求:AService初始化之前,BService必须先初始化,AService不通过属性或构造函数参数显式依赖于BService。比如:AService负责产出消息,BService负责消费消息,最理想的做法是BService必须先AService初始化,让消息能及时消费掉,而spring创建bean的顺序是不固定的,所以我们可以使用@DependsOn来要求在初始化AService之前必须先初始化BService。

  1. package org.example.beans;
  2.  
  3. import org.springframework.context.annotation.DependsOn;
  4. import org.springframework.stereotype.Component;
  5.  
  6. @Component
  7. @DependsOn({"c"})
  8. public class B {
  9. public B() {
  10. System.out.println("B construct...");
  11. }
  12. }
  13.  
  14. package org.example.beans;
  15.  
  16. import org.springframework.stereotype.Component;
  17.  
  18. @Component
  19. public class C {
  20.  
  21. public C() {
  22. System.out.println("C construct...");
  23. }
  24. }
  25.  
  26. package org.example.config;
  27.  
  28. import org.springframework.context.annotation.ComponentScan;
  29.  
  30. @ComponentScan("org.example.beans")
  31. public class MyConfig5 {
  32. }

  

测试用例:

  1. @Test
  2. public void test12() {
  3. AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig5.class);
  4. }

  

运行结果:

  1. C construct...
  2. B construct...

  

我们在B类声明对C类的依赖,所以spring在创建bean的先创建C再创建B,大家也可以尝试下,把B类上的@DependsOn去掉,spring会先创建B再创建C。

FactoryBean

当创建一个bean的逻辑十分复杂,不适合以@Bean来标注方法返回时,我们可以创建一个类并实现FactoryBean接口,然后在FactoryBean的实现中编写复杂的创建逻辑。我们来看下FactoryBean接口:

  1. public interface FactoryBean<T> {
  2. //返回工厂所创建的实例,实例可被共享,取决于这个实例是单例还是原型
  3. T getObject() throws Exception;
  4. //返回bean的class对象
  5. Class<?> getObjectType();
  6. //返回bean是否单例
  7. default boolean isSingleton() {
  8. return true;
  9. }
  10. }

  

我们编写一个机器人FactoryBean(RobotFactoryBean)用来产生机器人(Robot)对象:

  1. package org.example.beans;
  2.  
  3. import org.springframework.beans.factory.FactoryBean;
  4. import org.springframework.stereotype.Component;
  5.  
  6. @Component
  7. public class RobotFactoryBean implements FactoryBean<Robot> {
  8. @Override
  9. public Robot getObject() throws Exception {
  10. return new Robot();
  11. }
  12.  
  13. @Override
  14. public Class<?> getObjectType() {
  15. return Robot.class;
  16. }
  17.  
  18. @Override
  19. public boolean isSingleton() {
  20. return true;
  21. }
  22. }
  23.  
  24. package org.example.beans;
  25.  
  26. public class Robot {
  27. }

  

一般我们通过beanName来获取bean时,都是通过类名来获取,但FactoryBean有点特殊,如果我们要获取Robot这个bean,需要传入它的工厂名称robotFactoryBean,如果我们需要获取工厂对象本身,则在robotFactoryBean前面加一个'&'符号。

  1. @Test
  2. public void test13() {
  3. AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig5.class);
  4. Object robot = ac.getBean("robotFactoryBean");
  5. System.out.println(robot);
  6. System.out.println(robot.getClass());
  7. Object factoryBean = ac.getBean("&robotFactoryBean");
  8. System.out.println(factoryBean);
  9. System.out.println(factoryBean.getClass());
  10. }

  

运行结果:

  1. org.example.beans.Robot@6b1274d2
  2. class org.example.beans.Robot
  3. org.example.beans.RobotFactoryBean@7bc1a03d
  4. class org.example.beans.RobotFactoryBean

  

Spring源码解析之基础应用(三)的更多相关文章

  1. Spring源码解析之BeanFactoryPostProcessor(三)

    在上一章中笔者介绍了refresh()的<1>处是如何获取beanFactory对象,下面我们要来学习refresh()方法的<2>处是如何调用invokeBeanFactor ...

  2. Spring源码解析之ConfigurationClassPostProcessor(三)

    在上一章笔者介绍了ConfigurationClassParser.doProcessConfigurationClass(...)方法,在这个方法里调用了processImports(...)方法处 ...

  3. Spring源码解析之基础应用(二)

    方法注入 在spring容器中,大部分bean的作用域(scope)是单例(singleton)的,少部分bean的作用域是原型(prototype),如果一个bean的作用域是原型,我们A bean ...

  4. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegistry和SingletonBeanRegistry接口. 这边主要提供了 ...

  5. Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean

    Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean 七千字长文深刻解读,Spirng中是如何初始化单例bean的,和面试中最常问的Sprin ...

  6. TiKV 源码解析系列文章(三)Prometheus(上)

    本文为 TiKV 源码解析系列的第三篇,继续为大家介绍 TiKV 依赖的周边库 rust-prometheus,本篇主要介绍基础知识以及最基本的几个指标的内部工作机制,下篇会介绍一些高级功能的实现原理 ...

  7. spring 源码解析

    1. [文件] spring源码.txt ~ 15B     下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB     下载( ...

  8. Spring源码解析——循环依赖的解决方案

    一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...

  9. Spring源码解析-ioc容器的设计

    Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...

随机推荐

  1. js动画之轮播图

    一. 使用Css3动画实现 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...

  2. python的命名规则

    命名规则:大小写字母,数字,下划线和汉字等字符及组合 注意事项:大小写敏感,首字符不能是数字,不与保留字相同 Python语言有33个保留字(关键字) 如:if ,elif, else ,in 33个 ...

  3. 二分类问题 - 【老鱼学tensorflow2】

    什么是二分类问题? 二分类问题就是最终的结果只有好或坏这样的一个输出. 比如,这是好的,那是坏的.这个就是二分类的问题. 我们以一个电影评论作为例子来进行.我们对某部电影评论的文字内容为好评和差评. ...

  4. linux学习(四)Linux 文件基本属性

    一.引言 Linux系统是一种典型的多用户系统,不同的用户处于不同的地位,拥有不同的权限. 为了保护系统的安全性,Linux系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定. 在Lin ...

  5. 刷题[GXYCTF2019]禁止套娃

    梳理思路 打开网站,发现很简单,只有flag在哪里的字样. 查看源码,常用后台目录,robots.txt,都未发现有任何东西. 扫描 直接拉进扫描器一扫,发现 思考可能是git源码泄露,可能可以恢复源 ...

  6. VueX中state变化捕捉不到_getters监测不到state的变化

    原因 可能有多种原因, 现在我说一下我碰到的一种情况: state种有一个变量叫state,它是一个json对象, 可把我害惨了.因为他这个json长这个样: messageBox:{ friendI ...

  7. 基于C#的内网穿透学习笔记(附源码)

    如何让两台处在不同内网的主机直接互连?你需要内网穿透!          上图是一个非完整版内外网通讯图由内网端先发起,内网设备192.168.1.2:6677发送数据到外网时候必须经过nat会转换成 ...

  8. oracle中插入一条记录后,重新登录查找不到数据

    你插入了数据,但是没有提交.其他Session也就是你再次登录后自然就看不到了(但是在当前回话可以看到插入的数据),但是你用SQLPLUS EXIT之后再次登录就可以看到插入的数据了,因为ORACLE ...

  9. 01 学习人工智能,不做笔记?做笔记不知道如何输入数学公式?“onenote+Mathematics Add-In”拯救你!onenote安装数学输入公式插件Microsoft Mathematics Add-In for Word and OneNote教程走一波

    一.Microsoft Mathematics Add-In 插件下载 Microsoft Mathematics Add-In for Word and OneNote插件下载链接: https:/ ...

  10. Cadence OrCAD如何查看整页原理图中的元件的属性

    转载: 1.选中DSN文件右键"Edit Object Properties ".在这里插入图片描述2.单击选择"Pivot"按钮. 3.找到Part refe ...