Spring Framework 官方文档中文版—Core_part_2

内容过长,core部分分开发布,core章节第1部分点击:Spring Framework 官方文档中文版—Core_part_1
主目录或专题地址可以点击:主目录, 专题地址

自动装(Autowiring)配协作者

Spring容器可以自动装配协作bean之间的关系。你可以通过检查ApplicationContext的内容,让Spring自动为你的bean解析协作者(即其他bean)。自动装配有以下优点:

  • 自动装配可以显著减少指定属性或构造方法的场景。

  • 自动装配可以随着你的对象改变而改变。例如,你需要往一个类中增加依赖,那么无需修改配置就可以自动满足这个需求。因此自动装配在开发过程中非常有用,当代码库趋于稳定时,不需要否定显示的装配。(最后一句原文:Thus autowiring can be especially useful during development, without negating the option of switching to explicit wiring when the code base becomes more stable.)

当使用基于XML的配置时,你需要在标签内指定属性autowire自动装配功能有4种模式。你可以为每个bean单独指定自动装配,所以也就可以选择其中一个进行自动装配。

下面是自动装配的4种模式:

模式 说明
no (默认)不自动装配。bean的引用必须通过ref属性标签来指定。对于较大型的项目,并不推荐修改此默认配置,因为明确的指定bean可以更易于管理和更可控。在某种意义上,这相当于是记录了系统的结构
byName 根据名称自动装配。Spring自动寻找同名的bean作为需要装配的属性。例如,如果设置了一个bean定义为byName自动装配,并且含有一个master属性(也就是说它有一个setMaster(..)方法) ,Spring寻找到名称为master的bean定义,并设置到其属性中
byType 如果容器中恰好和属性类型相同的bean,那么允许将这个bean自动装配到属性。如果这种bean的数量为多个则会抛出异常,表明你并不适合用词类型的自动装配,如果没有此类型的bean匹配,则不会发生什么异常,属性也就有可能没有被设置
constructor 和bytype类似,但是是用于构造函数参数,如果容器中没有一个和构造函数参数类型一样的bean,则会引发致命异常

使用byType 或 constructor 自动装配模式,你可以装配数组和集合类型。这种情况下,容器内所有与预期类型匹配的bean都会被装配至此数据或集合。你可以自动装配强类型的map,如果key类型正好的String。自动装配的map的value是由和预期类型一样的bean组成,key的值会包含bean的名称。

你可以将自动装配的行为与依赖检查相结合,依赖检查是在自动装配结束后开始执行的。

自动装配的局限性和缺点*

自动装配在项目中应用时,要么全部使用,不要部分使用。如果一般不使用自动装配,只是在少数的一两个bean定义中使用它,自动装配可能会让开发者产生混淆。

考虑自动装配的局限性和缺点:

  • property 和 constructor-arg中明确的依赖会覆盖自动装配。你不能自动装配简单类型的属性,如原始类型,String,Class(以及这些简单类型组成的数组)。这个局限是因为就是这么设计的

  • 明确的装配要比自动装配准确。尽管如上面的表所示,Spring小心的避免猜测所导致预期外的结果,但是项目中被Spring管理的对象关系并不会被明确记录。

  • 可能无法从Spring容器生成文档的工具获得使用连接信息。

  • setter方法或构造函数所指的的类型,容器中可能会存在多个bean匹配上。对于数组,集合或map来说这并不是一个问题。然而对依赖来说只需要一个结果的话,这并不会被有效的解决。如果非唯一值存在,则会抛出致命的异常。

在下面,你可以有以下几个选择:

  • 放弃自动装配,全部使用显示的(常规)装配

  • 将其autowire-candidate属性设置为false,避免bean定义进行自动装配,如下一节所示。

  • 指派一个单独的bean定义作为主要对象,需要将标签的属性primary属性设置为true。

  • 使用基于注解的容器配置,实现更细粒度的配置。

