组合Java配置

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

@Configuration
public class ConfigA { @Bean
public A a() {
return new A();
}
} @Configuration
@Import(ConfigA.class)
public class ConfigB { @Bean
public B b() {
return new B();
}
}

  

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

public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
ConfigA configA = ctx.getBean(ConfigA .class);
ConfigB configB = ctx.getBean(ConfigB.class);
}

  

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

package org.example.config;

import org.example.beans.D;
import org.springframework.context.annotation.Import; @Import(D.class)
public class MyConfig3 {
} package org.example.beans; public class D {
}

  

测试用例:

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

  

运行结果:

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接口的部分方法:

package org.springframework.beans.factory.config;
……
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
……
//设置bean所对应的class
void setBeanClassName(@Nullable String beanClassName);
//获取bean所对应的class
String getBeanClassName();
//设置bean的作用域
void setScope(@Nullable String scope);
//获取bean的作用域
String getScope();
//设置bean是否懒加载
void setLazyInit(boolean lazyInit);
//判断bean是否懒加载
boolean isLazyInit();
//设置bean在初始化前需要的依赖项
void setDependsOn(@Nullable String... dependsOn);
//获取bean在初始化前需要的依赖项
String[] getDependsOn();
……
}

  

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

public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) { registerBeanDefinitions(importingClassMetadata, registry);
} default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}

  

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

package org.example.beans;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata; public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(D.class);
registry.registerBeanDefinition("d", beanDefinition);
}
} package org.example.beans; public class D {
}

  

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

package org.example.config;

import org.example.beans.MyImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Import; @Import(MyImportBeanDefinitionRegistrar.class)
public class MyConfig4 {
}

  

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

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

  

运行结果:

org.example.beans.D@4b53f538

  

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

@DependsOn

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

package org.example.beans;

import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component; @Component
@DependsOn({"c"})
public class B {
public B() {
System.out.println("B construct...");
}
} package org.example.beans; import org.springframework.stereotype.Component; @Component
public class C { public C() {
System.out.println("C construct...");
}
} package org.example.config; import org.springframework.context.annotation.ComponentScan; @ComponentScan("org.example.beans")
public class MyConfig5 {
}

  

测试用例:

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

  

运行结果:

C construct...
B construct...

  

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

FactoryBean

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

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

  

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

package org.example.beans;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component; @Component
public class RobotFactoryBean implements FactoryBean<Robot> {
@Override
public Robot getObject() throws Exception {
return new Robot();
} @Override
public Class<?> getObjectType() {
return Robot.class;
} @Override
public boolean isSingleton() {
return true;
}
} package org.example.beans; public class Robot {
}

  

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

    @Test
public void test13() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig5.class);
Object robot = ac.getBean("robotFactoryBean");
System.out.println(robot);
System.out.println(robot.getClass());
Object factoryBean = ac.getBean("&robotFactoryBean");
System.out.println(factoryBean);
System.out.println(factoryBean.getClass());
}

  

运行结果:

org.example.beans.Robot@6b1274d2
class org.example.beans.Robot
org.example.beans.RobotFactoryBean@7bc1a03d
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. 吴恩达《深度学习》-课后测验-第一门课 (Neural Networks and Deep Learning)-Week 4 - Key concepts on Deep Neural Networks(第四周 测验 – 深层神经网络)

    Week 4 Quiz - Key concepts on Deep Neural Networks(第四周 测验 – 深层神经网络) \1. What is the "cache" ...

  2. Robotframework自动化1-Windows环境搭建

    前言: robotframework环境搭建-环境准备 1.python2,pip2 2.WxPython 3.Robot Framework 4.Robotframework-ride 5.RIDE ...

  3. 论如何学习Extjs

    可能现在学习Extjs相比于Vue,在网上的资料要少很多,不过一些旧的视频还是可以帮助你们了解到Extjs是怎么回事. 这里讲一下自己是如何开始学习Extjs语言的: 1.先从Ext的中文文档中学习怎 ...

  4. redis哨兵机制--配置文件sentinel.conf详解

    转载自 https://blog.csdn.net/u012441222/article/details/80751390 Redis的哨兵机制是官方推荐的一种高可用(HA)方案,我们在使用Redis ...

  5. 定时任务与feign超时的纠葛,该咋优化?

    1 背景 业务定时器应用半夜经常会触发熔断异常的告警邮件 根据邮件提示的类找到归纳以下表格 编号 报错方法 接口所属应用 所属定时任务类 A VipTradeReportFeignService#ge ...

  6. matplotlib设置颜色、标记、线条,让你的图像更加丰富

    今天是数据处理专题的第11篇文章,我们继续来介绍matplotlib这个包的使用方法. 在上一篇文章当中我们介绍了matplotlib当中subplot的概念以及用法,今天我们将会来介绍matplot ...

  7. redis过期策略以及内存淘汰机制(理论+配置)

    一.redis的过期策略: redis的过期策略是:定期删除+惰性删除redis在存储数据时,可能会设置过期时间,而所谓的定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的k ...

  8. ios7.1发布企业证书测试包的问题

    关于升级了ios7.1之后发布企业版证书的测试包不能下载的问题,这个苹果也挺坑的,什么都不说,也不警告一下,直接就不能用了 用xcode的organizer里面的console里发现安装的时候提示这个 ...

  9. Solidity智能合约面向对象编程(一、类的创建)

    Solidity编写智能合约 1 pragma solidity ^0.4.4;//版本声明 ^代表向上兼容 pragma代表版本声明 solidity 代表开发语言 2 //定义类 3 contra ...

  10. Spring中用@DependsOn注解控制Bean的创建顺序

    1. 概述 Spirng容器自己会管理bean的生命周期和bean实例化的顺序,但是我们仍然可以根据我们自己的需求进行定制.我可以可以选择使用SmartLifeCycle接口,也可以用@Depends ...