SpringFramework学习-(8)Bean的特性

bean具有哪些特性呢。
包括bean的生命周期,bean的集成,bean的扩容等。
具体我们先看官方文档里面怎么说,都说了哪些。

1.bean的官方文档翻译

1.1 bean的特性

1.1.1 生命周期回调

为了与容器中bean的生命周期管理交互,可以实现Spring的InitializingBeanDisposableBean接口。容器会在初始化和销毁bean时调用前者的afterPropertiesSet()和后者destroy()方法执行相应的动作。

在现代spring应用中,JSR-250的@PostConstruct@PreDestroy注解通常被认为是接收生命周期回调更好的实践。使用这些注解意味着bean不会与spring的接口耦合。更详细的信息请参考@PostConstruct@PreDestroy
如果不想使用JSR-250的注解,但又不想不与spring耦合,可以在bean定义时使用init-methoddestroy-method指定其初始化和销毁方法。
spring内部使用BeanPostProcessor的实例处理它能找到的任何回调接口并调用合适的方法。如果需要定制一些spring未提供的又可以立即使用的功能或生命周期行为,可以直接实现自己的BeanPostProcessor
更多信息请参考下面内容:容器扩展点。

除了初始化和销毁回调,spring管理的对象也可以实现Lifecycle接口,从而参与到容器本身的生命周期的启动和关闭中。

生命周期回调接口将在下面描述。

初始化回调

org.springframework.beans.factory.InitializingBean接口允许bean的所有必要属性都被容器设置好后执行初始化操作。InitializingBean接口只有一个方法:

void afterPropertiesSet() throws Exception;

其实并不建议使用InitializingBean 接口,因为没有必要把代码与spring耦合起来。可以使用@PostConstruct注解或在bean定义中指定初始化方法作为替代方案。在xml配置中,可以使用init-method属性指定一个无参的方法。在Java配置中,可以使用@BeaninitMethod属性,参考接收生命周期回调。
例如,下面这种方式不会与spring耦合:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}

上面这种方式严格来说与下面的方式是一样的:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

    public void afterPropertiesSet() {
        // do some initialization work
    }
}

销毁回调

实现了org.springframework.beans.factory.DisposableBean接口的bean允许在包含这的容器被销毁时获取一个回调。DisposableBean接口只有一个方法:

void destroy() throws Exception;

其实并不建议使用DisposableBean回调接口,因为没必要把代码与spring耦合。可以使用@PreDestroy注解或在bean定义中指定销毁方法作为替代方案。在xml配置中,可以使用<bean/>元素的destroy-method属性。在Java配置中,可以使用@BeandestroyMethod属性,参考接收生命周期回调。
例如,下面这种方式不会与spring耦合:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}

上面这种方式严格来说与下面是一样的:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {

    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

<bean>元素的destroy-method属性可以被赋予一个特殊的值“inferred”,它可以引导spring自动检测类中的公共closeshutdown方法(任何实现了java.lang.AutoCloseablejava.io.Closeable接口的类会匹配)。这个特殊的值“inferred”也可以设置在 <beans>元素的default-destroy-method属性上,这样会使所有的bean都执行这种行为(参考默认的初始化和销毁方法)。注意,这在Java配置中是默认行为。

默认的初始化和销毁方法

当不使用spring指定的InitializingBeanDisposableBean回调接口编写初始化和销毁回调方法时,我们一般会把这些方法命名为init(), initialize(), dispose()等等。理想状态下,在整个项目中这些方法的名字都是标准化的,以便所有的开发者都使用相同的名字并保证一致性。

可以配置spring容器在每个bean上自动寻找初始化和销毁的回调方法。这意味着,开发者可以直接使用init()作为初始化方法而不用为每一个bean配置init-method=”init”了。容器会在bean创建后调用这个方法(而且,这与标准的生命周期回调是一致的)。这个特性也需要为初始化和销毁方法定义统一的命名约定。

假设你的初始化方法叫init(),销毁方法叫destroy(),那么你的类将与下面的类很相似:

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }

}
<beans default-init-method="init">
    <bean id="blogService" class="com.foo.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>
