Spring Framework 中文文档 5.0.7.RELEASE - Core Technologies

1. The IoC container

1.1. Introduction to the Spring IoC container and beans

Inversion of control (IoC): 控制反转.
Dependence injection (DI): 依赖注入, IoC 的同义词.
org.springframework.beans 和 org.springframework.context 是 Spring IoC 容器的基础包.
IoC 容器核心接口: BeanFactory, ApplicationContext. 后者继承自前者且是前者的扩展.
Beans: Spring 概念, 指组成你的应用程序的普通对象, 它们由 IoC 容器初始化, 组装, 并管理.
Configuration metadata: Spring 概念, 指用来创建 beans 的配置信息, 它也包含 beans 之间的依赖关系.

1.2. Container overview

ApplicationContext 代表了 Spring 的 IoC 容器, 它通过读取 configuration metadata 的方式来初始化, 配置, 并组装 beans. Configuration metadata 主要有 3 种形式: XML, Java 注解, Java 代码.
Spring 自带了一些开箱即用的 ApplicationContext 实现. 例如想要在 standalone application 中初始化 IoC 容器, 通常情况下你得 new 一个 ClassPathXmlApplicationContext 或者 FileSystemApplicationContext 实例.
在大多数场景下, 都不需要你显示地去初始化 IoC 容器. 例如在 web 应用程序中, web.xml 中的几行模板配置就能搞定这一切.

1.2.1. Configuration metadata

你的应用程序由很多 beans 组成, 而 configuration metadata 就是这些 beans 的配置信息. IoC 容器通过读取这些配置信息来初始化, 配置, 并组装这些 beans.
XML 格式的 configuration metadata 是最传统的配置格式. 而 IoC 容器本身是和具体的配置格式解耦的, 你可以自由选择 configuration metadata 的格式.
Configuration metadata 包含若干的 bean definition 信息. 这些 bean definition 在 XML 配置中就是根元素 <beans/> 中的一个 <bean/> 元素, 在 Java 代码配置中, 就是 @Configuration 类中的一个 @Bean 方法.
一个 bean definition 就是一个对象的配置信息, 这些对象通常是你应用程序中的基础组件, 比如说各种 service, dao 之类的. 通常你不会配置领域类 (domain objects) 的 bean definition, 因为这些类通常是由 dao 或者业务代码创建的.
Configuration metadata 的 XML 配置示例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

String 类型的 id 属性唯一标识一个 bean definition, class 属性标志一个 bean 的类型, 使用 Java 类的全限定名.

1.2.2. Instantiating a container

直接明了:

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

Composing XML-based configuration metadata
你的应用程序中可能有很多个 bean definition 配置文件, 比如 service 模块一个, dao 模块一个. 这种情况下你可以像上面的示例一样, 把每个配置文件的资源路径作为参数传递给 ApplicationContext 的构造器, 来初始化一个 IoC 容器.
另一种更简明的方法是在一个根配置文件里使用 <import/> 标签:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

然后把这个根配置文件的资源路径传给 ApplicationContext 的构造器即可. 注意, 这里 <import/> 标签引入的配置文件的资源路径, 是根配置文件的相对路径, 所以这里的资源路径不建议以 "/" 开头 (虽然加了也没影响).

不建议用 "../" 引用根配置文件的上层目录中的文件.
<import/> 标签中可以使用绝对路径, 如 "file:C:/config/services.xml" 或者 "classpath:/config/services.xml". 但是不建议这么做. 如果确实需要, 你可以使用占位符 "${...}" 的形式.

1.2.3. Using the container

你可以像这样来使用 ApplicationContext:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

对于 Groovy 配置文件:

ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");

最灵活的方式是 GenericApplicationContext 加 reader delegates:

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

或者:

GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();

如果你愿意, 你可以在同一个 ApplicationContext 中混合使用各种 reader delegates, 来同时加载不同的配置文件.
然后你可以使用 getBean 方法来获取 beans 的实例. 但是不建议你这么做, 因为这样就把你的业务代码和 Spring 框架的 API 耦合起来了. 你应该使用依赖注入的方式来获取需要的实例.

1.3. Bean overview

一个Spring IoC 容器管理着若干个 beans. 这些 beans 的配置信息是由你提供的, 例如以 xml 配置文件的形式.
在容器内部, 这些 bean 的定义信息由 BeanDefinition 对象来表示, 它包含以下元数据:

  • 类的全限定名, 通常是这个 bean 的具体实现类.
  • Bean 的行为配置元素, 它们定义一个 bean 在容器中的行为 (scope, lifecycle callbacks 等等).
  • 对于其他 bean 的引用信息, 这些引用通常被称为依赖.
  • 其它的一些配置信息, 例如一个管理连接池的 bean, 它的连接数, 连接池大小等等.

这些元数据对应于配置中的属性如下表所示:

Property Explained in...
class Instantiating beans
name Naming beans
scope Bean scopes
constructor arguments Dependency Injection
properties Dependency Injection
autowiring mode Autowiring collaborators
lazy-initialization mode Lazy-initialized beans
initialization method Initialization callbacks
destruction method Destruction callbacks