自动装配中排除一个bean

基于每个bean的配置,你可以将一个bean从自动装配中排除。在Spring XML格式中,设置标签中的属性autowire-candidate为false;容器会让此bean定义无法进行自动装配(包括注解风格的配置例如@Autowired)。

属性autowire-candidate只对基于类型的自动装配有效。它不对明确的装配例如byName类型的自动装配有效,即使某bean没有被标记为autowire选项,它也会被解析。因此,只要名称匹配,自动装配总会注入一个bean。

你也可以利用表达式来限制自动装配候选者的名称。最顶层标签default-autowire-candidates属性接受一个或多个表达式。例如,限制候选bean的名称是以 Repository 结尾,只需要将表达式写为 *Repository 。要写入多个表达式,则表达式之间用逗号分隔。对于一个bean定义来说,明确指定autowire-candidate属性的值为true或false总是优先的,对于这个bean定义来说,表达式匹配规则并不生效。

这些技术,对于那些你不希望通过自动装配注入到其他bean的bean十分有用。这并不意味着被排除的bean,本身不能配置自动装配。相反的,而是bean本身不是其他自动装配bean的候选者。

方法注入(Method injection)

大多数应用场景中,容器中大部分bean都是单例的。当一个单例的bean,需要另一个单例bean协作,或者一个非单例bean需要另一个非单例bean协作,你通常通过定义一个bean作为另一个bean的属性来处理这种依赖关系。当bean的生命周期不同时问题就出现了。假设一个单例bean A需要一个非单例的bean B,也许A的每个方法都调用了。容器只创建bean A一次,因此也就只有一次机会来设置A的属性。当需要时,容器不一定能为bean A提供一个新的bean B的实例。

一个解决办法是放弃一些控制反转。你可以通过实现ApplicationContextAware接口,创建一个bean A aware(此单词不翻译,直译为:知道的,了解的),当bean A需要beanB的时候,容器调用getBean("B")方法来获得bean B。下面是相关的例子:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // 创建一个 Command实例
        Command command = createCommand();
        // 将状态设置为命令实例
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // 注意引用的spring依赖
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

上面的这个例子,在实际应用中是不可取的,因为你的业务代码耦合到了Spring代码中!方法注入是Spring IoC容器的一个高级特性,它能够以一种干净的方式来处理这个例子!

基于查找的方法注入(Lookup method injection)

Lookup方法注入,是指容器在其管理的bean上重写方法,将查找结果返回给容器中定义好的bean。查找通常涉及前面所讲的prototype类型的bean。Spring Framework实现这中方法注入主要使用了CGLIB的字节码生成技术去动态生成子类去覆盖方法。

  • 为了成功生成和使用这个动态子类,Spring要去继承的类不能为final,同样的,要被覆盖的方法也不能是final的。
  • 单元测试时,拥有抽象方法的类需要你自己去继承这个类并且实现它的抽象方法。
  • 组件扫描也需要非抽象方法,这需要非抽象类来提取。
  • 进一步的限制就是,方法查找注入不能用于工厂方法,尤其是不能在配置类中使用@Bean注解,因为这个情况下容器不负责创建实例。所以在运行时创建子类。

在前面的CommandManager类中,你可以看见,Spring容器会动态的覆盖createCommand()方法的实现。CommandManager类不会有任何Spring依赖项,我们可以看其重写的例子:

package fiona.apple;
// 没有任何Spring的依赖!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // 获取Command接口的新实例
        Command command = createCommand();
        command.setState(commandState);
        // 为新Command实例设置状态
        return command.execute();
    }
    // ok!但是,这个抽象方法在哪里来实现呢?
    protected abstract Command createCommand();
}

包含被注入方法的类(本例中的CommandManager),被注入的方法需要满足下面的格式:

 [abstract]  theMethodName(no-arguments);

如果方法是抽象的,会用动态生成子类来实现这个方法。动态生成子类会覆盖那个方法。例如:



    



    