</beans>

顶级元素<beans/>上的default-init-method属性会使得spring容器自动识别bean中的init方法作为初始化回调方法。当bean创建并装配后,如果它有这个方法就会在合适的时候被调用。

同样地,可以配置顶级元素<beans/>上的default-destroy-method属性声明销毁的回调方法。

当bean存在与上述约定不一样的回调方法名称时,可以使用<bean/>元素本身的init-methoddestroy-method代替默认的方法。

spring容器保证在bean的所有依赖都注入完毕时立马调用初始化回调方法。因此,初始化方法是调用在原生(非原生即代理)的bean引用上,这意味着AOP的拦截器还没开始生效。目标bean首先被完全创建,然后AOP代理及其拦截器链才被应用。如果目标bean与代理是分开定义的,我们甚至可以绕过代理直接与原生bean进行交互。因此,在初始化方法上应用拦截器是矛盾的,因为,这样做会把目标bean的生命周期与它的代理或拦截器耦合在一起,在直接使用原生bean时就会有很奇怪的语法。

组合使用生命周期机制

从spring 2.5 开始,有三种方式可以控制bean的生命周期行为:InitializingBeanDisposableBean回调接口;自定义的init()destroy()方法;@PostConstruct@PreDestroy注解。可以混合使用这些方式来控制给定的bean。

如果一个bean上配置了多种方式,并且每一种方式都配置为不同的方法名称,那么每一个配置的方法将按下面的顺序执行。但是,如果配置的相同的方法名称,那么只会执行一次。

同一个bean配置了多种方式,对于初始化方法将按以下顺序调用:

@PostConstruct注解的方法
InitializingBean接口定义的afterPropertiesSet()方法
自定义的init()方法

销毁方法也是同样的顺序:

@PreDestroy注解的方法
DisposableBean接口定义的destroy()方法
自定义的destroy()方法

启动和停止回调

Lifecycle接口为任何有自己生命周期需求的对象定义了一些基本的方法(例如,启动和停止一些后台进程)。

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();

}

所有spring管理的对象都可以实现这个接口。然后,当ApplicationContext接收到启动和停止的信号时,例如,运行时的stop/restart场景,它会级联调用此上下文内所有的Lifecycle实现。它是通过委托LifecycleProcessor实现的:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();

}

注意,LifecycleProcessor本身扩展了Lifecycle接口,同时添加了两个另外的方法用于在上下文刷新和关闭时做出响应。

注意,org.springframework.context.Lifecycle接口只能显式地接收启动和停止的通知,而不会在因上下文刷新导致自动启动时接收到通知。考虑使用org.springframework.context.SmartLifecycle接口实现更细粒度的控制,它可以检测到自动启动(当然,也包括启动阶段)。同样地,请注意停止通知并不保证在销毁之前来临:正常停止时,所有的Lifecycle bean将会在销毁回调前首先接收到停止通知,在上下文生命周期内的热刷新或强制刷新,仅仅销毁方法会被调用。
启动和停止的调用顺序可能是很重要的。如果两个对象存在依赖关系,那么依赖方将在其依赖启动之后启动,并在其依赖停止之前停止。但是,有些时候依赖并不是直接的。你可能只知道一些特定类型的对象将在另外一些类型的对象之前启动。在这种情形中,SmartLifecycle接口有另外一种选择,它的父接口Phased中定义了一个getPhase()方法。

public interface Phased {

    int getPhase();

}

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

启动时,低相位(phase)的对象先启动;停止时,顺序反过来。因此,实现了SmartLifecycle接口并且getPahse()返回Integer.MIN_VALUE的对象将先启动后停止。反过来,带有Integer.MAX_VALUE相位的对象将会后启动先停止(可能是因为它依赖于其它进程运行)。对于那些正常的未实现SmartLifecycle接口的Lifecycle对象,它们的相位默认值为0。因此,负相位的对象将在这些正常对象启动之前启动,停止之后停止,反之亦然。

