我们都知道
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());
}
}