问答

怎样才能给spring的bean的xml配置文件里的bean id加上一个前缀?

作者:admin 2021-04-17 我要评论

定义bean的配置文件,想加一个统一的前缀,但不是所有的bean都要加,我把前缀写到配置文件里,不过xml里对应的占位符不解析。 spring-config-beans.xml ?xml ver...

在说正事之前,我要推荐一个福利:你还在原价购买阿里云、腾讯云、华为云服务器吗?那太亏啦!来这里,新购、升级、续费都打折,能够为您省60%的钱呢!2核4G企业级云服务器低至69元/年,点击进去看看吧>>>)

定义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]

image

###

起初开始看到这个问题,修改自定义beanName,我下意识就联想到了spring给的扩展接口BeanNameGenerator,但是以前没在xml注入bean的方式使过,确实有点心虚,试了一下,确实不行,所以才弱弱的在问题里评论了一句,看看有没有直接说使用扫描注解的方式注入可以不?可以的话,我就不再看了。结果昨天问了,今天一直没回复,想着干脆也看看,我就不信xml注入bean的方式没有给出修改beanName的扩展口子,毕竟这是spring啊,"缝合怪"大师

因为bean实例化之前,肯定先要有BeanDefinition的,也就是bean的定义,bean的元数据,所以对于BeanDefinition的修改,我先去看了一下BeanFactoryPostProcessor,这是常见的扩展接口,不过一看接口方法的签名坏了,这只是一个ConfigurableListableBeanFactory
image.png

ConfigurableListableBeanFactory你是可以根据beanName拿到对应的BeanDefinition,但我们要做的事,其实不是真正要BeanDefinition啊,我们恰好要修改beanName,所以用BeanFactoryPostProcessor这个扩展接口就不行了,因为使用它时BeanFactory中就已经生成好了beanNameBeanDefinition的对应关系。因此我当时想着就是从调用BeanFactoryPostProcessor的源码出发,往前看,再找其他可以扩展的接口

结果在BeanFactoryPostProcessor的调用类PostProcessorRegistrationDelegateinvokeBeanFactoryPostProcessors方法中,没找几行代码,就看到了另一个扩展接口BeanDefinitionRegistryPostProcessor

这名字一看就很对味儿,看一下方法签名
image.png

这回参数是BeanDefinitionRegistry,好家伙,这不就是找到根儿了么。顾名思义就是Bean定义的注册表或者注册中心啊,再看看其中接口的方法,注册一个BeanDefinition,删除一个BeanDefinition,这不都整全了嘛

image.png

那改个beanName就太简单了,举个栗子
image.png

看吧三行其实就完成了修改功能。虽然还没有完成题主的前缀注入问题,也算是完成一半,不过呢,嘿嘿,简单的bean这样做肯定可以修改beanName,但这里面鸭还有很多未知的坑!恰好我这次demoxml文件是之前用过的,自己莫名就踩上了,顺道提上几个

  1. 继承类问题
    假设AB都是bean,而且A extends B,若要修改AA_new,那还需要再修改BBeanDefinition中的parentNameA_new
    image.png
  2. alias别名问题
    假设A还有name配置,也就是idname同时存在,这时name的值就是别名,若要修改AA_new,则还需要修改BeanDefinitionRegistry中的别名映射关系
    image.png

好了,上面这两个坑说完了,最后来说说看起来很简单,但实则不太那么容易的注入前缀问题,也就是注入properties问题。我开始也是很天真,想着这不是so easy嘛,我定义的MyBeanDefinitionRegistryPostProcessor中加个属性prefix
image.png

然后配置文件上一配
image.png

结果一执行,没错,这个prefix死活都是${myprefix},它就不给姥姥我变!幸好我试了一下其他bean这个${myprefix}是可以注入的,不然我就可能宕机了。

还是看了一下源码,我突然就释然了。。。BeanDefinitionRegistryPostProcessor到底是什么时机在执行的,它不就是在BeanFactoryPostProcessor之前执行的么,而可以实现${myprefix}el表达式解析的逻辑PropertySourcesPlaceholderConfigurer其实就是个BeanFactoryPostProcessor的实现,因此BeanDefinitionRegistryPostProcessor执行的时候还没有轮到PropertySourcesPlaceholderConfigurer执行呢。

也就是说BeanDefinitionRegistryPostProcessor的实现类可能只能有干巴巴的逻辑,不能享受到过多的spring注入的东西了。它虽然是个bean,但是里面基本上不可能注入其他业务上的bean,毕竟BeanDefinitionRegistryPostProcessor中处理的不还都是人家beanbeanNameBeanDefinition

所以此时我本身就想来写回答了,说让到时候这个前缀就写死在代码里,不要写在配置文件里了,不过呢还是很揪心,感觉就差一点点就完美了,个人觉得不完美的答案,我宁愿还是不写。

于是我把此时的问题换成怎么在类中读取配置文件,因为常规的spring读取配置不行,我就想到了spring对于资源的抽象,毕竟配置文件本质来说也是一种资源,这里就不得不提到了其抽象接口org.springframework.core.io.Resource

而我开始不是说其他bean注入${myprefix}是可以的,而处理这套逻辑前肯定要先读取配置文件,进入PropertySourcesPlaceholderConfigurer中可以找到关键抽象类PropertySource,这是键值对的抽象基类,只要我们想办法将配置文件抽线成PropertySource,然后就可以调用它的getProperty方法获取配置的值了
image.png

而之前说配置文件的抽象是Resource,所以找到他们之间的关系就可以了,我是从返回参数入手。直接搜PropertySource的使用地方,只看方法返回的那项
image.png

就几个,而且前几个都是不是创建或者不太像,后2个,从类名就基本断定了,PropertySourceFactoryDefaultPropertySourceFactory,其中DefaultPropertySourceFactory还是实现PropertySourceFactory,看PropertySourceFactory的方法签名
image.png

虽然没有Resource,但是第二个参数EncodedResource还是很像的,进去一看,果然包含了Resource,哈哈哈
image.png

不清楚怎么把Resource包装成EncodedResource,那赶紧搜搜哪里调用的,然后把他们官方的调用copy过来就可以啦,果然有一个ConfigurationClassParser

image.png

ok,结合上面的逻辑,这下就简单了,我们首先给MyBeanDefinitionRegistryPostProcessor中再增加一个属性resource
image.png

同时配置文件也相应配置上,再顺道删除prefix的注入,反正也注入不进去,再新增了一个init方法作为初始化方法
image.png

那我们的init方法就来帮助我们根据resource来初始化prefix
image.png

这里有个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就完美注入进来啦
image.png

至于在MyBeanDefinitionRegistryPostProcessor的实际业务逻辑中怎么控制哪些bean要配置前缀,那这个就看题主自己的设计啦,方法很多,比如特定的都实现某个接口啊,或者类上加一些自己的某个标记注解啊,都行,反正BeanDefinitionbeanclass,以及还有很多其他属性。可以根据自己情况定制

以上就是我的小小看法啦,如果题主你对于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自己通过规则运算一下

版权声明:本文转载自网络,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本站转载出于传播更多优秀技术知识之目的,如有侵权请联系QQ/微信:153890879删除

相关文章
  • nginx响应速度很慢

    nginx响应速度很慢

  • 点击选中的多选框,会在已选那一栏显示

    点击选中的多选框,会在已选那一栏显示

  • PHP 多态的理解

    PHP 多态的理解

  • 关于C语言中static的问题

    关于C语言中static的问题

腾讯云代理商
海外云服务器