Spring 之 MutablePropertyValues 和 ConstructorArgumentValues 的简单理解

Source

1、MutablePropertyValues 概述

其实在绝大多情况下,MutablePropertyValues 这个类很少用,但是涉及到框架改造扩展可能就要使用到这个类。并且这个类在 BeanDefinition 模板中也是一个非常重要的角色。

id:Bean 唯一标识名称。
beanClass:类全限定名(包名+类名)。
init-method:定义 Bean 初始化方法,Bean 组装之后调用,必须是一个无参数方法。
destory-method:定义 Bean 销毁方法,在 BeanFactory 关闭时触发,同样也必须是一个无参构造方法,只能应用于 SingletonBean 单例 Bean。
factory-method:定义创建 Bean 对象的工厂方法,用于下面的 factory-bean,表示这个 Bean 是通过工厂方法创建,此时,class 属性 “失效”。
factory-bean:定义创建该 Bean 的工厂类,如果使用了 factory-bean,则 class 属性相当于 “失效”。
MultablePropertyValues:用于封装类属性的集合,里面是一个 List 容器,包装了很多 PropertyValue ,一个 PropertyValue 封装了一个属性及其对应的值,可以说一个属性及其值就是一个 PropertyValue,当我们需要再 BeanDefinition 中修改某个类里面的属性时就可以使用该类。
ConstructorArgumentValues:用来在 BeanDefinition 模版中指定使用哪个构造方法进行实例化 Bean,这个参数在集成 MyBatis 框架就使用到了。

那么这个类有什么作用呢?

目前据我了解到的这个类可以帮助我们在 BeanDefinition 中修改某个类的属性,下面就举个案例说明。

2、代码演示

先定义 ProcessorEntity 实体类,类里面有两个属性:name、birthday,两个构造函数,如下:


public class ProcessorEntity {
    
      

    private String name = "ABC";
    
    private Integer birthday;

	@Autowired
	public ProcessorEntity(Integer birthday) {
    
      
		System.out.println("birthdaybirthdaybirthdaybirthday");
	}

	public ProcessorEntity(String name) {
    
      
		System.out.println("namenamenamenamenam");
	}

	
	public Integer getBirth() {
    
      
		return birthday;
	}

	public void setBirth(Integer birthday) {
    
      
		this.birthday = birthday;
	}

	public String getName() {
    
      
		System.out.println("getName() 方法中的 scannerEntity = " + scannerEntity);
		return name;
	}

	public void setName(String name) {
    
      
		this.name = name;
	}
}

ProcessorEntity 类我们通过 BeanDefinitionRegistryPostProcessor 手动注册,这样也可以比较方便的对 BeanDefinition 进行修改。

定义一个 MyConfigurationPostProcessor1 类获取到 BeanDefinition 定义,可以借助 BeanDefinitionRegistryPostProcessor 接口,如下:


@Component
public class MyConfigurationPostProcessor1 implements BeanDefinitionRegistryPostProcessor {
    
      
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
      
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    
      
		GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
		genericBeanDefinition.setBeanClass(ProcessorEntity.class);
		MutablePropertyValues propertyValues = genericBeanDefinition.getPropertyValues();
		// 修改 ProcessorEntity 类中 name 属性默认值
		propertyValues.addPropertyValue("name","小明");
		
		// 然后再将 BeanDefinition 注册到 BeanFactory 容器中
		registry.registerBeanDefinition("processorEntity",genericBeanDefinition);
	}
}

代码中通过自己定制一个 GenericBeanDefinition,然后注册到 Spring 容器中,这样 Spring 就会按照设定好的模版生产 bean。

通过代码 genericBeanDefinition.getPropertyValues() 可以获取到 MutablePropertyValues 集合,源码如下:

在这里插入图片描述

容器拿到之后,假设现在要对 ProcessorEntity 类的属性 name 赋值,或者说是修改,应该怎么做呢?

可以将属性 name 和要设置的值封装成 PropertyValue,然后添加到 MutablePropertyValues 容器中即可,这样 Spring 自动帮咱们实现属性的设置。

其中代码 propertyValues.addPropertyValue("name","小明") 就是将 name=小明 封装到 PropertyValue,并添加到 MutablePropertyValues 容器,可以从源码看出,如下:

在这里插入图片描述在这里插入图片描述

最后测试结果如下:


