Spring 容器有哪些拓展点?

目录

1、使用 BeanPostProcessor 自定义 bean 

(1)示例:Hello World,BeanPostProcessor 样式

(2)示例:AutowiredAnnotationBeanPostProcessor

2、使用 BeanFactoryPostProcessor 定义配置元数据

(1)示例:类名替换 PropertySourcesPlaceholderConfigurer

(2)示例:PropertyOverrideConfigurer

3、自定义实例化逻辑 FactoryBean


        一般的,开发者不需要采用实现 ApplicationContext 接口的方式来实现 Spring 容器的拓展,他们可以实现特殊的集成接口来实现此目的。

1、使用 BeanPostProcessor 自定义 beans

        BeanPostProcessor 接口定义了两个回调方法,支持在 bean 初始化前后进行回调,在回调方法中,支持用户对 bean 的实例化逻辑和依赖项解析逻辑等进行自定义。另外,你可以添加一个或多个 BeanPostProcessor 接口的实现来自定义 bean 的初始化逻辑。// 后置处理器,是在 Bean 被实例化、属性赋值后,实现一些自定义逻辑。该接口是在已经实例化的 bean 上进行操作。

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

        如果配置了多个 BeanPostProcessor 实例,那么在这些实例实现了 Ordered 接口后,可以通过在 BeanPostProcessor 实例中添加 order 属性来控制这些 BeanPostProcessor 实例的运行顺序,比如 AutowiredAnnotationBeanPostProcessor ,就实现了 Ordered 接口。所以当你自定义自己的 BeanPostProcessor 实例时,也应该考虑这个后置处理器的运行顺序。// 自定义的后置处理器应该考虑它们的运行顺序,因为 Spring 也自定义了很多后置处理器。

        注意,BeanPostProcessor 是对 bean 的实例进行操作。也就是说,只有当 Spring 容器实例化一个 bean 实例后,BeanPostProcessor 实例的逻辑才会开始执行

        BeanPostProcessor 实例的作用域是每一个容器。这只与按层次结构使用容器的有关。在当前容器中定义的 BeanPostProcessor 实例,它只会对该容器中的 bean 进行后置处理。它不会对另一个容器中的 beans 进行任何操作,即使这两个容器都是相同层次结构的一部分。// 按层次结构使用容器可以理解为 Controller 层、Service 层、DAO 层...

        BeanPostProcessor 接口通常用来检测回调接口,或者对 bean 进行代理包装。一些 Spring AOP 的基础类就是基于 BeanPostProcessor 接口来实现的,通过这种方式为 AOP 提供代理的包装的逻辑// AOP 的代理基于 bean 的后置处理器来实现

        ApplicationContext 容器会自动检测定义在配置文件中并实现了 BeanPostProcessor 接口的 beans,然后将这些 beans 注册为 BeanPostProcessor 的实例,等到容器在创建 bean 的时候,再调用这些后置处理器的回调方法。BeanPostProcessor 的实例跟 Spring 容器中的其他 beans 一样,都可以采用相同的方式部署在容器当中。

        需要注意的是,当在配置类中使用 @Bean 注解标识的工厂方法去声明一个 BeanPostProcessor 实例时,那么该工厂方法返回的类型应该是该后置处理器本身(BeanPostProcessor 接口的实现类),或者至少是 BeanPostProcessor 接口。通过明确的返回类型,可以清楚的向 Spring 容器表明该 bean 是一个后置处理器。否则,Spring 容器在完全创建这个 bean 前并不能自动通过类型进行识别。BeanPostProcessor 类型的 beans 需要提前进行实例化,然后才能应用到其他 beans 的初始化当中,所以,Spring 早期的类型检测非常重要。// BeanPostProcessor 实例需要提前被实例化。

以编程方式注册 BeanPostProcessor 实例

        虽然推荐使用Spring 容器自动检测来注册 BeanPostProcessor 实例,但你仍然可以通过实现 ConfigurableBeanFactory 接口中的 addBeanPostProcessor() 方法,通过编程方式向容器注册 Bean 后置处理器的实例。当你在后置处理器注册之前需要进行一些条件判断,或者需要跨容器复制 BeanPostProcessor 实例,那么这种方式将会很有用。不过,要注意的是,通过编程方式注册的 BeanPostProcessor 实例不会遵循 Ordered 接口定义的顺序。在这种方式下,后置处理器注册的顺序决定了执行的顺序。以编程方式注册的 BeanPostProcessor 实例总是在 Spring 容器自动注册的 BeanPostProcessor 实例之前被处理,所以,以编程方式注册的 BeanPostProcessor 实例不用考虑任何显式指定的顺序。// 可以手动注册后置处理器,以编程方式注册的 BeanPostProcessor 会优先于自动注册的BeanPostProcessor 执行