除了通过 bean 定义信息来创建 bean 对象, ApplicationContext 也允许注册业已存在的对象 (在 IoC 容器以外创建的对象). 你可以通过调用 ApplicationContextgetBeanFactory() 方法来获得 BeanFactory 的实现 DefaultListableBeanFactory 对象. 它的 registerSingleton(..)registerBeanDefinition(..) 方法允许这种注册操作. 然而这种用法并不常见.

Bean 定义信息的元数据和手工注册的单例对象应该尽早地提供给 IoC 容器, 以便它在组装 bean 或者其它的一些内省步骤中可以正确地响应对于这些 bean 的请求. 然而对于已存元数据或者单例对象的覆写操作, IoC 容器在某种程度上是支持的. 但是这种支持不是官方的, 所以在运行时去注册新的 beans 有可能引起 concurrent access exception 异常, 或者导致 IoC 容器的状态错乱.

1.3.1. Naming beans

每个 bean 都有一个或多个标识信息, 这些标识信息在其宿主容器中必须唯一. 通常一个 bean 只有一个标识信息 (id), 但若需要更多的标识信息, 可以取别名 (aliases).
在 xml 配置文件中, 你可以用 id/name 属性来指定一个 bean 的标识信息. Id 属性只允许指定一个标识信息, 通常由字母数字组成 (虽然你可以使用一些特殊的字符). Name 属性允许你指定若干个标识信息, 标识信息之间用 ,, ; 或者空格来分隔.
当然你也可以不提供任何的 id 或 name, 这时容器会为这个 bean 分配一个唯一的标识信息. 但是, 如果你想从其他地方引用这个 bean, 你就必须显式地提供一个 id 或 name.

在开启 Spring 的路径扫描功能时, Spring 会自动为扫描到的未命名 component 分配 name 属性. 其规则为保持类名不变, 将首字母小写. 但若类名以两个或以上连续的大写字母开始, 则首字母保持不变.

Aliasing a bean outside the bean definition
在一个 bean 的定义内部, 你当然可以通过 name 属性为这个 bean 提供若干个别名.
但在某些场景下, 这并不能满足需求. 例如在一个大系统中, 配置文件往往依据子系统被分割成好几份. 此时如何在另外一个地方为已定义的 bean 取别名呢?

<alias name="fromName" alias="toName"/>

如果你使用 Java 格式的配置文件, @Bean 注解可以用来提供别名信息.

1.3.2. Instantiating beans

若你使用 xml 配置文件, 你通过 <bean/> 元素的 class 属性来指定待创建对象的类型信息. 这个 class 属性, 对应于 BeanDefinition 实例中的一个 Class 属性, 通常是必不可少的 (例外情况请参见: Instantiation using an instance factory methodBean definition inheritance). 对于 Class 属性, 你有两种使用方式:

  • 指定待创建对象的类名, 然后容器通过反射调用其构造函数来创建对象.
  • 指定一个含有静态工厂方法的类, 然后容器通过调用其静态工厂方法来创建对象. 这个静态工厂方法返回的对象实例的类, 可以和工厂类一致, 也可以毫无关系.

如果你想为一个静态内部类创建 bean 定义, 其 class 属性可以定义为 com.example.Foo$Bar. 注意 $ 字符的运用.

Instantiation with a constructor
如果你想通过构造器来创建 bean, 只需提供一个 class 属性即可:

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

对于如何提供构造器参数, 如何在一个对象构造完毕后, 去设置它的依赖属性, 请参阅 Injecting Dependencies.

Instantiation with a static factory method
如果你想通过静态工厂方法来创建 bean, 你需要指定 class 属性为包含该静态工厂方法的类, factory-method 属性为该静态工厂方法的方法名. 同时该定义中并不包含待创建 bean 的类型 (class) 信息.

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

对于如何提供静态工厂方法的方法参数, 如果在一个对象被静态工厂方法返回后, 去设置它的依赖属性, 请参阅 Dependencies and configuration in detail.

Instantiation using an instance factory method
如果你需要通过一个实例工厂方法来创建一个 bean, 那么首先你的容器 (或者父/祖先容器) 里得有这个工厂实例. 然后在 bean 的定义中, class 属性留空, 指定 factory-bean 属性为该工厂实例的 name, factory-method 为实例工厂方法的方法名.

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

一个工厂类可以同时拥有多个工厂方法.

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

这个例子同时也表明 factory bean 自身也可以作为一个普通的 bean 而被 IoC 容器管理, 通过 DI 来注入所需依赖.

注意 factory bean 和 FactoryBean 的区别. 前者是被 IoC 容器管理的一种 bean, 只是它具有工厂方法而已. 后者是 Spring 框架的一个组件类 FacroryBean.

1.4. Dependencies

1.4.1. Dependency Injection

依赖注入 (DI) 指的是对象以构造参数, 工厂方法参数或者 setter 方法的形式来定义自己的依赖, 然后容器在创建出这些对象后, 再注入这些对象自己定义的依赖. 由于依赖的注入不是由对象自己来控制的, 所以这一过程又称为控制反转 (IoC).
DI 使得你的代码简洁且低耦合. 如果对象中的依赖都是基于接口或者抽象基类, 那么单元测试也会变得非常容易.
DI 主要有两种形式: 基于构造器, 基于 setter 方法.

