一起来读官方文档-----SpringIOC(07)

原创
09/29 17:27
阅读数 5.1K
1.8。容器扩展点

通常,应用程序开发人员不需要对ApplicationContext 实现类进行子类化。相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。接下来的几节描述了这些集成接口。

1.8.1。自定义bean实现BeanBeanPostProcessor接口

BeanPostProcessor接口定义了回调方法,您可以实现这些回调方法来修改默认的bean实例化的逻辑,依赖关系解析逻辑等。
如果您想在Spring容器完成实例化,配置和初始化bean之后实现一些自定义逻辑,则可以插入一个或多个自定义BeanPostProcessor。

您可以配置多个BeanPostProcessor实例,并且可以BeanPostProcessor通过实现Ordered 接口设置order属性来控制这些实例的运行顺序。

@Component
public class MyBeanPostProcessor implements BeanPostProcessor, Ordered {
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Override
	public int getOrder() {
		return 0;
	}
}
BeanPostProcessor实例操作的是bean的实例。
也就是说,Spring IoC容器实例化一个bean实例,
然后使用BeanPostProcessor对这些实例进行处理加工。

BeanPostProcessor实例是按容器划分作用域的。  
仅在使用容器层次结构时,这才有意义。
如果BeanPostProcessor在一个容器中定义一个,它将仅对该容器中的bean进行后处理。
换句话说,一个容器中定义的bean不会被BeanPostProcessor另一个容器中的定义进行后处理,
即使这两个容器是同一层次结构的一部分也是如此。

BeanPostProcessor修改的是bean实例化之后的内容,
如果要更改实际的bean定义(即bean definition)
您需要使用 BeanFactoryPostProcessor接口.

org.springframework.beans.factory.config.BeanPostProcessor接口恰好由两个回调方法组成。
当此类被注册为容器的post-processor时,对于容器创建的每个bean实例,post-processor都会在任何bean实例化之后并且在容器初始化方法(例如InitializingBean.afterPropertiesSet()或任何声明的init方法)被使用之前调用。
post-processor可以对bean实例执行任何操作,也可以完全忽略回调。
post-processor通常检查回调接口,或者可以用代理包装Bean。
一些Spring AOP基础结构类被实现为post-processor,以提供代理包装逻辑。

ApplicationContext自动检测实现BeanPostProcessor接口所有bean,注意是要注册成bean,仅仅实现接口是不可以的。

请注意,通过使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身或至少是org.springframework.beans.factory.config.BeanPostProcessor 接口,以清楚地表明该bean的post-processor性质。
否则,ApplicationContext无法在完全创建之前按类型自动检测它。
由于BeanPostProcessor需要提前实例化以便应用于上下文中其他bean的初始化,因此这种早期类型检测至关重要。

	@Bean
	public BeanPostProcessor myBeanPostProcessor(){
		return new MyBeanPostProcessor();
	} 
以编程方式注册BeanPostProcessor实例
虽然推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测,
但是您可以ConfigurableBeanFactory使用addBeanPostProcessor方法通过编程方式对它们进行注册。

当您需要在注册之前评估条件逻辑(比如应用场景是xxx条件才注册,xxx条件不注册时),
甚至需要跨层次结构的上下文复制Bean post-processor时,这将非常有用。

但是请注意,以BeanPostProcessor编程方式添加的实例不遵守该Ordered接口。
在这里,注册的顺序决定了执行的顺序。
还要注意,以BeanPostProcessor编程方式注册的实例总是在通过自动检测注册的实例之前进行处理,
而不考虑任何明确的顺序。
BeanPostProcessor 实例和AOP自动代理
实现BeanPostProcessor接口的类是特殊的,并且容器对它们的处理方式有所不同。
BeanPostProcessor它们直接引用的所有实例和bean在启动时都会实例化,
作为ApplicationContext的特殊启动阶段的一部分。

接下来,BeanPostProcessor以排序方式注册所有实例,并将其应用于容器中的所有其他bean。
但是因为AOP自动代理的实现是通过BeanPostProcessor接口,
所以在AOP的BeanPostProcessor接口实例化之前的
BeanPostProcessor实例或BeanPostProcessor实例直接引用的bean都没有资格进行自动代理。
并且对于任何此类bean都没有任何处理切面的BeanPostProcessor指向他们。
您应该看到一条参考性日志消息:
Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)。
这条消息的意思大概就是说这个bean没有得到所有BeanPostProcessor的处理

下面分析一下这条日志的逻辑:我们不用AOP的BeanPostProcessor用AutowiredAnnotationBeanPostProcessor来看这个情况