不管什么时候,只要commandManager bean调用自己的createCommand()方法它都需要一个myCommand bean的新实例。你必须要注意将myCommand bean定义设置为prototype,如果实际需求是这样。如果他是单例的,则每次myCommand bean都会返回同一个实例。

作为选择,也可以使用基于注解的配置,你可以通过@Lookup注解来声明:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

或者,更通俗的,你可以依赖目标bean的类型,将抽象方法的返回类型修改为目标bean的类型:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

注意,你会声明这样一个注解来查找一个一般方法的实现,以便让它们能够让Spring的组件去扫描。这种方式不适用于显式的注册或者显式导入bean class。

接受不同scope目标bean的另一个方法是ObjectFactory/ Provider 注入点。具体办法在bean scope章节给出。
感兴趣的读者可以去研究一下ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包下面)。

替换任意方法

比方法查找注入应用还少的一种方法注入是,可以在管理的bean中用另一个方法实现来替换任意方法。如果你在实际中没有此需求,那么可以完全跳过本节内容。

基于XML的配置中,你可以使用replaced-method标签去替换已经实现的方法实现。考虑一下下面的类,有一个我们要覆盖的方法:

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }
    // some other methods...
}

一个实现了org.springframework.beans.factory.support.MethodReplacer接口的类,定义了一个新方法:

/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }

}

定义原始类的bean,并且指定覆盖方法:


    
    
        String
    



你可以看到,标签内包含一个或多个标签,来表示被覆盖的方法签名。只有类中方法是重载的,这个参数声明才是必须的。方便起见,字符串类型的参数可以是完全限定的类型名字的一部分,例如,下面的类型都表示的是java.lang.String

java.lang.String
String
Str

因为参数数量,通常足够去彼此区分,这种简写可以节省很多拼写。

bean的作用域

当你定义了一个bean,你就创建了一个bean实例化的规则。这是非常重要的,因为这意味着,使用一个类,你可以从一个bean定义来创建多个实例。

你不光可以控制各种各样的的依赖,将配置的值注入到由bean的配置创建的对象,也可以控制由bean定义创建的对象的范围。这种方法非常强大和有效,因为你通过配置文件创建的对象,是可以选择它的范围的,这样避免了你在Java类级别去设置它的范围。bena可以被部署在以下几个范围:Spring支持6个范围,其中4个是只有在web项目中管用的。

下面的范围都是开箱即用的,你也可以定制自己的范围。

范围 描述
singleton (默认)整个Spring IoC容器中只有一个实例
prototype 每次使用都会创建一个新的实例
request 在一个JTTP请求生命周期内创建一个单实例;就是,每个HTTP请求有它自己的单例的bean,只有在web项目的Spring ApplicationContext的上下文中有效
session 在HTTP session的生命周期内有效。只有在web项目的Spring ApplicationContext的上下文中有效
application 在ServletContext的生命周期内有效。只有在web项目的Spring ApplicationContext的上下文中有效
webSocket 在webSocket的生命周期内有效。 只有在web项目的Spring ApplicationContext的上下文中有效

Spring 3.0中,是有线程范围的,但是默认是不注册的。更多信息,请参考SimpleThreadScope。像获得如何注册它和其他的自定义scope,可以查看使用自定义scope

单例(singleton)作用域

单例的bean,只存在一个被管理的共享实例,对于所有对这个bean实例的请求,例如用id去匹配bean定义,Spring容器都会返回一个特定的bean实例。

换句话说,当你在bean定义中把bean的范围设置成单例的时候,Spring Ioc容器会根据bean的定义只创建一个实例。此单个实例会被存在一个单例bean的缓存中,后面的所有请求和对这个bean的指向,都会返回缓存中的bean实例。

从始至终只创建一个bean

