Spring 容器扩展点


Spring ioC容器扩展点

通常来说,开发这不需要继承ApplicationContext来扩展功能,相反,Spring容器可以通过许多指定的接口进行扩展。

使用BeanPostProcessor进行bean自定义

BeanPostProcessor定义的回掉方法可以用来实现自己bean初始化逻辑,解决依赖逻辑等,如果需要在Spring容器对bean进行实例化、配置、初始化后进行一些自定义操作,也可以实现一系列BeanPostProcessor添加到容器中

BeanPostProcessor会作用于bean(或者说时对象)上,每当Springioc容器初始化一个bean,BeanPostProcessor就会被回掉。

BeanPostProcessor作用于一个容器中,如果使用父子关系的容器,bean只会被同一个容器中的BeanPostProcessor所处理。

如果需要修改bean的定义元数据,即beanDefinition,需要使用BeanFactoryPostProcessor

org.springframework.beans.factory.config.BeanPostProcessor由2个回掉方法组成,当一个对象被注册为BeanPostProcessor,只要当Spring容器创建bean,BeanPostProcessor就会在bean的初始化前和初始化后被回掉,这次期间,BeanPostProcessor可以对bean做任何事情,比如:检查bean被回掉的次数、Spring Aop就是通过BeanPostProcessor给bean实现代理逻辑

ApplicationContext会自动检测到容器中实现BeanPostProcessor接口的bean,ApplicationContext会注册这些bean以便后期被调用,BeanPostProcessor可以就像注册普通bean一样被注册到容器。

手动注册BeanPostProcessor实例:

虽然推荐使用ApplicationContext自动检测来注册,但是也可以通过ConfigurableBeanFactoryaddBeanPostProcessor方法来手动注册,这种做法的好处就是可以在注册之前实现一些逻辑。

注意:手动注册的BeanPostProcessor不会遵循Ordered接口来配置执行顺序,手动注册的BeanPostProcessor总是优先于自动检测的BeanPostProcessor执行。

BeanPostProcessor实例和AOP 自动代理:

实现BeanPostProcessor接口的类将会被容器区分对待,所有的BeanPostProcessor的实例以及他们直接依赖的bean都将在应用启动的时候实例化,作为ApplicationContext的一个特殊启动阶段。

因为AOP自动代理是通过BeanPostProcessor实现的,所以BeanPostProcessor实例或它们直接引用的bean都不适合进行自动代理,因此不会被注入切面。

对于这样的Bean,通常会看到以下日志信息:

Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)

如果我们通过@autowire@Resource(可能会退回到自动装配)注入bean到BeanPostProcessor实例中,则Spring在搜索类型匹配的依赖项候选对象时可能会访问意想不到的Bean,因此使这些bean不符合自动代理或被后置处理。

案例:BeanPostProcessor

BeanPostProcessor的基本使用,以下案例将会在bean创建完成之前调用bean的toString()方法获取bean信息:

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}
<?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>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

以上xml配置使用Groovy定义bean:messager

使用BeanPostProcessor

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

使用BeanFactoryPostProcessor自定义配置元数据

org.springframework.beans.factory.config.BeanFactoryPostProcessor也是Ioc容器的扩展点,和BeanPostProcessor类似,但是有一个主要的区别:BeanFactoryPostProcessor操作bean的配置元数据(beanDefinition),Spring Ioc容器能够让BeanFactoryPostProcessor读取到配置元数据并有可能在容器实例化除BeanFactoryPostProcessor实例以外的任何bean之前更改它。

我们可以配置多个BeanFactoryPostProcessor实例,并通过order属性来控制它们的调用顺序,但是只能通过实现Ordered接口来配置顺序,如果需要使用BeanFactoryPostProcessor则需要考虑到实现Ordered接口。