Constructor-based dependency injection
基于构造器的 DI 实现方式: IoC 容器调用对象的构造函数, 并提供构造函数所需的参数. 基于静态工厂方法的 DI 与此类似, 不再赘述. 下面是一个基于构造器 DI 的例子, 注意这个类只是一个普通的 POJO:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

Constructor argument resolution
构造器参数的匹配是基于类型的, 如果没有潜在的歧义, 那么 IoC 容器会按照参数的定义次序依序提供所需的构造参数. 请考虑以下实例:

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }
}

不存在歧义 (假设 Bar 和 Baz 没有继承关系), 因此下面的配置文件能够正常工作, 你不需要显式地提供构造参数的 index 或者类型信息.

<beans>
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>
</beans>

在上面的例子中, 因为一旦引用一个 bean, 那么其类型信息就知道了, 所以 IoC 容器可以根据参数的类型信息来进行匹配. 但是对于简单值, 例如 <value>true</value>, Spring 不能推断其类型信息, 因此你需要提供一些额外的信息来帮助匹配:

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

Constructor argument type matching
针对于以上场景, 你可以通过 type 属性来显示提供类型信息:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

Constructor argument index
你也可以通过 index 属性来显示提供参数顺序信息:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

index 属性除了能够解决简单值的问题, 同样也能解决多个参数是同一个类型的问题. 注意 index 是从 0 开始的.

Constructor argument name
你同样可以提供参数名称:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

请记住要想这种模式正常工作, 你的代码在编译时必须打开 debug flag. 如果不能或者不想这么做, 你可以通过 JDK 注解 @ConstructorProperties 来显式地定名参数名称:

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

Setter-based dependency injection
基于 setter 方法的 DI 实现方式: IoC 容器调用对象的无参构造函数 (或无参静态工程方法) 来创建对象, 然后通过 setter 方法注入对象的依赖.

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

当然, 基于构造器的 DI 和基于 setter 的 DI 可以混合使用.
在 Spring 框架中, 这些依赖是以 BeanDefinition 的形式来定义的, 配合 PropertyEditor 实例, 更可以将依赖属性从一种类型转换为另一种类型. 然而, 大部分使用者是不会直接使用这些类的. 你们只需要定义 xml 文件中的 <bean/> 元素, 定义注解类 (被 @Component 注解的类), 或者在 @Configuration 类中定义 @Bean 方法, 然后这些元数据会自动被转换成 BeanDefinition 实例.

怎样选择基于构造器的 DI 还是基于 setter 的 DI?
一个经验法则是用基于构造器的 DI 来注入必须的依赖, 用基于 setter 的 DI 来注入可选的依赖. 当然, 如果你在 setter 方法上使用 @Required 注解, 那么这个依赖就会变成必须的.
Spring 团队整体上是推荐基于构造器的 DI 的, 因为这种形式的依赖注入更容易让你构建一个不可变 (immutable) 对象, 并且保证所需的依赖都不空 (null). 而且当客户端代码请求一个对象时, 这种方式总是能够返回一个已经完全初始化的对象. 基于构造器的 DI 的一个副作用就是, 它容易产生构造参数过多的坏味道. 而一旦出现这种坏味道, 它可能意味着你的类做了太多的事情, 你可能需要重构来使其更符合单一职责原则.
基于 setter 的方式应该主要被用于可选依赖的注入上. 这些可选依赖应该有一个合理的默认值, 不然在你的代码中就得反复地做非空校验. 这种方式的好处是, 它可以使对象在某一时刻被重新设置, JMX MBeans 远程管理就是一个很好的应用场景.

Dependency resolution process
Spring 容器按照如下过程解决依赖问题:

  • 首先 Spring 用 beans 的配置元数据来创建 ApplicationContext. 你可以以 xml 文件, Java 代码或注解的形式来提供这些配置信息.
  • 对于每个 bean, 它的依赖是以 JavaBean 属性, 或者构造函数参数 (或者静态工厂方法参数) 的形式来表达的. 当这些 bean 被真正创建的时候, 容器会提供这些依赖给它.
  • 每个属性或者构造参数都是一个定义好的值, 或者是指向容器内另一个 bean 的引用.
  • 如果属性或者构造参数是一个定义好的值, 那么 Spring 会将其转换成所需要的类型. 默认情况下, Spring 能够将字符串形式的值转换成 Java 基本类型, 例如 int, long, String, boolean 等等.

Spring 在初始化 IoC 容器的时候会检查 beans 的配置信息是否合法. 但是只有在一个 bean 被实际创建的时候, 它的依赖才会被注入. 一个 singleton-scoped, pre-instantiated 的 bean 会在容器初始化的时候被创建, 而其他种类的 bean 只会在被请求的时候被创建. 创建一个 bean 可能引起一堆的 bean 被创建, 因为这个 bean 依赖的 bean, 以及它的依赖的依赖, 等等, 需要被事先创建. 注意, 如果 bean 的依赖配置有问题, 这个问题只会在 bean 被实际创建的时候才会暴露.