public class TestBeanScanner {
    
      
	public static void main(String[] args) {
    
      
		ApplicationContext context = new AnnotationConfigApplicationContext(ScannerConfig.class);
		ProcessorEntity processorEntity = context.getBean("processorEntity", ProcessorEntity.class);	
		System.out.println("processorEntity = " + processorEntity+",name="+processorEntity.getName1());
	}
}

结果如下:

processorEntity = com.gwm.bean221207.processors.ProcessorEntity@20ccf40b,name=小明

从结果可以发现,name 默认值是 “ABC”,最终被修改成了 “小明”。

name 属性修改完成了,现在又想将 ProcessorEntity 类另一个属性 birthday 也修改下,我们肯定以为那还不简单,直接写代码,如下所示:


@Component
public class MyConfigurationPostProcessor1 implements BeanDefinitionRegistryPostProcessor {
    
      
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
      
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    
      
		GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
		genericBeanDefinition.setBeanClass(ProcessorEntity.class);
		MutablePropertyValues propertyValues = genericBeanDefinition.getPropertyValues();
		// 修改 ProcessorEntity 类中 name 属性默认值
		propertyValues.addPropertyValue("name","小明");
	    // 修改 ProcessorEntity 类中 birthday 属性默认值
	    propertyValues.addPropertyValue("birthday",2022);
	
		// 然后再将 BeanDefinition 注册到 BeanFactory 容器中
		registry.registerBeanDefinition("processorEntity",genericBeanDefinition);
	}
}

测试如下:

public class TestBeanScanner {
    
      
	public static void main(String[] args) {
    
      
		ApplicationContext context = new AnnotationConfigApplicationContext(ScannerConfig.class);
		ProcessorEntity processorEntity = context.getBean("processorEntity", ProcessorEntity.class);
		System.out.println("processorEntity = " + processorEntity+",name="+processorEntity.getName());
		System.out.println("processorEntity = " + processorEntity+",birthday="+processorEntity.getBirth());
	}
}


结果如下:

在这里插入图片描述

发现抛出异常,奇怪,检查上面的代码,发现属性名称是叫做 birthday,GenericBeanDefinition 也确实是为 birthday 属性赋值了呀,应该没问题的,但是细心的朋友应该发现了,name 和 birthday 两个属性,name 的 getXxx() 、setXxx() 中 Xxx 和 name 的驼峰写法一样,但是 birthday 驼峰和 birth 完全不一样。

说到这儿了,大家应该能推测出,这个 MutablePropertyValues 的更新操作应该是调用了 setXxx() 方法去实现的,那么最好的验证就是在 setName() 方法里面打上端点 debug ,如下:

在这里插入图片描述

看调用栈及参数显示,可以发发现最终是调用 setName() 方法进行属性操作的,所以在 GenericBeanDefinition 中的 MutablePropertyValues 容器中属性名称要和 setXxx() 中的 Xxx 一样。所以我们需要将 MutablePropertyValues 中的 birthday 修改成和 setBirth() 方法中的 birth 一样,如下:


@Component
public class MyConfigurationPostProcessor1 implements BeanDefinitionRegistryPostProcessor {
    
      
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
      

	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    
      
		GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
		genericBeanDefinition.setBeanClass(ProcessorEntity.class);
		MutablePropertyValues propertyValues = genericBeanDefinition.getPropertyValues();
		// 修改 ProcessorEntity 类中 name 属性默认值
		propertyValues.addPropertyValue("name","小明");

		propertyValues.addPropertyValue("birth",2022);

		// 然后再将 BeanDefinition 注册到 BeanFactory 容器中
		registry.registerBeanDefinition("processorEntity",genericBeanDefinition);
	}
}

其实继续往深处源码看,会发现,它是先收集本类中所有方法,然后把 set、和 get 截取掉,截取处理之后,首字母转换成小写,最终将这个方法名称作为 key 封装成一个个的 PropertyInfo 如下:

在这里插入图片描述

所以我们在 MutablePropertyValues 中添加的名称,如果在 ProcessorEntity 类中能够找到一个对应的 setXxx() 方法,那么这个方法就会被调用,现在给 ProcessorEntity 添加一个方法 setSex() 方法,但是类中并没有 sex 这个属性,如下:


public class ProcessorEntity {
    
      

    private String name = "ABC";
    
    private Integer birthday;

	@Autowired
	public ProcessorEntity(Integer birthday) {
    
      
		System.out.println("birthdaybirthdaybirthdaybirthday");
	}

