Spring核心技术IoC容器(七)

本文将讨论如何关于在Spring生命周期中扩展Spring中的Bean功能。

容器的扩展

通常来说,开发者不需要通过继承ApplicationContext来实现自己的子类扩展功能。但是Spring IoC容器确实可以通过实现接口来增加一些功能。下面将描述一下这些接口。

通过BeanPostProcessor定义Bean

BeanPostProcessor接口定义了一些回调方法,开发者可以通过实现来自己的实例化逻辑,依赖解析逻辑等等。如果开发者只是想在Spring容器完成了实例化,配置以及初始化Bean之后来做一些操作的话,可以通过使用BeanPostProcessor来做到。

开发者可以配置多个BeanPostProcessor实例,开发者可以通过配置order属性来指定配置的BeanPostProcessor的执行顺序。当然,想要配置顺序必须同时要实现Ordered接口。如果开发者写了自己的BeanPostProcessor,开发者最好同时考虑实现Ordered接口。如果想了解更多的信息,可以参考BeanPostProcessor以及Ordered接口的javadoc。

BeanPostProcessors是操作Bean实例的,换言之,Spring IoC容器必须先初始化好Bean,然后BeanPostProcessors才开始工作。

BeanPostProcessors作用范围是基于容器的。当然,只有当开发者使用容器的层级的时候才是需要考虑的。如果开发者在容器中定义了一个BeanPostProcessor,这个实例只会在它所在的容器来处理Bean初始化以后的操作。换言之,一个容器中的Bean不会被另一个容器中定义BeanPostProcessor的在初始化以后进行后续处理,甚至就算两个容器同属同一个容器的部分。

org.springframework.beans.factory.config.BeanPostProcessor接口包含了2个回调方法。当这个接口的实例注册到容器当中时,对于每一个由容器创建的实例,这个后置处理器都会在容器开始进行初始化之前获得回调的调用。后置处理器可以针对Bean实例采取任何的操作,包括完全无视回调函数。Bean的后置处理器通常检查回调接口或者将Bean用代理包装一下。一些诸如Spring AOP代理的基础类都是通过Bean的后续处理器来实现的。

ApplicationContext会自动检查配置的Bean是否有实现BeanPostProcessor接口,ApplicationContext会将这些Bean注册为后续处理器,这样这些后续处理器就会在Bean创建之后调用。Bean的后续处理器就像其他的Bean一样,由容器管理的。

需要注意的是,在配置类的工厂方法上使用@Bean注解的时候,工厂方法的返回值应该是实现类本身,或者至少为org.springframework.beans.factory.config.BeanPostProcessor接口,来明确表明这个Bean是一个后置处理器。否则,ApplicationContext是无法在自动创建这个Bean的时候来知道它的属于后置处理器的。因为一个Bean后续处理器是需要按顺序来处理容器中的其他的Bean的,所以需要尽早严格检查类型。

尽管Spring团队推荐的注册Bean的后续处理的方式是通过ApplicationContext的自动检查,但是Spring也支持通过编程的方式,通过addBeanPostProcessor方法。这种方式有的时候也很有用,当需要在注册前执行一些条件判断的时候,或者在结构化的上下文中复制Bean后续处理器的时候尤其有效。需要注意的是,通过编程实现的BeanPostProcessors是会忽略掉Ordered接口的:由编程注册的BeanPostProcessors总是在自动检查到的BeanPostProcessors之前来执行的,而回忽略掉明确的顺序定义。


容器会特殊对待那些实现了BeanPostProcessor接口的类。所有的BeanPostProcessors和他们所引用的Bean都会在启动时直接初始化,作为ApplicationContext启动的一个特殊阶段。然后,所有的BeanPostProcessors会按顺序注册并应用到容器中的Bean。因为AOP自动的代理就是通过BeanPostProcessor来实现的,无论是BeanPostProcessor或者是它本身引用的Bean都不是自动代理的,也不是这样注入到Bean中的。对于这样的Bean,开发者应该注意到类似于info的日志信息Bean不适合由所有的BeanPostProcessor接口处理

需要注意的是,一旦开发者在BeanPostProcessor使用自动装载或者@Resource装载了Bean,Spring也许通过类型匹配进入了不被期望的Bean,因此令这些Bean无法自动代理或者后续处理。比如,如果开发者的Bean有注解@Resource的依赖注入而不是直接通过名字属性来进行依赖注入,那么Spring就会通过类型匹配来找到匹配的依赖。