可以发现SmartLifecycle中的stop方法可以接受一个回调。任何实现必须在其停止线程完成以后调用回调(Runnable)的run()方法。在必须的时候可以异步停止,因为LifecycleProcessor接口默认的实现DefaultLifecycleProcessor会等到超时直到每个相位的对象都调用了那个回调。默认每个相位的超时时间是30秒。可以在上下文中定义一个名叫“lifecycleProcessor”的bean重写默认的生命周期处理器。如果仅仅只修改超时时间,那么下面的定义就足够了:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

如前所述,LifecycleProcessor接口也定义了上下文刷新(onRefresh())和关闭(onClose())的方法。后者会简单地驱动关闭进程即使stop()方法被显式地调用了,但是它会发生在上下文关闭的时候。另一方面,刷新方法会带来SmartLifecycle bean的另一个功能。在上下文被刷新时(在所有对象都被实例化和初始化之后),这个方法会被调用,并且那时默认的生命周期处理器会检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为true,那么这个对象会在那时被启动而不会等待上下文显式地调用或它自己的start()方法(不像上下文的刷新,标准的上下文实现并不会自动启动)。相位的值及依赖关系将会按与上面描述相同的方式检查启动顺序。

在非web应用中优雅地关闭spring的IoC容器

这节只针对非web应用。spring中基于web的ApplicationContext的实现已经可以优雅地关闭容器了当相关的web应用停止时。
如果在非web应用环境中使用spring的IoC容器,例如,在富客户端桌面环境中,注册一个JVM的关闭钩子(shutdown hook)。这样做可以保证优雅地关闭并调用相关单例bean的销毁方法从而使得所有的资源都被释放。当然,你必须要正确地配置并实现这些销毁方法。

为了注册一个关闭钩子,调用ConfigurableApplicationContext接口的registerShutdownHook()方法。

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {

        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
                new String []{"beans.xml"});

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...

    }
}

ApplicationContextAware和BeanNameAware

ApplicationContext创建了一个实现了org.springframework.context.ApplicationContextAware接口的对象实例时,这个实例就拥有了ApplicationContext的引用。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

因此,bean可以编程式地操作创建了它的ApplicationContext,可以通过ApplicationContext接口,也可以把它强制转换成它的子类,比如ConfigurableApplicationContext,这样可以暴露更多的功能。一种使用方式是提取其它bean。有时这个能力很有用,但是,通常来说应该避免这样操作,因为这会使得代码与spring耦合,并且不符合控制反转的原则,而控制反转中合作者是以属性的方式提供的。ApplicationContext还提供了一些诸如访问文件资源、发布应用事件、访问消息源的方法。关于附加功能的描述请参考ApplicationContext的附加功能。

从spring 2.5 开始,自动装配就是另外一种获取ApplicationContext引用的方式。传统的constructor和byType装配模式(参考 自动装配合作者)能够分别为构造方法参数或setter方法参数提供一个ApplicationContext类型的依赖。另外,也可以使用注解方式自动装配字段或方法参数。如果这样做了,ApplicationContext就会被注入到一个字段、构造方法参数或者其它需要ApplicationContext类型的方法参数,当然这些字段或参数得带有@Autowired注解。更多信息请参考@Autowired

ApplicationContext创建了一个实现了org.springframework.beans.factory.BeanNameAware接口的类时,这个类就拥有了一个定义在它关联的对象定义中的名字的引用。

public interface BeanNameAware {

    void setBeanName(string name) throws BeansException;

}

这个方法会在操作了正常的属性之后但是在初始化方法之前运行,不管是InitializingBeanafterPropertiesSet()方法还是自定义的init()方法

1.1.2其它Aware接口

除了上面讨论的ApplicationContextAwareBeanNameAware接口,spring还提供了一系列的Aware接口使得bean可以从容器获取需要的资源(依赖)。大部分重要的Aware接口汇总在下面了——通常,它们的名字已经可以很好地解释它们的依赖类型了。