Spring的单例bean概念,不同于Gang of Four (GoF)设计模式书籍中的单例模式。GoF的单例是硬编码对象的范围,对于每个类加载器来说,类的对象有且只有一个实例。Spring的单例范围最好的理解是每一个容器和每一个bean内,有且只有一个实例。这意味着,在一个Spring IoC容器中,你为一个类定义了一个bean定义,Spring容器会为这个bean定义创建一个且只创建一个实例。单例范围是Spring中的默认范围。在xml中创建一个单例的bean,你可以利用下面的方式:





prototype作用域

非单例的,prototype范围的bean,在每次对bean实例的请求都会创建一个新的bean的实例。就是说,bean被注入到另一个bean中或通过getBean()调用bean的实例。一般来说,使用prototype范围的bean来创建有状态的bean,使用singleton(单例)范围来创建无状态的bean。下面的图表阐明了Spring的prototype范围的bean。比较典型的,一个数据访问对象(DAO)并不会被配置成prototype的bean,因为典型的DAO不具有任何会话状态;对作者来说只是简单的重复使用而已,就像上一节图表中展示的那样。

每次创建一个新的bean实例

下面的例子是如何在xml创建prototype的bean:


对比于其他的范围,Spring并不会完整的管理prototype范围bean的生命周期:容器的实例化,配置,其他方式组装对象,和将其交由客户端,整过程并没有对prototype进行进一步记录。因此,尽管初始化生命周期的回调方法不管是什么范围,在所有对象上都会被调用,但在prototype范围情况下,为其配置生命周期的回调并不会被调用。客户端代码必须要清理prototype对象,并释放prototype bean所占用的珍贵的资源。为了让Spring容器释放prototype bean占用的资源,可以使用BeanPostProcessor进行自定义扩展,这里面包含了需要清理的bean的引用。

在某些方面,Spring容器在prototype域中的角色,就相当于Java中new操作。所有生命周期的操作都需要由客户端自己来处理。

单例bean依赖于prototype bean

当你使用单例域的bean依赖于prototype bean的时候,要注意,依赖是在实例化的时候才解析。所以如果你将一个peototype域的bean注入到单例域的bean,一个新的prototype bean实例化并且被注入到单例域的bean中。prototype域的实例是提供给单例域的bean的唯一实例。

然而,假设你想单例域的bean在运行时反复获取一个prototype的bean的新实例。你不能将一个prototype的bean依赖注入到单例域的bean中,因为注入只发生一次,是在Spring容器实例化单例域的bean并解析它的依赖时。如果你需要在运行时获得一次或多次prototype bean的实例时,可以参考前面的方法注入章节。

Request,session,application,和WebSocket作用域

只有你在使用一个web方面的Spring ApplicationContext(例如XmlWebApplicationContext)实现时,Request,session,application,和WebSocket作用域才会起作用。如果你将这些作用域用在一个常规的Spring IoC容器中例如ClassPathXmlApplicationContext,则会抛出一个IllegalStateException异常,告诉你这是一个未知的bean作用域。

初始化web配置

为了支持requestsessionapplicationwebsocket等域,在你进行bean定义之前,需要做一些小的配置(这些小的配置在标准域中不需要配置,singleton和 prototype)。

怎么样完成这些初始化步骤,这取决于你的具体Servlet环境。

如果你使用Spring Web MVC来访问这些域的bean,实际上,就是通过DispatcherServlet来调用, 根本不需要其他的步骤。

如果你使用Servlet 2.5容器时,也就是说不使用DispatcherServlet时(例如使用JSF,Struts等),你需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener。对于Servlet 3.5+,可以实现WebApplicationInitializer接口,或者对于老版本容器可以在web.xml中做如下的配置:


    ...
    
        
            org.springframework.web.context.request.RequestContextListener
        
    
    ...