下面的例子描述了如何写,注册和使用BeanPostProcessors

例子:Hello World BeanPostProcessor方式

第一个例子阐述了基本的用法。例子中展示了BeanPostProcessor的自定义实现,在Bean创建之后调用了toString()方法并打印到控制台。

参考如下代码:

package scripting;

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

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

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

    public Object postProcessAfterInitialization(Object bean,
            String beanName) throws BeansException {
        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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        http://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的配置中可以发现,InstantiationTracingBeanPostProcessor只是简单的定义成了一个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 = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}

输出的结果会类似下面:

Bean ‘messenger‘ created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

例子:RequiredAnnotationBeanPostProcessor

使用回调接口或者注解和BeanPostProcessor实现是常见的来扩展Spring IoC容器的方式。Spring中的一个例子就是RequiredAnnotationBeanPostProcessor就是用来确保JavaBean的标记有注解的属性确实注入了依赖。

通过BeanFactoryPostProcessor定义配置元数据

下一个扩展是org.springframework.beans.factory.config.BeanFactoryPostProcessor。语义上来说,这个接口有些类似BeanPostProcessor,只是有一个很大的区别:BeanFactoryPostProcessor操作Bean配置元数据,也就是说,Spring允许BeanFactoryPostProcessor来读取配置源数据,并且可能会在容器实例初始话Bean之前就改变配置元数据。

如前文所述,开发者可以配置多个BeanFactoryPostProcessors,而且开发者可以控制其具体执行的顺序。当然,配置顺序是必须实现Ordered接口的。如果实现了自己的BeanFactoryPostProcessor,开发者也应该考虑实现Ordered接口。可以参考BeanFactoryPostProcessorOrdered接口的javadoc来了解更多细节。

如果开发者希望改变实际的Bean实例(比如由配置元数据创建的对象),那么你需要使用BeanPostProcessor。但是BeanPostProcessor是可能通过BeanFactoryPostProcessor来和Bean实例在技术上进行协同工作的(比如,通过使用BeanFactory.getBean()函数),这样做会引起一些不成熟的Bean初始化问题,破坏容器标准的标准生命周期。这种行为可能带来负面的行为,比如绕过Bean的后续处理流程等等。同时,BeanFactoryPostProcessors作用范围是基于容器的。当然仅仅在使用容器层级的时候才相关。如果开发者在容器中定义了一个BeanFactoryPostProcessor,那么其配置只会应用到那个配置的容器之中。另一个容器中的Bean定义将不会通过BeanFactoryPostProcessors进行后续处理,就算两个容器都是同一层级中的一部分也是一样。

当在ApplicationContext中声明了后续处理器,Bean的后续处理器就会自动的执行,来实现在配置中定义的行为。Spring包括一些预定义好的后续处理器都可以使用,比如PropertyOverrideConfigurerPropertyPlaceholderCOnfigurer,也能够使用自定义的BeanFactoryPostProcessor,比如,来注册自定义属性编辑器。

ApplicationContext会自动的检查容器中实现了BeanFactoryPostProcessor接口的Bean。然后合适的时候将其用作工厂的后续处理器。开发者可以像其他的Bean一样来部署后续处理器。

如果使用BeanPostProcessor,开发者通常不会配置BeanFactoryPostProcessors来延迟初始化。因为如果没有其他的Bean引用Bean(Factory)PostProcessor,那么后续处理器根本不会实例初始化。所以,令其延迟初始化配置将被Spring无视掉,而且Bean(Factory)PostProcessor将无论是否配置延迟初始化都会被实例化,就算在<beans />配置了default-lazy-init属性为true

例子:类名替换BeanFactoryPostProcessor

开发者使用PropertyPlaceholderConfigurer来从使用标准JavaProperties格式的不同文件中来具体化属性的值。通过这个类,让开发者可以将应用部署到不同的环境使用不同的属性,比如数据库的URLs或者是用户名密码等,而不需要修改XML的定义。

参考下面的XML配置,使用DataSource占位符定义了数据库的一些配置。这个例子也展示了配置的属性通过额外的Properties文件来配置。在运行时,PropertyPlaceholderConfigurer应用到元数据上,将会替换掉DataSource的一些属性。替换的值,就是占位符的名字,这些属性配置是基于Ant/log4j/JSP EL形式的。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/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>

而实际占位符的值是配置在另一个文件中的,使用的是JavaProperties的格式,如下:

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

因此,字符串${jdbc.username}在运行时会被替换为sa,其他的占位符也会依次被替换掉。PropertyPlaceholderConfigurer会在Bean定义中检查占位符,而且,占位符的前缀后缀也能够自定义。

Spring 2.5后引入了context命名空间,可以专用的配置元素来配置属性占位符。多个地址可以使用逗号分隔符。

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

PropertyPlaceholderConfigurer不仅仅查看开发者指定的Properties文件。默认的话,它如果不能再指定的属性文件中找到属性的话,仍然会检查Java的System配置。开发者可以通过配置systemPropertiesMode属性来修改这个行为,这个属性有如下三种值:

  • nerver(0):从不检查系统属性
  • fallback(1):如果没有从指定的属性文件中找到特定的属性时检查系统属性,这个是默认值。
  • override(2):优先查找系统属性,而后才试着检查指定配置文件。系统的属性会覆盖指定文件的属性。

可以查阅PropertyPlaceholderConfigurer的Javadocs来了解更多的信息。

开发者可以使用PropertyPlaceholderConfigurer替代类名,这个在需要修改特定实现类的时候很有效。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/foo/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.foo.DefaultStrategy</value>
    </property>
</bean>
<bean id="serviceStrategy" class="${custom.strategy.class}"/>

如果类不能够在运行时解析,那么就会在创建Bean实例的时候失败。

例子:PropertyOverrideConfigurer

PropertyOverrideConfigurer,是另一个bean的后置处理器,有些类似于PropertyPlaceholderConfigurer,但是区别于后者,原有的定义可能有默认值,或者没有任何值。如果覆盖的Properties文件没有一个确切的Bean属性,就使用默认的定义。

需要注意的是,Bean定义本身是不会知道被覆盖的,所以,从XML的定义上很难直观看到配置被覆盖了。如果配置了多个PropertyOverrideConfigurer实例定义了不同的值,那么基于覆盖的机制,最后一个配置会生效。

属性文件的配置类似如下:

beanName.property=value

比如:

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

上面的实例文件,可以用于一个名为dataSource的Bean,包含着driver和url的属性。

复合的配饰名称也是支持的,但是要求每一个路径上的组件,需要是非空的,比如:

foo.fred.bob.sammy=123

指定的覆盖的值总是字面值;而不能是其他的Bean依赖,这一约定也会应用到原来的值特指Bean引用的情况下。

在Spring 2.5引入的context命名空间中,也可以指定配置覆盖的文件属性如下:

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

自定义FactoryBean的初始化逻辑

一些对象本身类似于工厂的可以考虑实现org.springframework.beans.factory.FactoryBean接口。

FactoryBean接口是一种类似于Spring IoC容器的插件化的逻辑。如果当开发者的代码有复杂的初始化代码,在配置上使用Java代码比XML更有效时,开发者可以考虑创建自己的FacotoryBean对象,将复杂的初始化操作放到类中,将自定义的FactoryBean扩展到容器中。

FacotryBean接口提供如下三个方法:

  • Object getObject():返回一个工厂创建的对象。实例可被共享,取决于返回Bean的作用域为原型还是单例。
  • boolean isSingleton():如果FactoryBean返回单例,为True,否则为False
  • Class getObjectType():返回由getObject()方法返回的对象的类型,如果对象类型未知,返回null。

FactoryBean概念和接口广泛用预Spring框架,Spring本身就有多于50个FactoryBean的实现。

当开发者需要一个FactoryBean实例而不是其产生的Bean的时候,在调用ApplicationContextgetBean()方法时,在其id之前加上&符号。也就是说,对于一个给定的FactoryBean,其id为myBean,调用getBean("myBean")返回其产生的Bean对象,而调用getBean("&myBean")返回FactoryBean实例本身。

时间: 2024-08-01 03:39:04

Spring核心技术IoC容器(七)的相关文章

Spring核心技术IoC容器(八)

本文针对自动装载的一些注解进行描述. 基于注解的容器配置 @Required注解 @Required注解需要应用到Bean的属性的setter方法上面,如下面的例子: public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } //

Spring核心技术IoC容器(六)

前文已经描述了Bean的作用域,本文将描述Bean的一些生命周期作用,配置还有Bean的继承. 定制Bean 生命周期回调 开发者通过实现Spring的InitializeingBean和DisposableBean接口,就可以让容器来管理Bean的生命周期.容器会调用afterPropertiesSet()前和destroy()后才会允许Bean在初始化和销毁Bean的时候执行一些操作. JSR-250的@PostConstruct和@PreDestroy注解就是现代Spring应用生命周期回

Spring核心技术IoC容器(五)

前文概述了Spring的容器,Bean,以及依赖的一些信息,本文将描述一下Bean的作用域 Bean的作用域 当开发者定义Bean的时候,同时也会定义了具体如何创建Bean实例的步骤.这些步骤是很重要的,因为只有通过这些配置,开发者才能创建实例对象. 开发者不仅可以控制多种多样的依赖到Bean之中,也可以配置Bean的作用域.这种方法是非常强大而且弹性也非常好,开发者可以通过配置来指定对象的作用域,而不用在Java类层次上来配置.Bean可以配置多种作用域.Spring框架支持5中作用域,有三种

Spring 核心技术 IoC容器(一)

IoC 容器 IoC容器和Bean简介 这章包括了Spring框架对于IoC规则的实现.Ioc也同DI(依赖注入).而对象是通过构造函数,工厂方法,或者一些Set方法来定义对象之间的依赖的.容器在创建这些Bean对象的时候同时就会注入这些依赖.这个过程是根本上的反转了,不再由Bean本身来控制实例化和定位依赖,而是通过服务定位来控制这个过程,也是IoC(控制反转)的由来. org.springframework.beans和org.springframework.context包是Spring框