表1.1 Aware接口

名称注入的依赖描述
ApplicationContextAware声明ApplicationContextApplicationContextAware和BeanNameAware
ApplicationEventPublisherAware封闭的ApplicationContext的事件发布者ApplicatinContext的附加功能
BeanClassLoaderAware用于加载bean的类加载器实例化bean
BeanFactoryAware声明BeanFactoryApplicationContextAware和BeanNameAware
BeanNameAware定义的bean的名字ApplicationContextAware和BeanNameAware
BootstrapContextAware容器运行于其中的资源适配器BootstrapContext。一般只用于JCA所在的上下文中。JCA CCI
LoadTimeWeaverAware加载时处理类定义的织入者加载时使用AspectJ织入
MessageSourceAware配置的用于解决消息的策略(用于支持参数化及国际化)ApplicatinContext的附加功能
NotificationPublisherAwarespring中JMX通知发布者通知
PortletConfigAware当前容器运行于其中的PortletConfig。仅用于web上下文中。Portlet MVC 框架
PortletContextAware当前容器运行于其中的PortletContext。仅用于web上下文中。Portlet MVC 框架
ResourceLoaderAware配置的用于低级别访问资源的加载器资源
ServletConfigAware当前的容器运行于其中的ServletConfig。仅用于web上下文中。Web MVC 框架
ServletContextAware当前的容器运行于其中的ServletContext。仅用于web上下文中。Web MVC 框架

再次注意,使用这些接口将使你的代码与spring的API耦合,并且不会遵循控制反转的原则。因此,他们被推荐用于需要编程式访问容器的基础设施bean。

1.2bean定义的继承

bean的定义中可以包含大量的配置信息,包括构造方法参数、属性值以及容器指定的信息,比如初始化方法、静态工厂方法名等等。子bean定义可以从父定义中继承配置信息。子定义可以重写这些值,也可以在需要的时候添加新的。使用父子定义可以少码几个字,这是模板的一种形式。

如果编程式地使用ApplicationContext,子定义可以使用ChildBeanDefinition类。不过大部分用户并不这样使用,而是以类似ClassPathXmlApplicationContext的形式声明式地配置。在xml中,可以使用子定义的parent属性指定父bean。

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>

如果没有指定子定义的class,则会使用父定义的class,不过也可以重写它。后者必须保证子定义中的class与父定义中的class兼容,即它可以接受父定义中的属性值。

子定义可以从父定义继承作用域、构造方法参数值、属性值以及方法,也可以为它们添加新值。任何在子定义中定义的作用域、初始化方法、销毁方法或者静态工厂方法都会覆盖父定义中的配置。

其余的配置总是取决于子定义:依赖、自动装配模式、依赖检查、单例、延迟初始化。

上面的例子中在父定义中显式地使用了abstract属性。如果父定义不指定类,那么必须显式地标记abstract属性,如下:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>

父bean不能被实例化,因为它本身并不完整,并且被显式地标记为abstract了。当一个bean被定义为abstract,则它只能用做定义子bean的模板。如果试着使用abstract的父bean,比如通过另一个bean引用它或调用getBean()方法获取它,都会报错。同样,容器内部的preInstantiateSingletons()方法也会忽略定义为abstract的bean。

ApplicationContext默认预实例化所有的单例。因此,如果有一个仅用于模板的bean,并且指定了类,请保证把abstract属性设置为true,否则容器会尝试预实例化它。

1.3 容器扩展点

通常,开发者不需要自己实现ApplicationContext。然而,Spring提供了一些特殊的集成接口,实现这些接口可以扩展Spring。下面几个章节将介绍这些集成接口。

1.3.1 使用BeanPostProcessor自定义bean

可以实现BeanPostProcessor接口定义的回调方法,从而提供自己的实例化逻辑、依赖注入逻辑等。如果想要在spring容器完成了实例化、配置及初始化bean前后实现自定义的逻辑,那么可以插入一个或多个BeanPostProcessor的实现。

