定义bean的配置文件,想加一个统一的前缀,但不是所有的bean都要加,我把前缀写到配置文件里,不过xml里对应的占位符不解析。
spring-config-beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="spring-config-beans.xml"/>
</beans>
spring-config-beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jsf="http://jsf.jd.com/schema/jsf"
default-lazy-init="true" default-autowire="byName"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://jsf.jd.com/schema/jsf http://jsf.jd.com/schema/jsf/jsf.xsd">
<!-- I want to bean id will be "asdfStudentBean",now it can't work -->
<bean id="`${myprefix}`StudentBean"
class="com.example.beans.StudentBean">
<property name="name" value="${myprefix}" />
<!-- if beanid is "asdfStudentBean" then it can work, the name will be "asdf" -->
</bean>
</beans>
application.properties
myprefix=asdf
StudentBean.java
public class StudentBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void printName() {
System.out.println("your name is : " + this.name);
}
}
TestCode
@SpringBootTest
class Demo3ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
void contextLoads(){
StudentBean bean = (StudentBean) applicationContext.getBean("asdfStudentBean");
bean.printName();
}
}
<bean id="${myprefix}StudentBean" [FAIL]
<bean id="`${myprefix}`StudentBean" [FAIL]
<bean id="@@myprefix@@StudentBean" [FAIL]
<bean id="$${myprefix}StudentBean" [FAIL]
###起初开始看到这个问题,修改自定义beanName
,我下意识就联想到了spring
给的扩展接口BeanNameGenerator
,但是以前没在xml
注入bean
的方式使过,确实有点心虚,试了一下,确实不行,所以才弱弱的在问题里评论了一句,看看有没有直接说使用扫描注解的方式注入可以不?可以的话,我就不再看了。结果昨天问了,今天一直没回复,想着干脆也看看,我就不信xml
注入bean
的方式没有给出修改beanName
的扩展口子,毕竟这是spring
啊,"缝合怪"大师
因为bean
实例化之前,肯定先要有BeanDefinition
的,也就是bean
的定义,bean
的元数据,所以对于BeanDefinition
的修改,我先去看了一下BeanFactoryPostProcessor
,这是常见的扩展接口,不过一看接口方法的签名坏了,这只是一个ConfigurableListableBeanFactory
用ConfigurableListableBeanFactory
你是可以根据beanName
拿到对应的BeanDefinition
,但我们要做的事,其实不是真正要BeanDefinition
啊,我们恰好要修改beanName
,所以用BeanFactoryPostProcessor
这个扩展接口就不行了,因为使用它时BeanFactory
中就已经生成好了beanName
和BeanDefinition
的对应关系。因此我当时想着就是从调用BeanFactoryPostProcessor
的源码出发,往前看,再找其他可以扩展的接口
结果在BeanFactoryPostProcessor
的调用类PostProcessorRegistrationDelegate
的invokeBeanFactoryPostProcessors
方法中,没找几行代码,就看到了另一个扩展接口BeanDefinitionRegistryPostProcessor
这名字一看就很对味儿,看一下方法签名
这回参数是BeanDefinitionRegistry
,好家伙,这不就是找到根儿了么。顾名思义就是Bean
定义的注册表或者注册中心啊,再看看其中接口的方法,注册一个BeanDefinition
,删除一个BeanDefinition
,这不都整全了嘛
那改个beanName
就太简单了,举个栗子
看吧三行其实就完成了修改功能。虽然还没有完成题主的前缀注入问题,也算是完成一半,不过呢,嘿嘿,简单的bean
这样做肯定可以修改beanName
,但这里面鸭还有很多未知的坑!恰好我这次demo
的xml
文件是之前用过的,自己莫名就踩上了,顺道提上几个
- 继承类问题
假设A
和B
都是bean
,而且A extends B
,若要修改A
为A_new
,那还需要再修改B
的BeanDefinition
中的parentName
为A_new
alias
别名问题
假设A
还有name
配置,也就是id
和name
同时存在,这时name
的值就是别名,若要修改A
为A_new
,则还需要修改BeanDefinitionRegistry
中的别名映射关系
好了,上面这两个坑说完了,最后来说说看起来很简单,但实则不太那么容易的注入前缀问题,也就是注入properties
问题。我开始也是很天真,想着这不是so easy
嘛,我定义的MyBeanDefinitionRegistryPostProcessor
中加个属性prefix
然后配置文件上一配
结果一执行,没错,这个prefix
死活都是${myprefix}
,它就不给姥姥我变!幸好我试了一下其他bean
这个${myprefix}
是可以注入的,不然我就可能宕机了。
还是看了一下源码,我突然就释然了。。。BeanDefinitionRegistryPostProcessor
到底是什么时机在执行的,它不就是在BeanFactoryPostProcessor
之前执行的么,而可以实现${myprefix}
的el
表达式解析的逻辑PropertySourcesPlaceholderConfigurer
其实就是个BeanFactoryPostProcessor
的实现,因此BeanDefinitionRegistryPostProcessor
执行的时候还没有轮到PropertySourcesPlaceholderConfigurer
执行呢。
也就是说BeanDefinitionRegistryPostProcessor
的实现类可能只能有干巴巴的逻辑,不能享受到过多的spring
注入的东西了。它虽然是个bean
,但是里面基本上不可能注入其他业务上的bean
,毕竟BeanDefinitionRegistryPostProcessor
中处理的不还都是人家bean
的beanName
和BeanDefinition
嘛
所以此时我本身就想来写回答了,说让到时候这个前缀就写死在代码里,不要写在配置文件里了,不过呢还是很揪心,感觉就差一点点就完美了,个人觉得不完美的答案,我宁愿还是不写。
于是我把此时的问题换成怎么在类中读取配置文件,因为常规的spring
读取配置不行,我就想到了spring
对于资源的抽象,毕竟配置文件本质来说也是一种资源,这里就不得不提到了其抽象接口org.springframework.core.io.Resource
而我开始不是说其他bean
注入${myprefix}
是可以的,而处理这套逻辑前肯定要先读取配置文件,进入PropertySourcesPlaceholderConfigurer
中可以找到关键抽象类PropertySource
,这是键值对的抽象基类,只要我们想办法将配置文件抽线成PropertySource
,然后就可以调用它的getProperty
方法获取配置的值了
而之前说配置文件的抽象是Resource
,所以找到他们之间的关系就可以了,我是从返回参数入手。直接搜PropertySource
的使用地方,只看方法返回的那项
就几个,而且前几个都是不是创建或者不太像,后2个,从类名就基本断定了,PropertySourceFactory
和DefaultPropertySourceFactory
,其中DefaultPropertySourceFactory
还是实现PropertySourceFactory
,看PropertySourceFactory
的方法签名
虽然没有Resource
,但是第二个参数EncodedResource
还是很像的,进去一看,果然包含了Resource
,哈哈哈
不清楚怎么把Resource
包装成EncodedResource
,那赶紧搜搜哪里调用的,然后把他们官方的调用copy
过来就可以啦,果然有一个ConfigurationClassParser
ok,结合上面的逻辑,这下就简单了,我们首先给MyBeanDefinitionRegistryPostProcessor
中再增加一个属性resource
同时配置文件也相应配置上,再顺道删除prefix
的注入,反正也注入不进去,再新增了一个init
方法作为初始化方法
那我们的init
方法就来帮助我们根据resource
来初始化prefix
这里有个buildPropertySource
方法也很简单,就是copy
过来的
private PropertySource buildPropertySource() {
PropertySource<?> propertySource = null;
try {
propertySource = DEFAULT_PROPERTY_SOURCE_FACTORY.createPropertySource(
PROPERTY_SOURCE_NAME, new EncodedResource(resource));
} catch (IOException e) {
e.printStackTrace();
}
return propertySource;
}
这一下我们的配置文件的前缀在执行MyBeanDefinitionRegistryPostProcessor
就完美注入进来啦
至于在MyBeanDefinitionRegistryPostProcessor
的实际业务逻辑中怎么控制哪些bean
要配置前缀,那这个就看题主自己的设计啦,方法很多,比如特定的都实现某个接口啊,或者类上加一些自己的某个标记注解啊,都行,反正BeanDefinition
有bean
的class
,以及还有很多其他属性。可以根据自己情况定制
以上就是我的小小看法啦,如果题主你对于spring
容器相关知识还不是很清楚,可能看我写的思路还是有点不太顺溜,你可以先根据我提供的方法试试看,以后有机会再回过头来看吧,这里面spring
里的东西很多,我自己都觉得有时候看昏了,但是他们的设计确实很棒,也就是编程思想很6,想看懂确实需要点功夫,我这次也算是帮我自己再多学点了点东西,所以大家都加油吧!φ(゜▽゜*)?
补充一下,相关代码放这github上了,方便参考
###其实这个问题好办.
你自己实现一个BeanFactory,然后需要加前缀的bean都通过这个工厂来创建即可,这个时候你就可以通过value带入${}你的配置文件变量,重新定义bean name的生成规则
/**
* @see org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(DefaultListableBeanFactory)
* @see XmlBeanDefinitionReader#createBeanDefinitionDocumentReader()
* @see DefaultBeanDefinitionDocumentReader#createDelegate(XmlReaderContext, Element, BeanDefinitionParserDelegate)
* @see BeanDefinitionParserDelegate#parseBeanDefinitionElement(Element, BeanDefinition)
*/
至少需要重写3个类,还挺麻烦的
###自定义 BeanFactoryPostProcessor 就好了,这里面可以修改bean的定义,核心代码如下:
@Component
public class Test implements BeanFactoryPostProcessor, PriorityOrdered {
@Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory factory) throws BeansException {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) factory;
registry.registerBeanDefinition("newName", registry.getBeanDefinition("oldName"));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
newName和oldName自己通过规则运算一下