首页 文章
取消

BeanFactoryPostProcessor的应用案例

我们都知道BeanFactoryPostProcessorSpring的扩展点之一,它允许在Bean实例化之前对Bean的定义进行修改,本文结合自身工作经历对其实际应用进行简单讲解。

背景

toB产品一般都要对接甲方的一些系统才能落地,对接方式多种多样,其中一种是甲方以jar包的形式提供相关API给乙方使用,乙方需要将jar包中的类注入到定制代码中完成对接。然而,jar包意味着我们无法修改其源码,只能被动使用,这就带来了一些问题:

  • jar包中的类未必有setter,故无法用setter方式注入。

  • 有些类属性的值是动态的,容器启动时需要从配置文件或者配置中心读取。

针对这些问题,我们可以尝试使用BeanFactoryPostProcessor来处理。

实践

假设jar包中有com.ainoe.unifiedmatter.manager.JmsProducer这么一个类,其中包含ipportusernamepassword属性。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不会帮我们自动拆箱。

我们来分析一下这段代码:

  1. JmsProducerConfig实现了BeanFactoryPostProcessor接口并实现了其postProcessBeanFactory方法。

  2. postProcessBeanFactory方法中,通过ConfigurableListableBeanFactorygetBeanDefinition方法获取到了jmsProducerBeanDefinition

  3. 通过BeanDefinitiongetConstructorArgumentValues方法获取到了jmsProducer构造方法中的所有参数。

  4. 获取到构造方法中的所有参数后,我们通过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());
    }
}