可以配置多重BeanPostProcessor实例,并且可以设置它们的order属性控制它们的执行顺序,不过只有当这些实例也实现了Ordered接口才可以。更多信息请参考BeanPostProcessorOrdered接口的javadoc,也可以参考下面关于BeanPostProcessor的编程式注册的说明。

BeanPostProcessor操作对象实例,也就是说IoC容器实例化bean实例后BeanPostProcessor才工作。
BeanPostProcessor是每个容器作用域的,即使使用容器继承也不行。声明在一个容器内的BeanPostProcessor只能处理那个容器内的bean。换句话说,一个容器内的bean不会被另一个容器的BeanPostProcessor处理,即使两个容器处于同一继承体系中。
为了改变实际的bean定义,需要使用BeanFactoryPostProcessor,参考 使用BeanFactoryPostProcessor自定义配置元数据
org.springframework.beans.factory.config.BeanPostProcessor接口包含两个回调方法。如果容器中一个类被注册为后处理器,那么对于容器创建的每一个bean实例,在它们初始化前后都会获得这个后处理器的一个回调。这个后处理器可以对bean实例采取任何行动,也可以完全忽略回调。一个bean后处理器一般用于检查回调接口或用代理包装bean。一些Spring的AOP基础类为了提供代理包装的逻辑都被实现为后处理器。

(此处要理解实例化(instantiation)和初始化(initialization)的区别,bean是先实例化再初始化的,后(置)处理器是针对实例化来说的,它是在bean实例化后起作用,但是它内部的两个方法分别在bean初始化前后执行拦截的作用)

ApplicationContext会自动检测所有实现了BeanPostProcessor接口的所有bean,接着把它们注册为后处理器,以便在其它bean创建的时候调用它们。后处理器bean可以像容器中其它bean一样部署。

注意,当使用@Bean工厂方法配置一个BeanPostProcessor实现类时,这个工厂方法的返回值类型必须是这个实现类本身或至少得是org.springframework.beans.factory.config.BeanPostProcessor接口,清晰地表明这个bean是后处理器的本性。否则,ApplicationContext在完全创建它之前不能通过类型检测到它。因为BeanPostProcessor需要早点被实例化以便应用到其它bean的初始化中,所以早期的类型检测是必要的。

虽然我们推荐使用ApplicationContext自动检测BeanPostProcessor,但是也可以使用ConfigurableBeanFactoryaddBeanPostProcessor方法编程式地注册。这在注册之前处理条件逻辑或者在同一继承体系上下文之间复制后处理器很有用。注意,编程式注册的BeanPostProcessor并不遵从Ordered接口的顺序,那是因为它们注册的顺序就是它们执行的顺序。同样地,编程式注册的BeanPostProcessor总是在通过自动检测注册的BeanPostProcessor之前执行。

实现了BeanPostProcessor接口的类是很特殊的,并且会被容器特殊对待。所有BeanPostProcessor和它们直接引用的bean会在启动时实例化,且作为ApplicationContext启动的特殊部分。然后,所有的BeanPostProcessor按顺序注册并应用到所有后来的bean上。因为AOP的自动代理也被实现为BeanPostProcessor本身,所以无论是BeanPostProcessor还是它们直接引用的bean都有获得自动代理的资格,且不会有切面织入它们。对于这些bean,你应该会看到一条信息日志消息:“Bean foo is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)”。注意,如果有bean通过自动装配或@Resource(可能会变成自动装配)被装配到了BeanPostProcessor,Spring在通过类型匹配查找依赖时可能会访问非预期的bean,从而使它们具有自动代理或其它后处理的资格。例如,如果通过不带name属性的@Resource注解在一个字段或setter方法上声明了一个依赖,并且这个字段或setter方法的名字并不直接与一个bean的名字通信,然后Spring将通过类型去匹配它们。