循环依赖
如果你的代码主要使用构造器注入, 那么你有可能碰到循环依赖的问题.
例如 class A 的构造器需要一个 class B 的实例, 而 class B 的构造器需要一个 class A 的实例. 如果你在 Spring 中把这两个类配置成相互通过构造器依赖, 那么 Spring 会在运行时侦测到这种循环依赖, 并抛出 BeanCurrentlyInCreationException 异常.
尽管不推荐, 这种循环依赖可以通过 setter 注入来解决. 不同于典型 (无循环依赖) 的情况, 循环依赖的场景下会强制用一个未被完全初始化的 bean 注入进另一个 bean 中 (鸡生蛋, 蛋生鸡问题).

IoC 容器在初始化的时候会检测一些问题. 例如依赖于一个不存在的 bean, 循环依赖等. 但是容器只会在一个 bean 被创建的时候才去设置它的属性, 注入它的依赖, 所以说当一个 IoC 容器被正确地初始化后, 它也可能在运行时挂掉, 比如说你在运行时请求了一个 bean, 而这个 bean 在创建的时候抛了个异常. 为了应对这种潜在问题延迟出现的场景, Spring 的默认配置是 pre-instantiate 单例的 bean, 这样虽然花费了一些启动时间和内存, 但是可以使潜在问题尽早暴露. 当然, 你可以改变这种设置, 使 IoC 容器懒加载单例 bean.
在没有循环依赖的场景下, 当若干个被依赖的 bean 被注入进一个依赖于它们的 bean 中时, 这些被依赖的 bean 已经事先被完全配置好了. 换句话说, IoC 容器创建一个 bean 的过程是: 初始化, 注入依赖, 调用生命周期方法 (Configured init method, InitializingBean callback method 等).

Examples of dependency injection
Setter 注入的例子:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

构造器注入的例子:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}

静态工厂方法的例子:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}

静态工厂方法的参数是通过 <constructor-arg/> 元素来定义的, 就像构造器参数. 静态工厂方法返回的 bean 的类型不必和工厂类保持一致 (尽管此例是一致的). 实例工厂方法的配置和静态工厂方法基本一致 (除了使用 factory-bean 属性而不是 class 属性), 这里不在赘述.

1.4.2. Dependencies and configuration in detail

Straight values (primitives, Strings, and so on)
你可以通过 value 属性来提供值, Spring 使用 conversion service 来将这些字符串形式的值转换成实际所需的类型.

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="masterkaoli"/>
</bean>

下例使用 p-namespace 来简化配置:

<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.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="masterkaoli"/>

</beans>

以上配置文件虽然简洁, 但是拼写错误会延迟到运行时才被发现. 建议你使用具有自动提示功能的 IDE, 比如 IntelliJ IDEA.
你可以像这样配置 java.util.Properties 实例:

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

Spring 容器使用 PropertyEditor 机制将 <value/> 元素内的文本转换成 java.util.Properties 实例中的元素. 这种写法很方便, 也是为数不多的 Spring 团队推荐使用 <value/> 元素而不是 value 属性的场景之一.

The idref element
<idref/> 元素的作用是保证将容器内另一个 bean 的 id 以字符串值的形式 (注意, 不是引用) 正确地传递给 <contructor-arg/> 元素或 <property/> 元素.

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

上面地写法与下面地写法完全等同:

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>

然第一种写法更严谨一些, 因为 <idref/> 元素允许容器在程序部署的时候就可以检查其 id 指向的 bean 是否真实存在. 而第二种写法不支持这种检查, 拼写错误只有在 bean 被实际创建后才会被发现. 如果 bean 地类型是 prototype, 那么这种错误可能在程序部署完很久以后才会触发异常.
<idref/> 的一种使用场景是, 在 ProxyFactoryBean 的定义中配置 AOP interceptors, 它可以防止你将某一个 interceptor 的 id 拼错.

References to other beans (collaborators)
最常用的方式是通过 <ref/> 元素的 bean 属性来指定依赖的 bean. 它可以指向当前容器或其父容器中的任何一个 bean, 不论其配置信息在不在同一个 xml 文件中. bean 属性的值可以是要依赖的 bean 的 id, 或其 name 中的任何一个别名.

<ref bean="someBean"/>

通过 <ref/> 中的 parent 属性可以引用一个父容器中的 bean, 目标 bean 必须是在当前容器的父容器中. 这种方式的主要使用场景为, 你想用代理去包装一个父容器中的 bean, 且这个代理和父容器中的 bean 名称保持一致.

<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>

<ref/> 元素的 local 属性已不再支持, 由 bean 属性替代

Inner beans
<property/><constructor-arg/> 内部定义的 <bean/>, 称之为内部 bean.

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

内部 bean 不需要 id 或 name 属性, 即使你提供了, IoC 容器也不会使用. IoC 容器也会忽略 scope 标识, 内部 bean 永远只在其宿主 bean 被创建的时候而被创建, 并且是匿名的. 除了宿主 bean, 你不可以将一个内部 bean 注入进容器内另外一个 bean 中, 换句话说, 你无法直接引用一个内部 bean.
作为一种特例, 如果宿主 bean 是单例, 而内部 bean 是自定义的 scope (例如 request-scope), 此时内部 bean 可以收到自定义 scope 的 destruction callbacks: 当内部 bean 被创建后会被绑定到其宿主 bean 上, 但是 destruction callbacks 允许它参与到 request scope 的生命周期中去. 当然, 这只是一种特例, 大部分情况下, 内部 bean 共享其宿主 bean 的 scope.