BeanPostProcessor 实例和 AOP 自动代理

        实现 BeanPostProcessor 接口的类具有特殊性,会被 Spring 容器区别对待。因为 Spring AOP 自动代理本身是通过 BeanPostProcessor 实现的,因此无论是 BeanPostProcessor 实例还是它们直接引用的 bean 对象都不适合进行自动代理,所以,这些后置处理器的实例并没有嵌入切面// BeanPostProcessor 实例不能进行 AOP 代理

        如果你使用自动装配或 @Resource 注解将 bean 装配到 BeanPostProcessor 中,那么 Spring 在通过类型匹配依赖项时,可能访问到意料之外的 bean。所以,针对这些 bean,需要使他们不能被自动代理或者不能成为 BeanPostProcessor。// BeanPostProcessor 依赖的 bean 不能被自动代理。

        下面的示例将演示如何在 ApplicationContext 中编写、注册和使用 BeanPostProcessor 实例。

(1)示例:Hello World,BeanPostProcessor 样式

        第一个示例演示了基本用法。该示例展示了一个自定义 BeanPostProcessor 的实现,该实现在容器创建每个 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>

       上边定义的 InstantiationTracingBeanPostProcessor ,它甚至都没有名称,但是,因为它是一个 bean,所以可以像其他 bean 一样对它进行依赖注入。(前面的配置还定义了一个由 Groovy 脚本支持的 bean 。Spring 动态语言支持在“动态语言支持”一章中有详细介绍)

        以下是 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

(2)示例:AutowiredAnnotationBeanPostProcessor

        扩展 Spring 容器的常用方式,是将回调接口或注解与自定义的 BeanPostProcessor 实现结合使用。一个具体例子是 Spring 的 AutowiredAnnotationBeanPostProcessor —— 一个随 Spring 发行版一起发布的 BeanPostProcessor 实现,自动装配带注解的字段、setter 方法和任意配置的方法。// Spring 有很多的 BeanPostProcessor,通过阅读源码可以详细的了解每一个后置处理器的调用时机。注意,这里的注解不包括自动装配的注解?

2、使用 BeanFactoryPostProcessor 定义配置元数据

        BeanFactoryPostProcessor 和 BeanPostProcessor 主要不同点在于,BeanFactoryPostProcessor 操作的是 bean 的元数据。也就是说,Spring 容器允许 BeanFactoryPostProcessor 读取配置元数据,并在容器实例化 BeanFactoryPostProcessor 实例以外的任何 bean 之前更改配置元数据。// Bean 工厂的后置处理器可以更改 bean 的配置元数据

@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}

        你可以配置多个 BeanFactoryPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanFactoryPostProcessor 实例的运行顺序。但是,如果 BeanFactoryPostProcessor 实现了Ordered 接口,则只能设置此属性。如果编写自己的 BeanFactoryPostProcessor,也应该考虑实现 Ordered 接口。// 通过 Ordered 接口来实现实例的执行顺序。

        如果你想要更改实际的bean实例,那么你需要使用BeanPostProcessor。虽然技术上可以在 BeanFactoryPostProcessor 中使用 bean 实例(例如,通过使用 BeanFactory.getBean() ),但这样做会导致 bean 过早的实例化,违反标准的容器生命周期。这可能会造成一些负面的影响,比如绕过 bean 的后置处理过程。// BeanFactory.getBean() 会导致 bean 过早的实例化。

        BeanFactoryPostProcessor 的作用域是每一个容器,这只与使用容器的层次结构有关。如果你在一个容器中定义了 BeanFactoryPostProcessor,那么它只应用于该容器中的 bean 定义。一个容器中的 Bean 定义不会被另一个容器中的 BeanFactoryPostProcessor 实例进行后置处理,即使两个容器属于同一个层次结构的一部分。// BeanFactoryPostProcessor 的作用域,现实中两个容器的情况是什么样子的?

        当在 ApplicationContext 中声明一个 BeanFactoryPostProcessor 后,processor 将自动运行,这主要为了方便对配置元数据进行更改。Spring 包括许多预定义的 bean 工厂后置处理器,比如 PropertyOverrideConfigurer 和PropertySourcesPlaceholderConfigurer 。您还可以使用自定义的 BeanFactoryPostProcessor —例如,注册自定义的属性编辑器。// bean 工厂后置处理器自动运行。

        ApplicationContext 会自动检测实现了 BeanFactoryPostProcessor 接口的 beans。它会在适当的时候把这些 beans 当作 BeanFactoryPostProcessor。你可以像部署其他 bean 一样部署这些工厂后置处理器的 beans。//  ApplicationContext 会自动检测 BeanFactoryPostProcessor 实例

        与 BeanPostProcessors 一样,通常不会为 BeanFactoryPostProcessors 配置延迟初始化(懒加载)。因为如果没有其他 bean 引用 bean(工厂) 后置处理器,那么该后置处理器根本不会被实例化。因此,懒加载配置将会被忽略,即使在 <beans /> 标签中将 default-lazy-init 属性设置为 true,Bean(Factory)PostProcessor 也会提前实例化。// 后置处理器不能被设置为懒加载,即使设置了,设置也无效。