下面的例子展示了如何在ApplicationContext中编写、注册并使用BeanPostProcessor

例子,BeanPostProcessor风格的Hello World

第一个例子展示了基本用法,它显示了一个自定义的BeanPostProcessor实现在bean在被创建时调用它们的toString()方法并把结果打印到控制台。

请看下面自定义的BeanPostProcessor实现类的定义:

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>

从上面可以看到,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 = (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容器的常用方式。Spring的RequiredAnnotationBeanPostProcessor是其中一个例子——它可以保证bean中被注解@Required标记的属性能被依赖注入一个值。

1.3.2 使用BeanFactoryPostProcessor自定义配置元数据

接下来我们看的一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语法与BeanPostProcessor类型,主要区别是:BeanFactoryPostProcessor操作的是配置元数据,也就是说,Spring容器允许BeanFactoryPostProcessor读取配置元数据并可能在容器实例化这些bean之前改变它们,当然,BeanFactoryPostProcessor除外。

可以配置多重BeanFactoryPostProcessor,也可以通过设置它们的order属性控制它们的执行顺序,当然,只有实现了Ordered接口才能设置顺序。更多BeanFactoryPostProcessorOrdered接口的详细信息请参考javadoc。

如果想改变实际的bean实例,使用BeanPostProcessor即可。尽管在技术上使用BeanFactoryPostProcessor也是可以的,但是这样做会导致不成熟的bean实例化,违背了标准的容器生命周期,并可能导致负面影响,比如绕过bean的后置处理。
同样地,BeanFactoryPostProcessor也是每个容器作用域的,即使在容器继承体系中也是如此。在一个容器中定义的BeanFactoryPostProcessor,只能作用于这个容器的bean定义,不能处理另一个容器的bean定义,即使两个容器羽毛球同一个继承的体系也不可以。
ApplicationContext中定义的BeanFactoryPostProcessor是自动执行的,以便作用于这个容器中定义的元数据。Spring包含很多预先定义好的BeanFactoryPostProcessor,比如PropertyOverrideConfigurePropertyPlaceholderConfigure。也可以使用自定义的BeanFactoryPostProcessor,比如,用于注册自定义的属性编辑器。

ApplicationContext会自动检测所有实现了BeanFactoryPostProcessor接口的bean,并在适当的时候使用这些bean作为bean工厂后置处理器。可以像部署其它bean一样部署这些后置处理器。

BeanPostProcessor一样,一般不会配置BeanFactoryPostProcessor延迟初始化。如果没有别的bean引用一个Bean(Factory)PostProcessor,那这个后置处理器不会被实例化。因此,设置延迟初始化会被忽略,并且Bean(Factory)PostProcessor会自动初始化,即使在<beans/>元素设置default-lazy-init为true也没用。
例子:类名替代PropertyPlaceholderConfigurer

如果使用标准的Java Properties形式从外部文件中加载属性值,那么需要使用PropertyPlaceholderConfigurer。这样做可以定制环境相关的属性值,比如数据库URL和密码,而不需要冒很大的风险去修改容器中XML的定义。

查看下面的XML配置片段,配置DataSource时使用了占位符。这个例子展示了如何从外部文件配置属性。在运行时,PropertyPlaceholderConfigurer会替代其中的一些属性。这些被替代的占位符以${property-name}的形式定义,这与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>

实际的值是以标准Java Properties的形式存在于另一个文件中的:

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的系统属性。设置它的systemPropertiesMode属性可以改变它的行为,包含以下三个值:

never(0):从不检查系统属性。
fallback(1):如果指定文件中没有找到才检查系统属性。这是默认值。
override(2):在尝试查找指定的文件前先检查系统属性。这允许系统属性覆盖其它的属性来源。

更多信息请参考PropertyPlaceholderConfigurer的javadoc。

可以使用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会创建失败,对于非延迟初始化的bean,这种失败发生在ApplicationContextpreInstantiateSingletons()期间。

PropertyOverrideConfigurer

PropertyOverrideConfigurer,另外一个bean工厂后置处理器,与PropertyPlaceholderConfigurer类似,但又不像后者,原始的bean定义的属性可以有默认值或没有值。如果重写的Properties文件没有合适的值,那就使用默认值。

注意,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
它表示foo有一个fred属性,fred有一个bob属性,bob有一个属性sammy,这个sammy属性会被设置成123。

注意,重写的值总是字面值,它们不会被翻译成bean的引用,这种转换也适用于XML定义中指定的bean引用的原始值。
Spring 2.5中引入的context命名空间中有专门配置属性重写的元素。

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

1.3.3 使用FactoryBean自定义实例化逻辑

为对象实现org.springframework.beans.factory.FactoryBean接口,这些对象是他们自己的工厂。

FactoryBean接口是Spring窗口实例化逻辑的插入点。如果有一个非常复杂的初始化代码,它用Java能比XML更好地表述,那么就可以创建一个自己的FactoryBean,编写复杂的初始化逻辑在那个类中,并插入到窗口中。

FactoryBean接口提供了三个方法:

·Object getObject():返回这个工厂创建的一个实例。这个实例可以共享,由这个工厂单例还是原型决定。
·boolean isSingleton():如果返回的是单例则返回true,否则返回false。
·Class getObjectType():返回getObject()方法返回的对象的类型,如果事先不知道类型那就返回null。

FactoryBean的概念和接口在Spring框架中被大量使用,Spring自己至少使用了50个FactoryBean的实现。

当需要从容器获得实际的FactoryBean的实例时,调用ApplicationContextgetBean()方法时在bean的id前面加上&符号即可。所以对于一个id为myBean的FactoryBean,调用getBean(“myBean”)会返回FactoryBean创建的实例,然而,调用getBean(“&myBean”)则会返回FactoryBean的实例本身。

2.简化学习

从官方文档的翻译中可以看到,官方文档提供了很多的特性,包括这些特性该怎么使用。但是官方文档也给了很多的建议,哪些可以使用,哪些尽量不要使用。因此,那些不建议使用的方式就作为了解。
这里写图片描述
这里写图片描述
图片引自Spring Bean的生命周期(非常详细)

2.1继承

定义一个实体类:

package com.jd.relation;

public class Address {

    private String city;
    private String street;

    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getStreet() {
        return street;
    }
    public void setStreet(String street) {
        this.street = street;
    }
    @Override
    public String toString() {
        return "Address [city=" + city + ", street=" + street + "]";
    }

}

创建配置文件:
springrelation.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- address2继承了address1 -->
    <bean id="address1" class="com.jd.relation.Address" p:city="beijing" p:street="zhongguancun"></bean>

    <bean id="address2" p:street="dazhongsi" parent="address1"></bean>      
</beans>

配置文件中创建两个bean,address1和address2。为address1配置属性值,address2不设置city的值,但是通过parent 指定继承。那么address2能够获取到city的值呢。

测试:

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("springrelation.xml");
        Address address1 = (Address)ctx.getBean("address1");
        System.out.println(address1);
        Address address2 = (Address)ctx.getBean("address2");
        System.out.println(address2);
    }