public interface BeanFactoryPostProcessor {

    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

如果需要改变实际返回的bean实例需要使用上述的BeanPostProcessor,从技术上来说,在postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)中可以使用BeanFactory.getBean()来获取bean然后修改它,但是这回导致提前bean实例化并且打乱容器正常的生命周期,这种做法会带来许多负面影响,比如:bean不会被所有的BeanPostProcessor做后置处理

ApplicationContext会自动调用这些BeanFactoryPostProcessor来修改bean的配置元数据,Spring内置了一些实现:PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer,也可以自定义BeanFactoryPostProcessor,比如:实现自定义的bean属性修改器

Bean(Factory)PostProcessor会忽略延迟加载配置,比如:标签的default-lazy-init属性

BeanFactoryPostProcessor使用案例:PropertySourcesPlaceholderConfigurer

PropertySourcesPlaceholderConfigurer是Spring内置的占位符处理器,就是使用的BeanFactoryPostProcessor来实现,通过PropertySourcesPlaceholderConfigurer,我们可以在bean的属性配置中使用占位符:

<beans>
        <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>
</beans>
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

在启动过程中。PropertySourcesPlaceholderConfigurer会读取外部的Properties文件,来替换相应的占位符。

PropertySourcesPlaceholderConfigurer不仅会在Properties文件中查找值,默认情况下,如果在Properties文件不能找到相应的属性,会自动的在Spring的Environment对象中寻找,甚至是System属性

小技巧:使用PropertySourcesPlaceholderConfigurer在运行时决定接口实现类:

<beans>
    <bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <value>classpath:com/something/strategy.properties</value>
        </property>
        <property name="properties">
            <value>custom.strategy.class=com.something.DefaultStrategy</value>
        </property>
    </bean>

    <bean id="serviceStrategy" class="${custom.strategy.class}"/>
</beans>

如果在运行时找不到指定的类,则在ApplicationContextpreInstantiateSingletons()阶段就会报错

BeanFactoryPostProcessor使用案例:PropertyOverrideConfigurer

PropertyOverrideConfigurer也是Spring的常用BeanFactoryPostProcessor实现类,和PropertySourcesPlaceholderConfigurer类似,但不同的是原始定义对于bean属性可以具有默认值,也可以完全没有值。 如果覆盖的属性文件没有某个bean属性的条目,则使用默认的上下文定义。

<beans>
        <bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
            <property name="location" value="classpath:myproperties.properties" />
        </bean>

        <bean id="person" class="com.sample.Employee" >
               <property name="name" value="Dugan"/>
               <property name="age" value="50"/>       
        </bean> 
</beans>

myproperties.properties:

person.age=40
person.name=Stanis

如上配置,当获取personbean时:

Employee e  = (Employee)context.getBean(Employee.class);

e.getAge() => 40    //被properties文件的值覆盖掉
e.getName() => "Stanis"   //被properties文件的值覆盖掉

使用FactoryBean自定义实例化逻辑

如果一个对象本身作为工程类的话,可以实现org.springframework.beans.factory.FactoryBean接口,FactoryBean接口是Spring Ioc容器实例户逻辑的一个插件类,如果有一些复杂的实例化逻辑使用FactoryBean来实现比用XML配置精简的多:

FactoryBean接口提供3个方法:

  • Object getObject(): 获取FactoryBean创建的对象,对象是否为单实例取决于第二个方法返回值。
  • boolean isSingleton(): 如果FactoryBean返回单实例则返回true,否则返回false
  • Class getObjectType(): 返回 getObject() 方法放回对象的类型,如果为止则返回null

FactoryBean在Spring中被大量使用,超过50个类实现了FactoryBean接口。

当我们需要获取FactoryBean本身而不是FactoryBean产生的对象时,需要在bean 的id前加上&。比如:有一个FactoryBean的id为myBean,当调用getBean("myBean")时,将会得到FactoryBeangetObject()方法产生的对象,而调用getBean("&myBean")时会得到FactoryBean本身


文章作者: Ubi-potato
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Ubi-potato !
评论
  目录