Spring核心技术IoC容器(四)

前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置.本文将继续描述玩全部的依赖信息. 使用 depends-on 如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也就是另一个Bean的属性之一.多数情况下,开发者可以在配置XML元数据的时候使用<ref/>标签.然而,有时Bean之间的依赖关系不是直接关联的.比如:需要调用类的静态实例化器来出发,类似数据库驱动注册.depends-on属性会使明确的强迫依赖的Bean在引用之前就会初始化.下面的例子使用dep

Spring 核心技术IoC容器(二)

本文将继续前文,描述Spring IoC中的依赖处理. 依赖 一般的企业应用也不会只有一个对象(或者是Spring Bean).甚至最简单的应用都要有一些对象来协同工作来让终端用户看到一个完整的应用.下一部分将解释开发者从单独的定义Bean,到让这些对象在一个应用中协同工作. 依赖注入 依赖注入是一个让对象只通过构造参数,工厂方法的参数或者配置的属性来定义他们的依赖.这些依赖也是对象所需要协同工作的对象.容器在之后会在创建Bean的时候注入这些依赖.整个过程完全反转了Bean自己控制实例化或者,

Spring 核心技术IoC容器 (三)

本文将继续前文,针对依赖注入的细节进行描述 依赖注入细节 如前文所述,开发者可以通过定义Bean的依赖的来引用其他的Bean或者是一些值.Spring基于XML的配置元数据支持一些子元素<property/>以及<constructor-arg/>来达到这一目的. 内在值类型(Java Primitives类型,字符串等) 元素<property/>有value属性来以易读的形式配置一个属性或者构造参数.Spring的遍历就是用来讲这些字符串的值转换成指定的类型. &l

Spring之IOC容器加载初始化的方式

引言 我们知道IOC容器时Spring的核心,可是如果我们要依赖IOC容器对我们的Bean进行管理,那么我们就需要告诉IOC容易他需要管理哪些Bean而且这些Bean有什么要求,这些工作就是通过通过配置文件告诉Spring 的IOC容器.在我们的完成这些配置文件以后,如果IOC容易要实现对这些Bean的管理,除了资源的定位还有一个很重要的一步就是完成IOC加载初始化,也就是配置文件的载入过程.完成IOC容器加载初始化的方式只要有三种,第一种就是通过File文件加载,第二种是通过Classpath

Spring的IOC容器—依赖注入

前面一篇博客大致讲了一下Spring的IOC容器的原理,IOC即控制反转主要是依靠依赖注入的方式来实现的.依赖注入是指所依赖的对象不是由自己new出来的,而是用别的方式像打针似的注入进来. 其实说白了不管是控制反转还是依赖注入都说明了Spring采用动态.灵活的方式来管理各种对象. Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理.有以下几种注入方式: 1. Setter 注入 因为对于javaBean来说,我们可以通过setter和getter方法