Collections
对于集合类型, 你可以通过 <list/>, <set/>, <map/><props/> 来设值.

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

map 的 key 或 value, 或 set 的 value, 可以用以下任一元素来设置:

bean | ref | idref | list | set | map | props | value | null

Collection merging
Spring 支持集的合并操作. 你可以在父 bean 中定义一个集合, 在子 bean 中定义一个集合, 然后子 bean 中的集合会继承并覆盖父 bean 中集合的元素. 不熟悉 bean 继承机制的读者, 可以先行参阅相关章节.

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

注意 merge="true" 属性的运用. 当名为 child 的 bean 最终被创建后, 它的 adminEmails 属性将包含以下值:

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

对于 <list/>, <map/>, <set/> 来说, 这种合并的行为类似. 基于 List 的语义, <list/> 的合并操作有点不一样: 定义中的元素顺序会被保留, 父 bean list 中的元素全部排在子 bean list 元素之前, 所以也不存在元素覆盖问题.

Limitations of collection merging
你不可以合并不同类型的集合 (例如 Map 和 List), 如果你这么做了, Spring 会抛出一个相应的异常. merge 属性必须在子 bean 的集合元素中指明, 定义在父 bean 集合元素中的 merge 不会生效.

Strongly-typed collection
从 Java 5 开始, Java 加入了对泛型的支持. 也就是说, 现在你可以声明一个集合类, 指定它只能包含特定类型的元素 (例如 String). 如果你让 Spring 来注入一个泛型集合, Spring 的 type-conversion 功能会先将元素转换成所需的类型, 然后再注入进泛型集合.

public class Foo {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="foo" class="x.y.Foo">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

上例中, Spring 通过反射能够拿到 Map<String, Float> 的值为 Float 类型, 然后会使用 type conversion 功能将 "9.99", "2.75", "3.99" 这些字符串转换成 Float 类型.

Null and empty string values
空字符串可以这么设置:

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

上面的配置与以下代码等效:

exampleBean.setEmail("");

null 可以这么设置:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上面的配置与下面的代码等效:

exampleBean.setEmail(null);

XML shortcut with the p-namespace
p-namespace 可以用属性来代替元素来简化你的配置.
你可以借助不同的命名空间来扩展 Spring 配置文件的格式, 这些命名空间是基于 XML Schema 定义的. 本章讨论的 beans 配置格式, 就是定义于一个 XML Schema 文档中的. 然而, p-namespace 却没有 XSD 文件.

<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.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="foo@bar.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="foo@bar.com"/>
</beans>

这个例子显示了 p-namespace 中的一个属性 email, 由于 p-namespace 没有 Schema 文件, 所以你可以设置任意你需要的属性 (例如 email).
以下的例子演示了如何引用另一个 bean:

<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.xsd">

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

注意 p:spouse-ref="jane" 的运用: spouse 表示依赖属性的名字, -ref 表示这不是一个普通字符串, 而是一个引用.

p-namespace 有时候并不如标准配置格式来的灵活, 例如有一个依赖属性的名字以 Ref 结尾, 此时就会与 p-namespace 的语法冲突, 而标准配置格式就不会有这个问题. 我们建议你在选择配置格式的时候要慎重, 并且经过了与小组成员的充分沟通, 尽量避免在同一份配置文件中同时使用各种配置格式的情况.

XML shortcut with the c-namespace
与 p-namespace 类似, Spring 3.1 引入的 c-namespace 可以简化构造参数的配置.

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

    <bean id="bar" class="x.y.Bar"/>
    <bean id="baz" class="x.y.Baz"/>

    <!-- traditional declaration -->
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
        <constructor-arg value="foo@bar.com"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>

</beans>

其用法与 p-namespace 类似, 不在赘述.
在极端情况下, 你得不到构造参数的 name (通常是因为你在编译的时候把 debug flag 关掉了), 此时你仍可以通过参数的 index 来配置:

<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

由于 xml 的语法不允许属性名以数字开头, 所以这里的 index 都以 _ 作为前缀.

除非你碰到了这种极端情况, 一般我们不建议这种 index 形式的配置.

Compound property names
Spring 支持复合属性的注入, 只要这个属性链中除了最末位待注入的属性外, 所有的属性都不为 null 即可.

<bean id="foo" class="foo.Bar">
    <property name="fred.bob.sammy" value="123" />
</bean>

上例中, 在这个 bean 被创建后, 其 fred 属性, fred 的 bob 属性都不能为 null, 否则容器会抛出 NPE 异常.

1.4.3. Using depends-on

如果一个 bean 是另外一个 bean 的依赖, 通常意味着这个 bean 会作为一个属性被注入进另外一个 bean 中. 一般来说, 在 xml 配置文件中, 你可以通过 <ref/> 元素来达到这个目的. 然而, 有些时候 bean 之间的依赖并不是这样直接的, 例如某些 bean 在创建之前可能需要另外一些 bean 的静态初始化被触发 (像数据库驱动类的注册过程). depends-on 属性可以显式地要求在这个 bean 被初始化以前, 另外的一些 bean 必须被先初始化完毕.

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

对于多个 bean 的依赖, 可以将这些 bean 的名称用逗号, 空格或分号隔开:

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

如果 bean 的 scope 是单例, depends-on 不仅控制了初始化顺序, 还控制了销毁顺序. 依赖 bean 总是后初始化, 先销毁; 被依赖 bean 总是先初始化, 后销毁.

1.4.4. Lazy-initialized beans

默认情况下, ApplicationContext 总是在容器初始化的时候, 就会将所有的单例 bean 都初始化了. 这种行为是合理的, 因为它可以将配置中隐藏的错误尽早地暴露出来. 如果你不想要这种行为, 你可以将单例 bean 设置为懒加载模式. 懒加载模式的单例 bean 只会在它被第一次请求时被创建, 而不是随着 IoC 容器的启动而被立即初始化.
在 xml 配置文件中, 你可以设置 lazy-init 属性:

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

注意如果一个懒加载模式的单例 bean 被另一个普通的单例 bean 依赖了, 那么这个懒加载的单例 bean 仍然会在 IoC 容器启动的时候被创建, 因为被依赖的 bean 要在依赖 bean 之前被初始化完毕.
你可以通过 default-lazy-init 来配置容器级别的默认行为:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

1.4.5. Autowiring collaborators

IoC 容器支持自动注入功能. 自动注入功能具有以下优点:

