BeanDefinition 是 Spring 用来描述用来生成 Bean 的类的元数据信息的一个接口。容器中的 BeanDefinitionMap 是 IOC 的一个基础的组成部分,也是非常重要的一个组件。
BeanDefinition 是一个接口,它的继承关系如下:
AbstractBeanDefinition是一个抽象类,它的三个子类都具有各自的特点,下会重点分析。
BeanDefinition 是一个接口,里面定义了一些方法和常量。
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
/**
* singleton 单例作用域标识
*/
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
/**
* prototype 原型作用域标识
*/
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
/**
* 如果一个 beanDefinition 需要继承另一个 beanDefinition 的属性,那么需要指定一个 parent definition 的名字
*/
void setParentName(@Nullable String parentName);
/**
* 设置 bean 的类名,这个类名可以在 BeanFactoryPostProcessor 中修改
*/
void setBeanClassName(@Nullable String beanClassName);
/**
* 设置 bean 的作用域类型
*/
void setScope(@Nullable String scope);
/**
* 设置该 bean 是否懒加载,如果是 false 那么将会在容器启动是加载该 bean
*/
void setLazyInit(boolean lazyInit);
/**
* 设置这个 bean 需要依赖的其他 bean 的名字,在初始化这个 bean 之前会先保证所依赖的 bean 都完成初始化
*/
void setDependsOn(@Nullable String... dependsOn);
/**
* 设置一个 bean 是否作为自动装配的的候选人,这个参数仅仅影响 byType 的装配模式
*/
void setAutowireCandidate(boolean autowireCandidate);
/**
* 将该 Bean 设置为主要候选者
*/
void setPrimary(boolean primary);
}
当一个 Bean Player 中需要装配另一个 Bean Hero 的时候,如果此时有多各 Hero Bean 存在,那么就会发生异常,因为 Spring 容器不知道需要装配哪一个 Hero 给 Player,此时如果 Hero Bean 设置了 autowireCandidate=false
,那么该 Bean 将不会作为候选者自动装配,但是该参数仅仅在 byType 模式下生效。代码如下:
public interface Hero {
void selfIntroduce();
}
public class Player {
private Hero hero;
public Hero getHero() {
return hero;
}
public void setHero(Hero hero) {
this.hero = hero;
}
}
public class HanXin implements Hero{
private String type;
private String name;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void selfIntroduce() {
System.out.printf("我是%s,是一名%s\n", name, type);
}
}
public class LiBai implements Hero {
private String type;
private String name;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void selfIntroduce() {
System.out.printf("我是%s,是一名%s\n", name, type);
}
}
public class AutowireCandidateDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowireCandidateDemo.class);
Player player = context.getBean(Player.class);
player.getHero().selfIntroduce();
}
@Bean
public Player role(Hero hero){
Player player = new Player();
player.setHero(hero);
return player;
}
@Bean
public Hero hanXin() {
HanXin hanXin = new HanXin();
hanXin.setName("韩信");
hanXin.setType("刺客");
return hanXin;
}
@Bean(autowireCandidate = false)
public Hero liBai() {
LiBai liBai = new LiBai();
liBai.setName("李白");
liBai.setType("刺客");
return liBai;
}
}
此时的运行结果是
我是韩信,是一名刺客
如果将autowireCandidate = false
去掉,那么启动会报错
expected single matching bean but found 2: hanXin,liBai
同样,可以测试 primary 属性,加上@Primary
即可。
scope 表示一个作用域标识。
代码测试如下:
public class ScopeDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScopeDemo.class);
System.out.println("韩信的作用域是 singleton,获取两次韩信,这两个韩信其实是同一个对象");
Hero hanXin1 = context.getBean("hanXin", Hero.class);
System.out.println(hanXin1);
Hero hanXin2 = context.getBean("hanXin", Hero.class);
System.out.println(hanXin2);
System.out.println("李白的作用域是 prototype,获取两次李白,这两个李白其实是不同的对象");
Hero liBai1 = context.getBean("liBai", Hero.class);
System.out.println(liBai1);
Hero liBai2 = context.getBean("liBai", Hero.class);
System.out.println(liBai2);
}
@Bean
@Scope(scopeName = BeanDefinition.SCOPE_SINGLETON)
public Hero hanXin() {
HanXin hanXin = new HanXin();
hanXin.setName("韩信");
hanXin.setType("刺客");
return hanXin;
}
@Bean
@Scope(scopeName = BeanDefinition.SCOPE_PROTOTYPE)
public Hero liBai() {
LiBai liBai = new LiBai();
liBai.setName("李白");
liBai.setType("刺客");
return liBai;
}
}
结果如下:
韩信的作用域是 singleton,获取两次韩信,这两个韩信其实是同一个对象
study.qiqiang.context.beandefinition.HanXin@76f2b07d
study.qiqiang.context.beandefinition.HanXin@76f2b07d
李白的作用域是 prototype,获取两次李白,这两个李白其实是不同的对象
study.qiqiang.context.beandefinition.LiBai@6ee52dcd
study.qiqiang.context.beandefinition.LiBai@4493d195
AbstractBeanDefinition 实现了 BeanDefinition 接口,本身是个抽象类。它有三个常用的子类。
RootBeanDefinition 被称作根 BeanDefinition,在 Spring 早期是最佳选择,可作为父 BeanDefinition。ChildBeanDefinition 是子 BeanDefinition,它必须有一个父 BeanDefinition才能被实例化出来,可以继承父 BeanDefinition的属性。而 GenericBeanDefinition 是一个通用的 BeanDefinition,它既可以作为父也可以作为子。
RootBeanDefinition 与 ChildBeanDefinition 继承测试如下。
public class BeanDefinitionInheritApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(BeanDefinitionInheritApp.class);
//注册刺客父类 bean definition
RootBeanDefinition ckBeanDefinition = new RootBeanDefinition();
ckBeanDefinition.setAbstract(true);
ckBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
ckBeanDefinition.getPropertyValues().add("type", "刺客");
applicationContext.registerBeanDefinition("ck", ckBeanDefinition);
//注册刺客李白 bean definition
ChildBeanDefinition liBaiBeanDefinition = new ChildBeanDefinition("ck");
liBaiBeanDefinition.getPropertyValues().add("name", "李白");
liBaiBeanDefinition.setBeanClass(LiBai.class);
applicationContext.registerBeanDefinition("liBai", liBaiBeanDefinition);
//注册刺客韩信 bean definition
GenericBeanDefinition hanXinBeanDefinition = new GenericBeanDefinition();
hanXinBeanDefinition.setParentName("ck");
hanXinBeanDefinition.getPropertyValues().add("name", "韩信");
hanXinBeanDefinition.setBeanClass(HanXin.class);
applicationContext.registerBeanDefinition("hanXin", hanXinBeanDefinition);
// 刷新容器
applicationContext.refresh();
LiBai liBai = applicationContext.getBean(LiBai.class);
liBai.selfIntroduce();
HanXin hanXin = applicationContext.getBean(HanXin.class);
hanXin.selfIntroduce();
}
}
测试结果
我是李白,是一名刺客
我是韩信,是一名刺客
可以看到,首先定义了一个 RootBeanDefinition,里面设置了type=刺客
,在注册李白 bean definition和韩信 bean definition时,分别用了ChildBeanDefinition
和GenericBeanDefinition
作为子 BeanDefinition,虽然没有指定type,但是最终都能继承RootBeanDefinition ckBeanDefinition
里面的属性。注意,在定义父 BeanDefinition 的时候,要么设置setAbstract(true)
,此时该父 BeanDefinition 不会被实例化成一个 bean,要么设置成一个有BeanClass
属性的能被正常实例化的 BeanDefinition。
ScannedGenericBeanDefinition 是 GenericBeanDefinition 的一个子类。如果一个类加上了@ComponentScan
注解,在扫描到那些被标记了@Component
、@Controller
、@Service
等注解的 bean 类时,为这些类生成的 bean definition 是 ScannedGenericBeanDefinition。
AnnotatedGenericBeanDefinition 也是 GenericBeanDefinition 的子类,被@Configuration
标记的类将会生成一个 AnnotatedGenericBeanDefinition。
ConfigurationClassBeanDefinition 是 RootBeanDefinition 的子类,如果一个 bean 是通过@Bean
标记产生的,那么这个 bean 的 bean definition 将是 ConfigurationClassBeanDefinition 类型。
代码如下:
@Configuration
@ComponentScan
public class OthersBeanDefinitionDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(OthersBeanDefinitionDemo.class);
BeanDefinition hanXinBeanDefinition = context.getBeanDefinition("hanXin");
System.out.println("@Bean:" + hanXinBeanDefinition.getClass().getSimpleName());
BeanDefinition teamBeanDefinition = context.getBeanDefinition("othersBeanDefinitionDemo");
System.out.println("@Configuration:" + teamBeanDefinition.getClass().getSimpleName());
BeanDefinition componentBeanBeanDefinition = context.getBeanDefinition("componentBean");
System.out.println("@Component:" + componentBeanBeanDefinition.getClass().getSimpleName());
}
@Bean
public Hero hanXin() {
HanXin hanXin = new HanXin();
hanXin.setName("韩信");
hanXin.setType("刺客");
return hanXin;
}
}
@Component("componentBean")
class ComponentBean {
}
测试结果如下:
@Bean:ConfigurationClassBeanDefinition
@Configuration:AnnotatedGenericBeanDefinition
@Component:ScannedGenericBeanDefinition
一个能被 Spring 生成 Bean 的 BeanDefinition 必须要设置一个 Class,但是还有其他很多需要设置,Spring 提供了一个工具类 BeanDefinitionBuilder 来快速生成 BeaDefinition。案例如下:
public class BeanDefinitionBuilderDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(BeanDefinitionBuilderDemo.class);
// 快速生成 beanDefinition
BeanDefinitionBuilder hanXinDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(HanXin.class);
// 添加属性
hanXinDefinitionBuilder.addPropertyValue("type", "刺客");
hanXinDefinitionBuilder.addPropertyValue("name", "韩信");
// 注册
context.registerBeanDefinition("hanXin", hanXinDefinitionBuilder.getBeanDefinition());
context.refresh();
context.getBean(HanXin.class).selfIntroduce();
}
}
结果如下:
我是韩信,是一名刺客