Spring BeanDefinition 详解与案例

介绍

BeanDefinition 是 Spring 用来描述用来生成 Bean 的类的元数据信息的一个接口。容器中的 BeanDefinitionMap 是 IOC 的一个基础的组成部分,也是非常重要的一个组件。

BeanDefinition 是一个接口,它的继承关系如下:
在这里插入图片描述

AbstractBeanDefinition是一个抽象类,它的三个子类都具有各自的特点,下会重点分析。

BeanDefinition 源码解读与案例

BeanDefinition

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);
}

autowireCandidate 解读与案例

当一个 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 解读与案例

scope 表示一个作用域标识。

  • singleton:在 Spring 启动的时候,会根据 bean definition 生成一个 bean 并缓存起来,每次获取都是获取这个缓存的 bean。
  • prototype:Spring 启动的时候并不会生成 bean 对象,每次获取的时候都会生成一个新的对象。

代码测试如下:

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

beanDefinition 的继承和 AbstractBeanDefinition 三大子类解析与案例

AbstractBeanDefinition 实现了 BeanDefinition 接口,本身是个抽象类。它有三个常用的子类。

  • RootBeanDefinition
  • ChildBeanDefinition
  • GenericBeanDefinition

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时,分别用了ChildBeanDefinitionGenericBeanDefinition作为子 BeanDefinition,虽然没有指定type,但是最终都能继承RootBeanDefinition ckBeanDefinition里面的属性。注意,在定义父 BeanDefinition 的时候,要么设置setAbstract(true),此时该父 BeanDefinition 不会被实例化成一个 bean,要么设置成一个有BeanClass属性的能被正常实例化的 BeanDefinition。

其它 BeanDefinition

ScannedGenericBeanDefinitionGenericBeanDefinition 的一个子类。如果一个类加上了@ComponentScan注解,在扫描到那些被标记了@Component@Controller@Service等注解的 bean 类时,为这些类生成的 bean definition 是 ScannedGenericBeanDefinition。

AnnotatedGenericBeanDefinition 也是 GenericBeanDefinition 的子类,被@Configuration标记的类将会生成一个 AnnotatedGenericBeanDefinition。

ConfigurationClassBeanDefinitionRootBeanDefinition 的子类,如果一个 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

BeanDefinitionBuilder 构建 BeanDefinition

一个能被 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();
	}
}

结果如下:

我是韩信,是一名刺客
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值