结果:

Address [city=beijing, street=zhongguancun]
Address [city=beijing, street=dazhongsi]

2.2生命周期
创建实体

package com.jd.cycle;

public class Car {

    private String brand;
    private String madeAddress;
    private double price;

    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        System.out.println("Car setBrand 给属性赋值");
        this.brand = brand;
    }
    public String getMadeAddress() {
        return madeAddress;
    }
    public void setMadeAddress(String madeAddress) {
        this.madeAddress = madeAddress;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }
    @Override
    public String toString() {
        return "Car [brand=" + brand + ", madeAddress=" + madeAddress
                + ", price=" + price + "]";
    }
    public Car() {
        System.out.println("Car 构造方法被调用");
    }

    public void initMethod(){
        System.out.println("Car启动时调用initMethod");
    }
    public void destoryMethod(){
        System.out.println("Car销毁时调用destoryMethod");
    }

}

创建配置文件springcycle.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- bean的生命周期 -->
    <bean id="car" class="com.jd.cycle.Car" init-method="initMethod" destroy-method="destoryMethod">
        <property name="brand" value="Audi"></property>
    </bean> 
</beans>

测试:

public static void main(String[] args) {
        //在实例化bean的时候 调用构造器,再调用init方法
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("springcycle.xml");
        Car car = (Car)ac.getBean("car");
        System.out.println(car);
        ac.close();
    }