首先这条日志是在BeanPostProcessorChecker类中打印的,
这个类本身就实现了BeanPostProcessor,
Spring容器增加这个processor的代码如下:
    
        //获取所有的BeanPostProcessor类型的bean 
        //第一个true表示包括非单例的bean
        //第二个false表示仅查找已经实例化完成的bean,如果是factory-bean则不算入内
        String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
    
        //当前beanFactory内的所有post-processor数 +  1 + postBeanNames的数量
        //这个数量在后续有个判断
        //beanFactory.getBeanPostProcessorCount() 系统内置processor
        //1 就是BeanPostProcessorChecker
        //postProcessorNames.length 就是能扫描到的processor
        //这个数量之和就是目前系统能看到的所有processor
        //还有的就可能是解析完了某些bean又新增了processor那个不算在内 
        int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
        
        //add BeanPostProcessorChecker 进入beanPostProcessor链
        beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

BeanPostProcessorChecker中判断并打印上边那条日志的方法如下:
        @Override
		public Object postProcessAfterInitialization(Object bean, String beanName) {
		    //如果当前bean不是postProcessor的实例
		    //并且不是内部使用的bean
		    //并且this.beanFactory.getBeanPostProcessorCount()小于刚才相加的值
		    //三个都满足才会打印那行日志
			if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) &&
					this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {
				if (logger.isInfoEnabled()) {
					logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() +
							"] is not eligible for getting processed by all BeanPostProcessors " +
							"(for example: not eligible for auto-proxying)");
				}
			}
			return bean;
		}

        //当前beanName不为空,并且对应的bean是容器内部使用的bean则返回true    
		private boolean isInfrastructureBean(@Nullable String beanName) {
			if (beanName != null && this.beanFactory.containsBeanDefinition(beanName)) {
				BeanDefinition bd = this.beanFactory.getBeanDefinition(beanName);
				return (bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE);
			}
			return false;
		}
		
在看Spring createBean时遍历postProcessor的代码
    @Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}
就是通过这么一个循环来执行后置方法applyBeanPostProcessorsAfterInitialization,前置方法也是这样的

现在假设我们有一个自定义的beanPostProcessor里面需要注入一个我们自定义的beanA,
那么在beanPostProcessor被实例化的时候肯定会要求注入我们自定义的beanA,
那么现在就有多种情况了:
    1.我们用的set或者构造器注入那beanA会被实例化并注入
    2.如果我们用的@Autowired,当我们自定义的beanPostProcessor实例化
    在AutowiredAnnotationBeanPostProcessor实例化之前,那么beanA都无法被注入值
    如果在之后,则还是可以被注入值
    但是这两种情况都会打印这行日志
Bean 'beanA' of type [org.springframework.beanA] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

以下示例显示了如何在ApplicationContext中编写,注册和使用BeanPostProcessor实例。

示例:Hello World,BeanPostProcessor-style

第一个示例演示了基本用法。示例展示了一个自定义BeanPostProcessor实现,它在容器创建每个bean时调用该bean的toString()方法,并将结果字符串打印到系统控制台。

下面的清单显示了自定义的BeanPostProcessor实现类定义:

package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // 只需按原样返回实例化的bean
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // 我们可以返回任何对象引用
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

以下beans元素使用InstantiationTracingBeanPostProcessor:

<?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:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
   当上述bean (messenger)被实例化时,这个自定义的BeanPostProcessor实现将事实输出到系统控制台   -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

请注意实例化tracingbeanpostprocessor是如何定义的。它甚至没有名称,而且,因为它是一个bean,所以可以像其他bean一样进行依赖注入。

下面的Java应用程序运行前面的代码和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}

前面的应用程序的输出类似于以下内容:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
示例: RequiredAnnotationBeanPostProcessor

将回调接口或注解与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的一种常见方法。

一个例子是Spring的AutowiredAnnotationBeanPostProcessor——一个随Spring发行版附带的BeanPostProcessor实现,它确保被注解(@Autowired,@Value, @Inject等注解)注释的属性会被注入一个bean实例。

1.8.2。自定义配置元数据BeanFactoryPostProcessor

我们要看的下一个扩展点是 org.springframework.beans.factory.config.BeanFactoryPostProcessor。

该接口与BeanPostProcessor主要区别在于:BeanFactoryPostProcessor对Bean配置元数据进行操作。
也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并有可能在容器实例化实例任何bean之前更改元数据。

您可以配置多个BeanFactoryPostProcessor实例,并且可以BeanFactoryPostProcessor通过设置order属性来控制这些实例的运行顺序。但是,仅当BeanFactoryPostProcessor实现 Ordered接口时才能设置此属性。