	public ProcessorEntity(String name) {
    
      
		System.out.println("namenamenamenamenam");
	}

    public Object setSex(String sex) {
    
      
		System.out.println("sex ====>"+sex);
		return new Object();
	}
	
	public Integer getBirth() {
    
      
		return birthday;
	}

	public void setBirth(Integer birthday) {
    
      
		this.birthday = birthday;
	}

	public String getName() {
    
      
		System.out.println("getName() 方法中的 scannerEntity = " + scannerEntity);
		return name;
	}

	public void setName(String name) {
    
      
		this.name = name;
	}
}

然后再 MutablePropertyValues 中调用并赋值,如下:


@Component
public class MyConfigurationPostProcessor1 implements BeanDefinitionRegistryPostProcessor {
    
      
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
      
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    
      
		GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
		genericBeanDefinition.setBeanClass(ProcessorEntity.class);
		MutablePropertyValues propertyValues = genericBeanDefinition.getPropertyValues();
		// 修改 ProcessorEntity 类中 name 属性默认值
		propertyValues.addPropertyValue("name","小明");

		propertyValues.addPropertyValue("birth",2022);

		propertyValues.addPropertyValue("sex","女");
		
		// 然后再将 BeanDefinition 注册到 BeanFactory 容器中
		registry.registerBeanDefinition("processorEntity",genericBeanDefinition);
	}
}

测试如下:

public class TestBeanScanner {
    
      
	public static void main(String[] args) {
    
      
		ApplicationContext context = new AnnotationConfigApplicationContext(ScannerConfig.class);
		ProcessorEntity processorEntity = context.getBean("processorEntity", ProcessorEntity.class);
		System.out.println("processorEntity = " + processorEntity+",name="+processorEntity.getName());
		System.out.println("processorEntity = " + processorEntity+",birthday="+processorEntity.getBirth());
	}
}

发现 setSex() 被调用了,结果也正常输出。如下:

sex ====>女
processorEntity = com.gwm.bean221207.processors.ProcessorEntity@32b260fa,name=abcdddddd
processorEntity = com.gwm.bean221207.processors.ProcessorEntity@32b260fa,birthday=2022

3、ConstructorArgumentValues 指定构造方法实例化 Bean

上面讲完了 MutablePropertyValues 类的作用,现在继续讲解下 ConstructorArgumentValues 的作用,这个作用就非常清晰了,就是可以再 BeanDefinition 创建的时候,指定使用哪个构造方法实例化 bean。

开局我们在 ProcessorEntity 类中准备好了两个构造方法,一个参数是 String,一个是 Integer,现在指定用 Integer 参数的构造方法实例化 bean,代码如下:



@Component
public class MyConfigurationPostProcessor1 implements BeanDefinitionRegistryPostProcessor {
    
      
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
      

	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    
      
		GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
		genericBeanDefinition.setBeanClass(ProcessorEntity.class);
		MutablePropertyValues propertyValues = genericBeanDefinition.getPropertyValues();
		// 修改 ProcessorEntity 类中 name 属性默认值
		propertyValues.addPropertyValue("name","小明");

		propertyValues.addPropertyValue("birth",2022);

		propertyValues.addPropertyValue("sex","女");

		ConstructorArgumentValues constructorArgumentValues = genericBeanDefinition.getConstructorArgumentValues();
		constructorArgumentValues.addGenericArgumentValue(18);

		// 然后再将 BeanDefinition 注册到 BeanFactory 容器中
		registry.registerBeanDefinition("processorEntity",genericBeanDefinition);
	}
}

通过 genericBeanDefinition.getConstructorArgumentValues() 代码可以获取到 ConstructorArgumentValues 容器,源码如下:

在这里插入图片描述

其实也是一个 List 容器,需要使用哪个构造方法,就对应将入参值添加到对应的参数上即可,constructorArgumentValues.addGenericArgumentValue(18) 源码如下:

在这里插入图片描述

我们传入给构造方法参数的值被封装成一个个 ValueHolder 对象,并被添加到了 ConstructorArgumentValues 容器中。调用的源码如下:

在这里插入图片描述

注意这里有个判断条件 mbd.hasConstructorArgumentValues() 这里肯定是成立,因为在 MyConfigurationPostProcessor1 类中已经添加参数值。

在这里插入图片描述在这里插入图片描述