本文就Spring配置项解析问题展开分析,这其中涉及到bean定义注册表后置处理、bean工厂后置处理、工厂bean等Spring相关的概念。本文将以上述问题作为切入点,进行分析和展开介绍。
问题背景介绍
<!-- 配置SqlSessionFactoryBean -->
<bean id="thirdPartySqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean"
depends-on="myDataSource">
<!-- datasource为自定义数据源 -->
<property name="dataSource" ref="myDataSource"/>
<property name="mapperLocations" value="classpath:mybatis/third-party/*.xml"/>
</bean>
<!-- 配置MapperScannerConfigurer -->
<bean id="thirdPartyMapperScannerConfigurer"
class="org.mybatis.spring.mapper.MapperScannerConfigurer"
depends-on="thirdPartySqlSessionFactory">
<property name="basePackage" value="com.alibaba.thirdparty.dao"/>
<property name="sqlSessionFactoryBeanName" value="thirdPartySqlSessionFactory"/>
</bean>
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
// 引入上述XML文件
@ImportResource("classpath*:/mybatis-third-party-config.xml")
public class MyDataSourceConfiguration {
// 声明自定义数据源
@Bean(name = "myDataSource")
public DataSource createMyDataSource(Environment env) {
// 返回数据源实例,具体代码略
}
}
项目启动后,我们发现一个原有的通过XML定义的HSF(HSF全称High-speed Service Framework,是阿里内部主要使用的RPC服务框架)客户端bean中的配置项无法被正常解析。由于这是一个与我们新引入的包无关的bean,大家都对问题产生的原因感到奇怪,也尝试了各种不同的处理方式,然而都没有效果。无奈之下,我们通过将整个XML文件改写为Java注解声明的形式,才最终解决了问题。相关代码如下所示:
<!-- 项目中一个原有的bean声明 -->
<bean id="myHsfClient" class="com.taobao.hsf.app.spring.util.HSFSpringConsumerBean" init-method="init">
<property name="interfaceName">
<value>com.taobao.custom.MyHsfClient</value>
</property>
<!-- version属性值为待解析的配置项占位符 -->
<property name="version">
<value>${hsf.client.version}</value>
</property>
</bean>
<!-- 其余bean声明省略 -->
// 改写后的Java注解声明方式
@Configuration
public class MyHsfConfig {
@HSFConsumer(serviceVersion = "${hsf.client.version}")
private MyHsfClient myHsfClient;
// 其余代码省略
}
虽然问题得到了解决,但是大家仍旧对这其中的原因不明所以。笔者在事后通过本地调试的方式,找到了问题的原因。这其中涉及到bean定义注册表后置处理、bean工厂后置处理、工厂bean等Spring相关的概念。本文将以上述问题作为切入点,进行分析和展开介绍。
XML配置项解析
// 处理属性值
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
// 遍历bean工厂中的bean名称集合
for (String curName : beanNames) {
// 跳过对自身的处理
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
// 通过bean的名称获取bean的定义
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
// 访问bean的定义,解析并替换其中的配置项占位符
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}
// 将配置项解析器注册添加至bean工厂,供基于注解的配置项解析处理器使用(后文将详细介绍)
// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
// 其余代码省略
}
问题原因分析
一些引申扩展
bean定义注册表后置处理
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// 部分代码省略
// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
boolean reiterate = true;
while (reiterate) {
reiterate = false;
// 从bean工厂中找出bean定义注册表后置处理器
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
// 如果当前处理器尚未被触发过
if (!processedBeans.contains(ppName)) {
// 初始化处理器,并加入到本次需要触发的处理器集合中
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
// 标记处理器为已被处理
processedBeans.add(ppName);
// 继续循环,因为当前集合中的处理器被触发后,可能会引入新的bean定义,其中可能包含新的bean定义注册表后置处理器需要被触发
reiterate = true;
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
// 触发集合中的处理器的bean定义注册表后置处理动作
// * 本文案例中,我们在第三方XML文件中引入的MapperScannerConfigurer,便是在此时被触发的
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
}
}
/*
* BeanDefinitionRegistries are called early in application startup, before
* BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been
* loaded and any property substitution of this class' properties will fail. To avoid this, find
* any PropertyResourceConfigurers defined in the context and run them on this class' bean
* definition. Then update the values.
*/
// 上面这段英文注释体现了作者的考虑,即文中描述的情况
private void processPropertyPlaceHolders() {
// 获取配置项处理器实例,即PropertySourcesPlaceholderConfigurer处理器
Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext)
.getBeanFactory().getBeanDefinition(beanName);
// 构造一个新的bean工厂
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 将自身的bean定义注册到这个bean工厂中
factory.registerBeanDefinition(beanName, mapperScannerBean);
// * 对这个bean工厂执行配置项后置处理操作
for (PropertyResourceConfigurer prc : prcs.values()) {
prc.postProcessBeanFactory(factory);
}
PropertyValues values = mapperScannerBean.getPropertyValues();
// 使用被解析处理过的值更新原有的值
this.basePackage = updatePropertyValue("basePackage", values);
this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
}
}
bean工厂后置处理
public class ConfigurationBeanFactoryMetaData implements BeanFactoryPostProcessor {
private Map<String, MetaData> beans = new HashMap<String, MetaData>();
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
// 遍历bean工厂中的bean定义
for (String name : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(name);
String method = definition.getFactoryMethodName();
String bean = definition.getFactoryBeanName();
// 如果存在工厂方法元数据(如通过@Bean注解声明的bean),则将相关信息记录下来
if (method != null && bean != null) {
this.beans.put(name, new MetaData(bean, method));
}
}
}
}
工厂bean
// MyBatis配置文件路径。配置文件包含数据源、映射器等信息
String resource = "org/mybatis/example/mybatis-config.xml";
// 创建配置文件输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建SqlSession实例
try (SqlSession session = sqlSessionFactory.openSession()) {
// 获取BlogMapper映射器
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 执行查询语句
Blog blog = mapper.selectBlog(101);
}
<bean id="myInputStream" class="org.apache.ibatis.io.Resources"
factory-method="getResourceAsStream">
<constructor-arg value="org/mybatis/example/mybatis-config.xml"/>
</bean>
<bean id="mySqlSessionFactoryBuilder" class="org.apache.ibatis.session.SqlSessionFactoryBuilder"/>
<bean id="mySqlSessionFactory" class="org.apache.ibatis.session.SqlSessionFactory"
factory-bean="mySqlSessionFactoryBuilder"
factory-method="build">
<constructor-arg ref="myInputStream"/>
</bean>
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
// 映射器接口类型
private Class<T> mapperInterface;
// 通过该方法获取我们实际需要的映射器实例
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
// 获取实际的bean的类型,即映射器接口类型
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
}
基于注解的配置项解析
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(bean.getClass(),
ConfigurationProperties.class);
if (annotation != null) {
postProcessBeforeInitialization(bean, beanName, annotation);
}
// 处理方法级别的@ConfigurationProperties注解
// 这里的this.beans即为ConfigurationBeanFactoryMetaData实例
annotation = this.beans.findFactoryAnnotation(beanName, ConfigurationProperties.class);
if (annotation != null) {
postProcessBeforeInitialization(bean, beanName, annotation);
}
return bean;
}
思考与总结
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
注释:
-
除了被@Configuration注解标注的类外,被@Component等注解标注的类也被Spring视为配置类,不过是轻量级(lite)配置类,参见《Spring Core Technologies》1.12章节 - Java-based Container Configuration。
-
参见《Spring实战》6.1.1小节 - 理解Spring的环境抽象。
-
对于匹配到多个bean的情况,会优先取包含@Primary注解或者优先级高的bean,如果无法判断,则会抛出NoUniqueBeanDefinitionException异常;对于没有匹配到bean的情况,抛出NoSuchBeanDefinitionException异常。
-
具体代码详见AbstractAutowireCapableBeanFactory#getTypeForFactoryBean方法。
-
即调用BeanDefinitionRegistryPostProcessor接口中定义的postProcessBeanDefinitionRegistry方法。
-
具体代码详见AnnotationConfigUtils#registerAnnotationConfigProcessors方法。
-
某些bean工厂后置处理器也会向bean工厂中添加新的bean定义,比如我们后文将讨论的HsfConsumerPostProcessor处理器。
-
参见《Spring Core Technologies》1.8.3小节 - Customizing Instantiation Logic with a FactoryBean:If you have complex initialization code that is better expressed in Java as opposed to a (potentially) verbose amount of XML, you can create your own FactoryBean, write the complex initialization inside that class, and then plug your custom FactoryBean into the container.
-
在FactoryBean的代码注释中,我们可以看到,该类是在2003年3月份被创建的。而根据《History of Spring Framework and Spring Boot》一文,Spring 0.9的发布时间为2003年6月。
-
具体代码详见AbstractAutowireCapableBeanFactory#populateBean方法。
-
具体代码详见AbstractAutowireCapableBeanFactory#initializeBean方法。
参考资料:
-
《Spring Core Technologies》:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html
-
《Spring实战》:https://book.douban.com/subject/36142064/
-
《MyBatis 3源码深度解析》:https://book.douban.com/subject/34836563/
-
《History of Spring Framework and Spring Boot》:https://www.quickprogrammingtips.com/spring-boot/history-of-spring-framework-and-spring-boot.html
-
《SpringCloud、Nginx高并发核心编程》:https://book.douban.com/subject/35396578/
-
mybatis - getting started:https://mybatis.org/mybatis-3/getting-started.html文章来源:https://www.toymoban.com/news/detail-450054.html
-
github - mybatis-spring issue #58:https://github.com/mybatis/spring/issues/58文章来源地址https://www.toymoban.com/news/detail-450054.html
-
github - mybatis-spring pull request #59:https://github.com/mybatis/spring/pull/59
到了这里,关于深入Spring配置项问题,全面解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!