(1)示例:类名替换 PropertySourcesPlaceholderConfigurer

        你可以使用 PropertySourcesPlaceholderConfigurer,通过使用标准 Java Properties 格式将 bean 定义中的属性值分离到单独的文件中。这样做可以使开发人员自定义特定环境的配置,比如数据库 url 和密码,从而减少需改 xml 配置文件的风险。//  支持 Java Properties 格式配置

        考虑以下基于 xml 配置的元数据片段,其中定义了一个带占位符值的 DataSource:

<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 定义的大多数属性和属性中的占位符。此外,你还可以自定义占位符前缀和后缀。// Configurer 提供占位符的解析过程。

        通过在 Spring 2.5 中引入的容器命名空间,你可以使用专用的配置元素配置 PropertySourcesPlaceholderConfigurer 。你可以在 location 属性中以逗号分隔的列表形式提供一个或多个位置,如下例所示:// 使用命名空间进行配置

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

        PropertySourcesPlaceholderConfigurer 不仅查找你指定的 properties 文件中的属性,默认情况下,如果在指定的属性文件中找不到属性,它还会检查 Spring 环境的 properties 和常规的 Java 系统properties 。// Spring Environment properties and regular Java System properties.

        你可以使用 PropertySourcesPlaceholderConfigurer 来替换类名,当你必须在运行时选择特定的实现类时,这有时很有用。下面的例子展示了如何做到这一点:

<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}"/>

        如果不能在运行时将类解析为有效的类,则在即将创建 bean 时解析失败,此时处在非惰性初始化 bean 的 ApplicationContext 容器的 preInstantiateSingletons() 阶段。

(2)示例:PropertyOverrideConfigurer

        PropertyOverrideConfigurer 是另一个 bean 工厂后置处理器。在 bean 的原始定义中,bean 的属性值可以有默认值也可以没有,PropertyOverrideConfigurer 的作用就是支持对 bean 属性值的覆盖,当定义属性覆盖的 Properties 文件中没有匹配到 bean 的属性项,那么该属性项的值还是使用容器中定义的默认值。// 支持属性覆盖

        需要注意的是,bean 定义本身并不知道正在被覆盖,所以不能立即从 XML 配置中看出正在使用覆盖配置器。如果多个 PropertyOverrideConfigurer 实例为同一个 bean 属性定义了不同的值,由于覆盖机制,最后使用最后一次被覆盖的值。// 如果存在多个覆盖,使用最后覆盖的

        Properties 文件配置行采用以下格式:

beanName.property=value

        下边展示了该格式的一个示例:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

        通过在 Spring 2.5 中引入的容器命名空间,可以使用专用的配置元素配置属性覆盖,如下例所示:

<context:property-override location="classpath:override.properties"/>

3、自定义实例化逻辑 FactoryBean

        可以为本身就是工厂对象的 bean 实现 FactoryBean 接口。

        FactoryBean 接口是 Spring 容器实例化中一个可以插入的点,你可以创建自己的 FactoryBean,在该类中编写复杂的初始化逻辑,然后将自定义的 FactoryBean 插入到容器中。// FactoryBean 是一个特殊的 bean 

        FactoryBean<T> 接口提供三个方法:

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}
  • T getObject():返回该工厂创建的实例。该实例是否可以被共享,取决于该实例是不是单例。
  • boolean isSingleton():返回 FactoryBean 是不是单例的布尔值,默认为 true。
  • Class<?> getObjectType():返回对象的类型,如果对象类型不能获取,返回 null。

        FactoryBean 接口在 Spring 框架中的许多地方都有使用。Spring 本身附带了 FactoryBean 接口的 50 多个实现。

        当你需要向容器请求 FactoryBean 的原实例,而不是它产生的 bean 时,在调用 ApplicationContext 的 getBean() 方法时,需将与符号 (&) 作为 bean 的 id 的前缀。所以,对于一个 id 为 myBean 的 FactoryBean ,调用 getBean("myBean") 将返回 FactoryBean 创建的 bean,调用 getBean("&myBean") 将返回 FactoryBean 的原实例(原实例由 Spring 容器创建)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值