结果:

Car 构造方法被调用
Car setBrand 给属性赋值
Car启动时调用initMethod
Car [brand=Audi, madeAddress=null, price=0.0]
[org.springframework.context.support.ClassPathXmlApplicationContext]Closing org.springframework.context.support.ClassPathXmlApplicationContext@182decdb: startup date [Thu Dec 28 15:57:29 CST 2017]; root of context hierarchy
Car销毁时调用destoryMethod

由打印结果可以看到bean的周期
1.初始化调用构造方法,添加属性配置;
2.加载完成调用init-method 方法;
3.结束时候调用destroy-method 方法。

2.3bean扩展

知道了bean的生命周期,那么如果想对bean进行各种操作,就方便多了。
由官方文档可以看到:
spring内部使用BeanPostProcessor的实例处理它能找到的任何回调接口并调用合适的方法。如果需要定制一些spring未提供的又可以立即使用的功能或生命周期行为,可以直接实现自己的BeanPostProcessor 。

在bean的生命周期的基础之上,添加配置bean的后置处理器

package com.jd.cycle;

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

/**
 * bean后置处理器
 */
public class MyBeanPostProcessor implements BeanPostProcessor{

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //只针对某些bean做处理,所以可以根据beanName判断
        if("car".equals(beanName)){
            System.out.println("bean处理器postProcessor before "+bean+","+beanName);
        }
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean处理器postProcessor after "+bean+","+beanName);
//      Car car = new Car();
//      car.setBrand("BMW");
        Car car = (Car)bean;
        car.setBrand("BMW");
        return car;
    }

}

在配置文件中,我们看到给Car配置的brand最初属性值是Audi,通过后置处理器,我们可以对该项值进行偷梁换柱改成其他的值。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">


    <!-- bean的生命周期 -->
    <bean id="car" class="com.jd.cycle.Car" init-method="initMethod" destroy-method="destoryMethod">
        <property name="brand" value="Audi"></property>
    </bean>

    <!-- 配置bean的后置处理器 ,实现BeanPostProcessor并实现其中的两个方法,不需要做其他操作
        在init-method前后调用该处理器。
        所以可以在处理器中对设置的属性进行修改操作。如将Audi偷梁换柱成BMW
    --> 
    <bean class="com.jd.cycle.MyBeanPostProcessor"></bean>

</beans>

测试:

public class CycleTest {

    public static void main(String[] args) {
        //在实例化bean的时候 调用构造器,再调用init方法
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("springcycle.xml");
        Car car = (Car)ac.getBean("car");
        System.out.println(car);

        ac.close();
    }
}

测试结果:

1.Car 构造方法被调用
2.Car setBrand 给属性赋值
3.bean处理器postProcessor before Car [brand=Audi, madeAddress=null, price=0.0],car
4.Car启动时调用initMethod
5.bean处理器postProcessor after Car [brand=Audi, madeAddress=null, price=0.0],car
6.Car setBrand 给属性赋值
7.Car [brand=BMW, madeAddress=null, price=0.0]
[org.springframework.context.support.ClassPathXmlApplicationContext]Closing org.springframework.context.support.ClassPathXmlApplicationContext@182decdb: startup date [Thu Dec 28 16:27:09 CST 2017]; root of context hierarchy
8.Car销毁时调用destoryMethod

我们可以看到后置处理器的执行时间,并且之前配置的Audi被改成了BMW。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值