如果希望更改实际bean实例(从配置元数据创建的对象),则需要使用BeanPostProcessor。
尽管在BeanFactoryPostProcessor中使用bean实例在技术上是可行的(例如,通过使用BeanFactory.getBean()),
但是这样做会导致过早的bean实例化,违反标准的容器生命周期。
这可能会导致负面的副作用,比如绕过bean的后处理。

另外,BeanFactoryPostProcessor实例的作用域为每个容器。
这只有在使用容器层次结构时才有用。
如果您在一个容器中定义了BeanFactoryPostProcessor,那么它只应用于该容器中的bean定义。
一个容器中的Bean定义不会被另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使这两个容器属于同一层次结构。

当BeanFactoryPostProcessor在ApplicationContext中声明时,它将自动运行,以便对定义容器的配置元数据应用更改。
Spring包括许多预定义的bean工厂后处理器,如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。
您还可以使用自定义BeanFactoryPostProcessor例如,用于注册自定义属性编辑器。

ApplicationContext自动检测部署其中实现BeanFactoryPostProcessor接口的任何bean。在适当的时候,这些bean会被bean factory post-processors来使用。

你也可以像部署任何其他bean一样部署这些自定义的bean factory post-processors。

示例:PropertySourcesPlaceholderConfigurer

您可以使用PropertySourcesPlaceholderConfigurer使用标准的Java属性格式将bean定义中的属性值外部化到单独的文件中。这样,部署应用程序的人员就可以自定义特定于环境的属性,比如数据库url和密码,而无需修改主XML定义文件或容器文件的复杂性或风险。

考虑以下基于xml的配置元数据片段,其中定义了具有占位符值的数据源:

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

该示例显示了从外部Properties文件配置的属性。
在运行时,将 PropertySourcesPlaceholderConfigurer应用于替换数据源的某些属性的元数据。将要替换的值指定为形式的占位符,该形式${property-name}遵循Ant和log4j和JSP EL样式。

实际值来自标准Java Properties格式的另一个文件:

jdbc.driverClassName = org.hsqldb.jdbcDriver
jdbc.url = jdbc:hsqldb:hsql://production:9002
jdbc.username = sa
jdbc.password = root

因此,${jdbc.username}在运行时将字符串替换为值“sa”,并且其他与属性文件中的键匹配的占位符值也适用。
在PropertySourcesPlaceholderConfigurer为大多数属性和bean定义的属性占位符检查。此外,您可以自定义占位符前缀和后缀。

	<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
		<property name="locations" value="classpath:jdbc.properties"/>
		//自定义前缀后缀
		<property name="placeholderPrefix" value="${"/>
		<property name="placeholderSuffix" value="}"/>
	</bean>
1.8.3。自定义实例化逻辑FactoryBean

您可以org.springframework.beans.factory.FactoryBean为本身就是工厂的对象实现接口。

该FactoryBean接口是可插入Spring IoC容器的实例化逻辑的一点。
如果您有复杂的初始化代码,而不是(可能)冗长的XML,可以用Java更好地表达,则以创建自己的代码 FactoryBean, 在该类中编写复杂的初始化,然后将自定义FactoryBean插入容器。

该FactoryBean界面提供了三种方法:

  • Object getObject():返回此工厂创建的对象的实例。实例可以共享,具体取决于该工厂是否返回单例或原型。
  • boolean isSingleton():true如果FactoryBean返回单例或false其他则返回 。
  • Class getObjectType():返回getObject()方法返回的对象类型,或者null如果类型未知,则返回该对象类型。

FactoryBeanSpring框架中的许多地方都使用了该概念和接口。Spring附带了50多种FactoryBean接口实现。Spring中的了解的少,但是Mybatis的MybatisSqlSessionFactoryBean很出名。

当您需要向容器询问FactoryBean本身而不是由它产生的bean的实际实例时,请在调用的方法时在该bean的id前面加上“&”符号(&)。
因此,对于给定id为myBean的一个FactoryBean ,调用getBean("myBean")返回的是FactoryBean生成的实例,getBean("&myBean")返回的是FactoryBean本身。

public class MyFactoryBean implements FactoryBean<MyBean> {

	@Override
	public MyBean getObject() throws Exception {
		return new MyBean();
	}

	@Override
	public Class<?> getObjectType() {
		return MyBean.class;
	}
}

<bean id="myFactoryBean" class="org.springframework.example.factoryBean.MyFactoryBean"/>

getBean("myFactoryBean")  返回的是MyBean实例
getBean("&myFactoryBean")  返回的是MyFactoryBean实例

展开阅读全文
打赏
2
8 收藏
分享
加载中
更多评论
打赏
0 评论
8 收藏
2
分享
返回顶部
顶部