如果你的listener启动有了问题,可以考虑使用Spring的RequestContextFilter。这个过滤器的配置依赖与web配置文件,可以参考如下配置:


    ...
    
        requestContextFilter
        org.springframework.web.filter.RequestContextFilter
    
    
        requestContextFilter
        /*
    
    ...

其实DispatcherServlet, RequestContextListener, 和 RequestContextFilter,它们都在做一件事,就是将HTTP request对象绑定到处理请求的线程上,这就可以让request或session域加入到请求链中了。

Request scope

请先看一下下面的XML配置:


Spring容器会创建一个beanLoginAction的实例,这样它就可以处理各个HTTP请求。也就是说,LoginAction是作用在了Http请求级别。你可以随便改变这个实例的内部状态,因为LoginAction是多实例的,所以实例之间不会受影响;每个请求都会有它自己的实例。当请求结束的时候,实例也会跟着销毁。

可以使用注解的方式来配置请求域,即@RequestScope,如下所示:

@RequestScope
@Component
public class LoginAction {
    // ...
}
Session作用域

看一下下面xml配置的bean:

对于每个HTTP Session,Spring容器都会创建一个UserPreferences的实例。也就是说UserPreferences是作用范围是Session级别的。和上面讲的request作用域类似,你也可以随便改变实例的内部状态而其他的实例则不受影响。当HTTP Session被销毁的时候,这个实例也就随之销毁。

当基于注解来配置的时候,可以使用@SessionScope来配置:

@SessionScope
@Component
public class UserPreferences {
    // ...
}
Application作用域

先看一下下面的xml配置:


上面配置的含义是,Spring容器会为整个web应用而创建appPreferencesbean实例,并且只创建一次。也就是说,appPreferences的作用级别是ServletContext级别的,是作为ServletContext的属性来保存的。这与Spring 单例bean有些类似,但是有两点不同:它是对于每个ServletContext来说的,不是针对ApplicationContext来说的,还有就是,它是彻底暴露出来的,是作为ServletContext属性来存储和使用的。

基于注解的配置,可以参考以下内容:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
非单例的bean作为被依赖

Spring IoC容器不光实例化你的bean,同时也会实例化你的bean的依赖。如果你想将一个HTTP请求域的bean注入到另一个bean,并且这个bean将会长期存在,那么你可以注入一个aop代理来替代这个bean。也就是说,你需要注入一个代理对象,并且暴露出一样的public接口,也可以从相关域(例如HTTP request域)来检索实际的目标对象,并将该方法委托给实际对象。

使用单例bena的过程里,可以使用标签,然后可以引用一个可以序列话的中间代理,从而在反序列化时候的时候重新获得这个单例的bean。

当对一个有具体域范围的bean(例如session域的bean)使用 标签时,共享代理的的每一次调用都会导致重新创建一次目标实例,然后将该实例转发给调用方。

另外,代理并不是从短生命周期(相对而言)bean获取bean的唯一方式。你也可以简单的声明你的注入点(例如构造/setter方法参数或成员变量)来作为ObjectFactory,同时提供getObject()方法来每次检索按需调用--无需保留实例或单独存储实例。

考虑扩展性,你可以声明一个ObjectProvider,额外提供几个访问方法,例如getIfAvailable 和 getIfUnique。

Provider是JSR-330变体,同时为每次检索和尝试来声明Provider和对应的get()。有关JSR-330的相信信息可以参阅这里

下配置的配置文件很简单,但是足够让你明白“为什么”和“如何”去明白上面讲的是什么意思:




    
    
        
        
    

    
    
        
        
    

要创建这样一个代理,你需要在有作用域的bean定义中插入一个子元素,那么为什么需要在request, session 和 custom-scope级别中定义一个 元素?让门来观察一下下面的单例bean,并与上面的例子做对比:




    

深入bean的本质
生命周期中的回调

如果想深入到容器对bean生命周期的管理, 你可以实现Spring的InitializingBeanDisposableBean两个接口. 容器在初始化时调用afterPropertiesSet()方法, 在销毁时调用destroy()方法.

在spring内部是使用BeanPostProcessor接口去执行它所能发现的所有回调接口或方法. 如果你想定制一些spring没有提供的功能, 那么你可以自己去实现BeanPostProcessor接口.

除了初始化回调, 销毁回调以外, spring所管理的对象也可以去实现生命周期接口, 这样的话, 这些对象可以随着容器的生命周期来初始化和停止了.

这些生命周期的回调接口被在此章节来说明.

初始化的回调
实现org.springframework.beans.factory.InitializingBean接口, 让bean在加载完必要的属性之后, 执行自己所需的初始化工作, 这个接口只指定了一个方法:

void afterPropertiesSet() throws Exception;

其实并不一定推荐你去使用这个接口, 因为这相当于和Spring耦合到了一起. 你也可以使用@PostConstruct注解或指定一个POJO初始化方法. 使用xml配置的时候, 使用init-method属性去指定没有返回值的, 没有参数的方法. 使用Java配置的话, 使用@bean注解的initMethod属性, 如下面的例子:


bean的java类:

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

实现InitializingBean:


java类:

public class AnotherExampleBean implements InitializingBean {
    public void afterPropertiesSet() {
        // 做一些初始化工作
    }
}

销毁的回调
当容器关闭的时候,如果你想让你的bean在这时候执行一些操作,那么可以实现接口org.springframework.beans.factory.DisposableBean,这个接口只有一个方法:

void destroy() throws Exception;

并不建议以上面这种方式去处理,因为这相当于和spring代码相耦合了。你可以使用@PreDestroy注解或指定一个普通方法。基于xml的配置,使用标签下的destroy-method属性。基于Java的配置,可以使用@Bean注解的destroyMethod属性. 如下面得例子所示:


ExampleBean得Java代码:

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

上面的例子和下面的事一样的, 但是没有和Spring解耦:


对应的java代码:

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

标签下的destroy-method属性, 可以指定一个值, 然后通过Spring会去自动发现这个bean上的此方法(一些实现了java.lang.AutoCloseablejava.io.Closeable接口的累也会因此而匹配上). 这个值也可以设置再default-destroy-method中,下一节内容会详解介绍.

默认的初始化和销毁方法
当你编写初始化和销毁回调时, 可以不使用使用Spring指定的InitializingBeanDisposableBean接口. 通常你应该写类似init(), initialize(), dispose()的方法, 此时应该尽可能的让这些生命周期方法保持一致, 以便让项目中所有的开发者一目了然.

可以通过配置Spring容器, 让他可以去每个bean中寻找已经命名好的生命周期方法. 这意味着, 作为一个应用开发者, 你可以在你自己的类中定义叫init()的初始化回调方法, 并且不用在bean的定义中配置init-method="init". Spring IoC容器在bean创建时调用此方法. 这种特性会强制执行初始化或回调方法.

可以参考以下例子:

public class DefaultBlogService implements BlogService {
    private BlogDao blogDao;
    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }
    // 这是初始化方法
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}

xml:


    
        
    

bean的配置中, 顶级标签为beans, 在这个标签中配置default-init-method="init", 就可以让Spring容器创建beans下所有的bean时, 都去执行init方法.

同样的, 在配置销毁回调方法时, 在beans标签中配置default-destroy-method属性.

如果有的bean的命名和beans标签重命名不一致, 可以在bean标签中配置init-methoddestroy-method, 这样就可以覆盖默认值了.

多种方式的结合使用
在Spring2.5 中, 你有三种方式来控制bean的生命周期, 他们分别是: 实现 InitializingBeanDisposableBean 回调接口; 自定义init()destroy()方法; 还有使用@PostConstruct@PreDestroy注解. 对于某一个bean, 你可以结合上面三种方法, 来控制它的生命周期.

如果对一个bean配置了多种生命周期, 并且每种配置的方法名都不一样, 这些配置会按照下面所讲的顺序来执行. 然而, 如果配置了多个一样的方法名的话, 例如初始化回调的init()方法, 如果有像上节所讲的内容一样, 它只会执行一次.

在一个bean上使用多种初始化方式, 调用优先级:

  • 加了@PostConstruct注解的方法
  • 实现了InitializingBean接口的afterPropertiesSet()方法
  • 自己实现的init()方法

对于销毁的回调方法, 他们会同时执行

  • 加了@PreDestroy注解的方法
  • 实现了DisposableBean接口的destroy()方法
  • 自定义的destroy()方法

启动和停止回调
任何有自己生命周期需求的对象都可以实现接口Lifecycle接口(例如开始或结束后台进程).

public interface Lifecycle {
    void start();
    void stop();
    boolean isRunning();
}

任何由Spring管理的对象都可以实现Lifecycle接口. 当ApplicationContext在运行时接收到停止或重启的信号时, 它将这些实现了Lifecycle接口的对象作为一个级联的上下文对象. 它将这些操作委派给了LifecycleProcessor接口:

public interface LifecycleProcessor extends Lifecycle {
    void onRefresh();
    void onClose();
}

需要注意的是, LifecycleProcessorLifecycle的子接口. 它增加了两个方法来支持contenx的刷新和关闭.

请注意, org.springframework.context.Lifecycle接口只是简单的规定了 开始/结束通知, 在context刷新时并不会自动启动. 可以考虑实现org.springframework.context.SmartLifecycle接口以来替代某个bean的自动启动的精确控制. 同样的, 请注意停止信号并不是稳定的: 在正常停止下, 所有的生命周期bean都会第一时间接收到停止信号, 然而, 在热刷新的时候, 只有销毁方法会被回调, start方法是不会被回调的.

启动和停止的调用顺序是很重要的. 如果两个对象之间是有依赖的, 依赖方会在其依赖启动后启动, 在依赖停止前停止. 但是有时候依赖关系是未知的.你可能只是知道某一对象可能在另一个对象启动之前启动. 在这个场景下, SmartLifecycle接口定义了另一种方法叫做getPhase(), 这个方法实际上是在其父接口Phased中定义的.

public interface Phased {
    int getPhase();
}

接口SmartLifecycle:

public interface SmartLifecycle extends Lifecycle, Phased {
    boolean isAutoStartup();
    void stop(Runnable callback);
}

启动的时候, 最底层的对象首先启动, 关闭的时候, 顺序则正好相反. 所以实现了SmartLifecycle接口, 并且在getPhase()方法中返回Integer.MIN_VALUE的话, 它就会最先启动并且最后停止. 没有实现SmartLifecycle接口的话则为"0".

SmartLifecycle的停止方法会接收一个回调. 实现它的对象, 会在其停止前调用调用run(). 所以停止就实现了异步操作.

在非web应用中优雅的关闭Spring容器

此章节仅针对非web应用。基于Spring的web容器的关闭已经说得很详细了。

如果你再非web项目中使用了Spring IoC容器,例如桌面程序中,你在其中注册了一个JVM关闭的hook。这样通过调用单例上的销毁方法来实现优雅的关闭,可以释放掉相关的资源。当然,你必须做正确的配置和实现。

想要注册一个停止钩子,你需要调用ConfigurableApplicationContextregisterShutdownHook()方法:

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("beans.xml");

        // 为上面的 context 增加一个hook
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}
ApplicationContextAware 和 BeanNameAware

ApplicationContext创建了一个实现了org.springframework.context.ApplicationContextAware的对象时,此时对象和容器之间就存在某种关联了。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

这样就可以让bean通过编程的方式来操作ApplicationContext, 也可以使用它的子类, 例如ConfigurableApplicationContext, 它也提供了更多的功能. 可以通过编程的方式来检索其他的bean. 一般这是比较实用的; 但是一般你需要避免实用它, 因为在一般的业务代码中, 这样做会将Spring的代码耦合近业务代码, 这并不符合Spring的控制反转的风格.

ApplicationContext创建了一实现了org.springframework.beans.factory.BeanNameAware接口的类, 这个类就被赋予了定义它的地方的引用.

public interface BeanNameAware {
    void setBeanName(String name) throws BeansException;
}

这个回调会在一般的的bean之后被调用, 但是会在初始化回调之前被调用, 例如InitializingBean或自定义初始化方法.

其他的Aware接口

除了上面提到的ApplicationContextAwareBeanNameAware以外, Spring还提供了一些Aware接口, 也允许bean指向创建它的容器, 但是需要一些依赖. 大部分重要的Aware接口可以总结如下: 一般可以通过名字来看他的依赖类型:

表: Aware相关接口

名称 需要的依赖 解释说明
ApplicationContextAware 声明ApplicationContext 前面章节中
ApplicationEventPublisherAware 围绕ApplicationContext发布时间 Additional capabilities of the ApplicationContext
BeanClassLoaderAware 加载bean的class的类加载器 实例化bean
BeanFactoryAware 声明BeanFactory 前面章节
BeanNameAware 声明bean的命名 前面章节
BootstrapContext BootstrapContext的资源装饰器 JCA CCI
LoadTimeWeaverAware 在加载时定义执行类的操作 Load-time weaving with AspectJ in the Spring Framework
MessageSourceAware 配置解析消息的策略 ApplicationContext
NotificationPublisherAware Spring JMX信号发布 信号
ResourceLoaderAware 配置底层资源的加载器 资源
ServletConfigAware 当前运行中容器的servletConfig,只在ApplicationContext加载的web应用有效 Spring MVC
ServletContextAware 同上 Spring MVC

需要再次注意, 使用上述这些接口同时也意味着不再遵循控制反转的style. 只有再你构建项目中比较基础的部分时, 才推荐使用上面的接口(普通业务代码不推荐).

bean定义的继承

一个bean定义会包含很多配置信息, 包括构造方法参数, 属性值 以及容器指定的信息(初始化方法, 静态工厂名称等等). 子bena定义可以从父bean定义继承配置信息. 子定义可以根据自身所需来覆盖一些值, 也可以增加一些配置. 使用父子bean定义可以让配置更加简洁.

如果你的项目使用的是 ApplicationContext, 子bean定义可以由ChildBeanDefinition定义. 很多用户并不在这个层面上使用它们, 而是使用了像ClassPathXmlApplicationContext的方式. 当你使用基于XML的配置, 你需要使用parent属性来生命当前配置是一个子bean.


    
    


    
    

如果子bean没有指定bean的class, 那么他就会从父bean继承过来, 当然也是可以覆盖父的class的. 稍后的例子中, 子bean必须要兼容父类的class, 也就是说它必须要接受父bena的属性值.

子bean定义会从父bean继承和覆盖scope, 构造方法参数, 属性值等, 也可以根据自己所需添加新值. 所有scope, 初始化方法, 销毁方法, 或静态工厂方法等其他你在子bean指定的, 会覆盖父bean的设置.

除上面提到的设置外, 其他的设置都会在子bean上定义: 依赖检查, 装配模式, 依赖检查, 单例, 延迟初始化等等

上一个例子中, 通过使用abstract属性来显式标记父bean定义是抽象的. 如果父定义没有指定一个类, 那么必须要使用abstract属性来生命这是一个抽象配置, 如下所示:


    
    



    
    

因为父bean是抽象且不完整的, 所以是不可以被实例化的, 当一个bean定义被生命成抽象的, 那么他就是只能是一个模板, 被子bean使用.

ApplicationContext默认是提前实例化所有的单例. 所以, 如果你只是想让一个bean定义当成模板来使用, 那么就必须要在bean定义上设置抽象属性为True. 否则容器就会去提前实例化(尝试)这个抽象bean.

你可能感兴趣的:(Spring Framework 官方文档中文版—Core_part_2)