  • 自动注入能够有效地减少依赖属性或构造参数地配置工作量 (其他的机制像模板配置也能达到这个目的).
  • 在开发过程中, 自动配置功能尤其有效. 例如当你的某个 bean 在演进的过程中又增加了一个依赖, 此时你不需要更新配置文件, 自动注入功能会自动注入这个新的依赖. 而当你的代码趋于稳定后, 你还可以选择将自动注入功能关闭, 切换回显式地配置模式.

在 xml 配置文件中, 你可以通过 autowire 属性来设置自动注入模式. 自动注入有以下四种模式:

Mode Explanation
no 默认模式, 不进行自动注入. 对其它 bean 地依赖必须显式地通过 ref 元素来表达. 在大型应用中不建议更改这一默认行为, 因为显式的依赖配置能够使应用更好地被控制, 结构更加地清晰. 某种程度上, 清晰的配置文件可以充当应用结构的文档来使用.
byName 依据属性的名称来自动注入. 例如一个 bean 有一个 master 属性 (具有 setMaster(..) 方法), Spring 会在容器中寻找名称为 master 的 bean, 然后将寻找到的 bean 注入到依赖 bean 中.
byType 依据类型来自动注入. 例如一个 bean 依赖某个属性, 且在容器中有且仅有一个 bean 的类型同这个属性一样, 那么这个 bean 会被注入进依赖 bean 中. 如果发现了多个 bean 的类型与这个属性一致, Spring 会抛出一个异常. 如果找不到该类型的 bean, 什么事也不会发生, 依赖 bean 的这个属性不会被设值.
constructor 同 byType 一样, 只不过用构造器注入的方式. 同样的, 如果某个参数的类型在容器中发现了好几个 bean, Spring 会抛出异常.

用 byType 或 constructor 的方式, 你可以自动注入数组类型或泛型集合类型的属性. 在这种情况下, 容器中所有类型一致的 bean 都会被注入进该属性中. 如果 Map 的 key 是 String 类型的, 你也可以注入泛型 Map 属性. 容器中所有类型一致的 bean 都会被当作 value 注入进这个 Map 中, 它们的名称将作为 key.
你在使用自动注入功能时, 可以配合使用依赖检查功能, 它在自动注入结束后执行.

Limitations and disadvantages of autowiring
自动注入功能最好是在应用中全局使用. 否则仅仅在几个 bean 上使用自动注入功能, 会给开发人员造成困扰.
请考虑以下这些限制与缺点:

  • 显式地依赖配置总是会覆盖自动注入功能. 并且你不能使用自动注入功能来注入简单属性 (像 Java 基本类型, Strings, Classes 以及这些类型地数组). 这些限制是特意这么设计的.
  • 自动注入功能没有显式配置来的精确. 尽管如上表所述, Spring 在有歧义的情况下不会主动猜测结果, 但是 IoC 容器中 beans 的依赖关系却缺少一份明确的注释.
  • 对于那些借助于 Spring 容器信息的文档生成工具, 自动注入信息是获取不到的.
  • 如果同一种 Java 类型在容器中有好几个 bean 定义, 此时对于 byType 自动注入来说, 注入数组, 集合或 Map 都是没问题的, 但是如果注入单个值的话, 由于没法判断那一个 bean 是所需的, Spring 会抛出一个异常.

对于后一种情况, 你有以下选择:

