我们都知道
BeanFactoryPostProcessor是Spring的扩展点之一,它允许在Bean实例化之前对Bean的定义进行修改,本文结合自身工作经历对其实际应用进行简单讲解。
背景
toB产品一般都要对接甲方的一些系统才能落地,对接方式多种多样,其中一种是甲方以jar包的形式提供相关API给乙方使用,乙方需要将jar包中的类注入到定制代码中完成对接。然而,jar包意味着我们无法修改其源码,只能被动使用,这就带来了一些问题:
jar包中的类未必有setter,故无法用setter方式注入。有些类属性的值是动态的,容器启动时需要从配置文件或者配置中心读取。
针对这些问题,我们可以尝试使用BeanFactoryPostProcessor来处理。
实践
假设jar包中有com.ainoe.unifiedmatter.manager.JmsProducer这么一个类,其中包含ip、port、username、password属性。JmsProducer没有提供setter,只有包含了四个属性的构造方法,并且属性值都要从Nacos读取,我们需要将其注册成Bean,下面我们来分步实现。
首先,我们在context.xml中声明JmsProducer。
<bean id="jmsProducer" class="com.ainoe.unifiedmatter.manager.JmsProducer" lazy-init="true" init-method="init"
destroy-method="destroy">
<constructor-arg index="0" value="20.0.8.180,20.0.8.181"/>
<constructor-arg index="1" value="1414"/>
<constructor-arg index="2" value="mqm"/>
<constructor-arg index="3" value="VMware#3809"/>
</bean>上面使用了
<constructor-arg>而非<property>,这是因为上文提到JmsProducer没有提供setter,所以无法用setter方式注入,只能使用构造方法注入。value的内容也不重要,因为我们会用Nacos的值覆盖掉。
然后,我们定义一个JmsProducerConfig去实现BeanFactoryPostProcessor接口。
public class JmsProducerConfig implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition jmsProducer = beanFactory.getBeanDefinition("jmsProducer");
ConstructorArgumentValues argumentValues = jmsProducer.getConstructorArgumentValues();
argumentValues.addIndexedArgumentValue(0, NacosConfig.UNIFIED_MATTER_HOSTS());
argumentValues.addIndexedArgumentValue(1, NacosConfig.UNIFIED_MATTER_PORT().intValue());
argumentValues.addIndexedArgumentValue(2, NacosConfig.UNIFIED_MATTER_USERNAME());
argumentValues.addIndexedArgumentValue(3, NacosConfig.UNIFIED_MATTER_PASSWORD());
}
}我们注意到,覆盖第二个参数port的值时,使用了intValue()来手动拆箱,这是因为
JmsProducer在定义port时的类型就是int而非Integer,我们传入Integer的话是会覆盖失败的,Spring不会帮我们自动拆箱。
我们来分析一下这段代码:
JmsProducerConfig实现了BeanFactoryPostProcessor接口并实现了其postProcessBeanFactory方法。在
postProcessBeanFactory方法中,通过ConfigurableListableBeanFactory的getBeanDefinition方法获取到了jmsProducer的BeanDefinition。通过
BeanDefinition的getConstructorArgumentValues方法获取到了jmsProducer构造方法中的所有参数。获取到构造方法中的所有参数后,我们通过
addIndexedArgumentValue方法,从Nacos中获取对应的配置值来覆盖掉<bean>声明中的值。addIndexedArgumentValue方法的第一个参数表示参数在构造方法中的位置,第二个参数表示目标值。
至此,我们就实现了注入JmsProducer的同时,将其属性值也动态注入的功能了。
扩展
如果JmsProducer提供了setter,那JmsProducerConfig可以用另一种写法,ConfigurableListableBeanFactory还提供了getPropertyValues方法与addPropertyValue方法,我们也可以使用他们来完成属性值的覆盖。
public class JmsProducerConfig implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition jmsProducer = beanFactory.getBeanDefinition("jmsProducer");
MutablePropertyValues propertyValues = jmsProducer.getPropertyValues();
propertyValues.addPropertyValue("ip", SuzhouBankConfig.UNIFIED_MATTER_HOSTS());
propertyValues.addPropertyValue("port", NacosConfig.UNIFIED_MATTER_PORT().intValue());
propertyValues.addPropertyValue("username", NacosConfig.UNIFIED_MATTER_USERNAME());
propertyValues.addPropertyValue("password", NacosConfig.UNIFIED_MATTER_PASSWORD());
}
}