  • 弃用自动注入功能, 转而使用显式的e配置.
  • 设置 beanautowire-candidate 属性为 false, 来明确地指定该 bean 不作为自动注入功能的候选 bean.
  • 设置 beanprimary 属性为 true, 来明确地指定该 bean 在自动注入场景下作为首选 bean.
  • 在基于注解地配置中, 对自动注入功能实现更为精细地控制, 参见 Annotation-based container configuration.

Excluding a bean from autowiring
在 xml 文件中, 你可以将 beanautowire-candidate 属性设置为 false, 来明确地指定一个 bean 在自动注入场景下不作为候选 bean, 从而使其它的 bean 没法儿自动注入该 bean. 自动注入场景同时包括基于注解地配置, 例如 @Autowired.

注意, autowire-candidate 属性仅仅影响 byType 类型的自动注入功能. 它不影响通过名称的引用, 因此, byName 的自动注入不受 autowired-candidate 属性影响.

你可以使用基于名称的模式匹配功能来限制作为自动注入场景下候选 bean 的范围. 具体的模式可以通过顶层元素 <beans/>default-autowire-candidates 属性来设置. 例如你想限制候选 bean 的名称必须以 Repository 结尾, 你可以将模式值设置为 *Repository. 多个模式值可以用逗号隔开. 如果单个 <bean/>autowire-candidate 属性被明确地设置为 true 或 false 的话, 此时这个配置的优先级高于全局的 default-autowire-candidates 配置.
这个设置只是让一个 bean 不被其它的 bean 自动注入, 并不代表这个 bean 本身不能使用自动注入功能. 实际上, 这个 bean 可以自由地使用自动注入功能来注入它自己的依赖属性.

1.4.6. Method injection

在大多数应用场景中, IoC 容器中的大多数 bean 都是单例的. 当一个单例的 bean 依赖于另一个单例的 bean, 或者当一个非单例的 bean 依赖于另一个非单例的 bean 时, 通常你会以将一个 bean 定义为另一个 bean 的属性的形式来满足这种依赖. 但是当不同生命周期的 bean 相互依赖时, 就会出现问题. 例如一个 singleton 的 bean A 依赖于一个 prototype 的 bean B, 可能对于 A 中方法的每次调用, 都需要一个全新的 B 实例. 而容器只会创建一次 bean A, 因此也只有一次机会去设置 bean A 的属性, 容器并不能在 bean A 每次需要 bean B 的时候提供一个全新的 B 实例.
一种解决方式是让 A 去实现 ApplicationContextAware 接口, 进而持有 ApplicationContext 实例. 这样在每次需要 (通常是一个全新的) B 实例的时候, 都可以通过 getBean(..) 方法获得. 只是这种方式有点违背 IoC 原则.

// 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) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

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

以上的这种方式是不被推荐的, 因为这使得你的业务代码与 Spring API 产生了耦合. Spring IoC 容器提供了一种高级功能: Method Injection, 来满足这种使用场景.

Lookup method injection
Lookup method injection 使得容器有能力去覆写一个 bean 的方法, 使这个方法去容器中寻找并返回一个需要的 bean. Spring 框架使用 CGLIB 的动态代理来实现的这个功能.

由于 CGLIB 动态代理是通过继承来实现的, 所以待继承的类不能是 final 的, 待覆写的方法同样不能是 final 的.
如果是抽象类, 单元测试的时候, 你需要自己去继承待测试的抽象类并针对抽象方法给出实现.
如果你使用了 Spring 的路径扫描功能, 此时的类必须是具体类, 而不能是抽象类.
另一个更关键的限制是, 工厂方法和 @Bean 方法产生的 bean 不能使用 lookup method 功能, 因为在这两种情况下, Spring 不负责 bean 实例的创建工作, 也就因此无法在运行时动态地去继承并创建其子类.

上例中的 CommandManager 类可以重构为一下形式:

package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

待注入的方法需要满足以下形式:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果这个方法是 abstract 的, 那么动态生成的代理类会去实现这个方法; 否则, 代理类会覆盖这个方法.

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

注意你需要将 myCommand 定义成 prototype, 如果定义成 singleton, 那么 lookup method 每次返回的还是同一个实例.
当然, 你也可以用 @Lookup 注解来定义 lookup method.

public abstract class CommandManager {

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

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

或者更一般地, 你可以依赖 lookup method 返回值的类型信息:

public abstract class CommandManager {

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

    @Lookup
    protected abstract MyCommand createCommand();
}

注意, 根据 Spring 的组件扫描规则, 抽象类会被忽略. 所以针对上面的抽象类, 如果你使用 Spring 的组件扫描功能, 你还得给出一个具体的实现类.

Arbitrary method replacement
相比于 lookup method 形式的方法注入, 另一种形式的方法注入则可以让你用任意的实现去替换掉一个 bean 的任意方法.
在 xml 配置文件中, 你可以使用 replace-method 元素去替换一个方法实现. 请考虑一下类中的 computeValue 方法, 我们想覆盖这个方法:

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 id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

你可以使用若干个 arg-type 元素来确定一个待覆写方法的签名. 如果一个方法被重载了多次, 那么方法签名的配置是必须的.

1.5. Bean scopes

Spring 原生支持 6 种 scope, 其中 4 种只有在 web 环境中可用. 除此之外, 你也可以创建自定义 scope.

Scope Description
singleton 在一个 IoC 容器中, 一个 bean definition 对应一个 object 实例.
prototype 一个 bean definition 可以创建任意多个 object 实例.
request 在一个 HTTP request 生命周期内, 一个 bean definition 只会创建一个实例.
session 在一个 HTTP session 生命周期内, 一个 bean definition 只会创建一个实例.
application 在一个 ServletContext 生命周期内, 一个 bean definition 只会创建一个实例.
websocket 在一个 WebSocket 生命周期内, 一个 bean definition 只会创建一个实例.

从 Spring 3.0 开始提供了一个 thread scope, 但是它默认未注册. 详细信息请参阅 SimpleThreadScope 的文档. 关于如何注册自定义 scope, 请参阅 Using a custom scope 章节.

1.5.1. The singleton scope

对于 singleton scope 的 bean, Spring IoC 容器仅仅只创建一个实例并将其缓存起来. 程序中任何地方对于这个 bean 的请求, IoC 容器只会返回这个缓存起来的实例.

<bean id="accountService" class="com.foo.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

1.5.2. The prototype scope

对于 prototype 类型的 bean, 每次对它的请求都会导致 IoC 容器创建一个全新的实例.
这里的最佳实践是, 对于有状态的 bean, 设置成 prototype, 对于无状态的 bean, 设置成singleton.

<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>

注意, 对于 prototype scope 的 bean, Spring 不负责全部的生命周期事件: 只有初始化相关的回调函数会被调用, 所有销毁相关的回调函数都不会被调用. 客户端使用完毕后, 得手工清理并释放资源. 如果想让 IoC 容器去释放被 prototype-scoped beans 持有的资源, 请考虑实现一个自定义的 bean post-processor, 用它来持有这些 bean 的引用.

1.5.3. Singleton beans with prototype-bean dependencies

如果一个 singleton-scoped bean 依赖于一个 prototype-scoped bean, 要注意依赖注入只会在 singleton-scoped bean 初始化的时候发生一次. 就是说, 在 singleton bean 的整个生命周期内, 它持有的 prototype bean 的实例永远是刚开始被注入的那个. 如果想在运行时不断地去获取 prototype bean 的新实例, 依赖注入的方式是不奏效的, 此时请参见 Method injection 章节.

1.5.4. Request, session, application, and WebSocket scopes

这些 scope 只在 web 环境下生效, 例如 XmlWebApplicationContext.

Initial web configuration
为了支持这些 web-scoped beans, 你需要设置一些初始化过程.
如果你使用 Spring Web MVC, DispatcherServlet 会去处理 request, 你不需要任何初始化设置.
如果你使用其他框架 (例如 JSF 或 Struts), 此时 request 不由 DispatcherServlet 来处理, 你需要注册一个 ServletRequestListener: org.springframework.web.context.request.RequestContextListener. 对于 Servlet 3.0+, 这可以通过 WebApplicationInitializer 接口以编程方式来实现. 对于老版本的 Servlet, 在 web.xml 文件中增加以下配置:

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>

如果这个 listener 启动时有异常, 请考虑使用 Spring 的 RequestContextFilter.

<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

DispatcherServlet, RequestContextListenerRequestContextFilter 其实都做了同一件事: 将 HTTP 请求对象绑定到这个请求的服务线程上, 这使得接下来的 request 和 session-scoped beans 得以实现.

Request scope
请考虑以下配置:

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>

对于每一个 HTTP 请求, IoC 容器都会创建一个新的 loginAction 实例. 你可以随便更改这个实例的状态, 因为其它的 HTTP 请求是不会看到这个实例状态的. 它们只能访问各自的 loginAction 实例. 当一个请求处理完毕后, 它所对应的 loginAction 实例会被丢弃.
以下是注解配置形式:

@RequestScope
@Component
public class LoginAction {
    // ...
}

Session scope
请考虑以下的配置:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

对于每一个 HTTP session, IoC 容器都会创建一个新的 userPreferences 实例, 你可以随便更改这个实例的状态, 因为其它的 HTTP session 是不会看到这个实例状态的. 它们只能访问各自的 userPreferences 实例. 当一个 HTTP session 最终被丢弃的时候, 它所对应的 userPreferences 实例会被丢弃.
以下是注解配置形式:

@SessionScope
@Component
public class UserPreferences {
    // ...
}

Application scope
请考虑以下配置:

<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>

对于整个 web application, IoC 容器只会创建一个 appPreferences 实例, 并作为一个 ServletContext 的属性来存储. 某种程度上来说, 它和 singleton bean 有点像, 但是要注意, session-scoped bean 在整个 ServletContext 中是都是唯一的, 而 singleton-scoped bean 是在 Spring 的 ApplicationContext 中唯一. 一个 web application 可以包含多个 Spring 的 ApplicationContext 容器.
以下是注解配置形式:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}

Scoped beans as dependencies

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 我对你的喜欢 三行写不完 最起码得四行
    麻天喜阅读 227评论 0 2
  • (1) “简梦,我鼻子上的水泡很丑么?”薛之寒拿着镜子看来看去,还是不安心。 简梦放下手中的笔,郑重其事的戴上眼镜...
    幸运孩子阅读 446评论 0 1
  • 今天阅读《刻意练习》的第6章:在生活中运用刻意练习原则。在这个章节作者告诉我们要想在某个领域取得进步或者...
    4b40c2d9081e阅读 274评论 0 0
  • 我实在是坚持不住了 虽窗外嘈杂着 每一个汉字弹奏着一个音符的催眠曲 令人着迷,神往 站在前面的那个女人 毫不留情地...
    刀小差阅读 267评论 0 0
  • 前几天,在一本杂志上看到有一种鱼,本来是它们生存水域最弱的,为了更好的生存,它们苦练速游,游速大增,但还时常受侵害...
    燊儿阅读 793评论 0 0