4.9 基于注解的容器配置

配置Spring时,注解要比XML更好吗? 基于注解配置的介绍就提出了这样的问题,这种方法要比XML‘更好’吗?简短的回答就是具体问题具体分析。完整的答案就是每种方法都有它的利与弊,通常是让开发 人员来决定使用哪种策略更适合使用。由于定义它们的方式,注解在它们的声明中提供了大量的上下文,使得配置更加简短和简洁。然而,XML更擅长装配组件,而不需要触碰它们源代码或重新编译。一些开发人员更喜欢装配源码而其他人认为被注解的类不再 是POJO了,此外,配置变得分散并且难以控制。

无论怎么去线则,Spring都可以容纳两种方式,甚至是它们的混合体。最值得指出 的是通过JavaConfig(4.12节)选择,Spring允许以非侵入式的方式来使用注解,而不需 要触碰目标组件的源代码和工具,所有的配置方式都是SpringSource Tool Suite所支持的。

作为 XML 配置的另外一种选择,依靠字节码元数据的基于注解的配置来装配组件代替 了尖括号式的声明。作为使用 XML 来表述 bean 装配的替换,开发人员可以将配置信息移入 到组件类本身中,在相关的类,方法或字段声明上使用注解。正如在 4.8.1.2 节,“示例: RequiredAnnotationBeanPostProcessor”所提到的,使用 BeanPostProcessor 来连接注解是扩展 Spring IoC 容器的一种常用方式。比如,Spring 2.0 引入的使用@Required(4.9.1 节)注解来强制所需属性的可能性。在 Spring 2.5 中,可以使用相同的处理方法来驱 动 Spring 的依赖注入。从本质上来说,@Autowired 注解提供了在 4.4.5 节,“自动装配协 作者”中描述的相同能力,但却有更细粒度的控制和更广泛的适用性。Spring 2.5 也添加了 对 JSR-250 注解的支持,比如@Resource,@PostConstruct 和@PreDestroy。Spring 3.0 添加了对 JSR-330(对 Java 的依赖注入)注解的支持,包含在 javax.inject 包下,比如@Inject,@Qualifier,@Named 和@Provider,当 JSR330 的 jar 包在类路径下时就可以使用。使 用这些注解也需要在 Spring 容器中注册特定的 BeanPostProcessor。

注意

注解注入会在 XML 注入之前执行,因此通过两种方式,那么后面的配置会覆盖前面装 配的属性。

一如往常,你可以注册它们作为独立的 bean,但是它们也可以通过包含下面的基于 XML的 Spring 配置代码片段被隐式地注册(注意要包含 context 命名空间):

<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/bean[s http://www.springframework.org/schema/beans/spring -beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:annotation-config/>
</beans>

( 隐 式 注 册 的 后 处 理 器 包 含 AutowiredAnnotationBeanPostProcessor , CommonAnnotationBeanPostProcessor , PersistenceAnnotationBeanPostProcessor , 以 及 上 述 的 RequiredAnnotationBeanPostProcessor)

注意

<context:annotation-config/>仅仅查找定义在同一上下文中的 bean 的注解。 这就意味着,如果你为 DispatcherServlet 将<context:annotation-config/>放 置在 WebApplicationContext 中,那么它仅仅检查控制器中的@Autowired bean,而 不是你的服务层 bean,可以参看 16.2 节,“DispatcherServlet”来查看更多信息。

4.9.1 @Required

@Required 注解应用于 bean 属性的 setter 方法,就向下面这个示例:

public class SimpleMovieLister {
    private MovieFinder movieFinder;
    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    // ...
}

这个注解只是表明受影响的 bean 的属性必须在 bean 的定义中或者是自动装配中通过明确的属性值在配置时来填充。如果受影响的 bean 属性没有被填充,那么容器就会抛出异 常;这就允许了急切而且明确的失败,要避免 NullPointerException。我们推荐你放 置断言到 bean 的类中,比如,放置到初始化方法中。当你在容器外部使用类时,这么来做 是强制那些所需的引用和值。

4.9.2 @Autowired 和@Inject

正如预期的那样,你可以使用@Autowired 注解到“传统的”setter 方法中: 注意在下面的示例中,JSR330 的@Inject 注解可以用于代替 Spring 的@Autowired。没有必须的属性,不像 Spring 的@Autowired 注解那样,如果要注入的值是可选的话,要有一个 required 属性来表示。如果你将 JSR330 的 JAR 包放置到类路径下的话,这种行为就会自 动开启。

public class SimpleMovieLister {
    private MovieFinder movieFinder;
    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    // ...
}

你也可以将注解应用于任意名称和/或多个参数的方法:

public class MovieRecommender {
    private MovieCatalog movieCatalog;
    private CustomerPreferenceDao customerPreferenceDao;
    @Autowired
    public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog; 
        this.customerPreferenceDao = customerPreferenceDao;
    }
    // ...
}

你也可以将用于构造方法和字段:

public class MovieRecommender {
    @Autowired
    private MovieCatalog movieCatalog;
    private CustomerPreferenceDao customerPreferenceDao;
    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }
    // ...
}

也可以提供 ApplicationContext 中特定类型的所有 bean,通过添加注解到期望哪 种类型的数组的字段或者方法上:

public class MovieRecommender {
    @Autowired
    private MovieCatalog[] movieCatalogs;
    // ...
}

相同地,也可以用于特定类型的集合:

public class MovieRecommender {
    private Set<MovieCatalog> movieCatalogs;
    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }
    // ...
}

甚至特定类型的 Map 也可以自动装配,但是期望的键的类型是 String 的。Map 值会 包含所有期望类型的 bean,而键会包含对应 bean 的名字:

public class MovieRecommender {
    private Map<String, MovieCatalog> movieCatalogs;
    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }
    // ...
    }

默认情况下,当出现零个候选 bean 的时候,自动装配就会失败;默认的行为是将被注解的方法,构造方法和字段作为需要的依赖关系。这种行为也可以通过下面这样的做法来改变。

public class SimpleMovieLister {
    private MovieFinder movieFinder;
    @Autowired(required=false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    // ...
}

注意

每一个类中仅有一个被注解方法可以标记为必须的,但是多个非必须的构造方法可 以被注解。在那种情况下,每个构造方法都要考虑到而且 Spring 使用依赖可以被满足 的那个构造方法,那就是参数最多的那个构造方法。

@Autowired 需要的属性推荐使用@Required 注解。所需的表示了属性对于自动 装配目的不是必须的,如果它不能被自动装配,那么属性就会忽略了。另一方面,

@Required 更健壮一些,它强制了由容器支持的各种方式的属性设置。如果没有注入任何值,就会抛出对应的异常。 你也可以针对我们熟知的解决依赖关系的接口来使用@Autowired:BeanFactory,ApplicationContext , Environment , ResourceLoader ,ApplicationEventPublisher 和 MessageSource。这些接口和它们的扩展接口,比 如 ConfigurableApplicationContext 或 ResourcePatternResolver 也会被自动解析,而不需要特殊设置的必要。

public class MovieRecommender {
    @Autowired
    private ApplicationContext context;
    public MovieRecommender() {

    }
    // ...
}

注意

@Autowired , @Inject , @Resource 和 @Value 注 解 是 由 Spring 的 BeanPostProcessor 实现类来控制的,反过来告诉你你不能在 BeanPostProcessor 或 BeanFactoryPostProcessor 类型(任意之一)应用这些注解。这些类型必须明确地 通过 XML 或使用 Spring 的@Bean 方法来‘装配’。

4.9.3 使用限定符来微调基于注解的自动装配

因为通过类型的自动装配可能导致多个候选者,那么在选择过程中通常是需要更多的控 制的。达成这个目的的一种做法就是 Spring 的@Qualifier 注解。你可以用特定的参数来 关联限定符的值,缩小类型的集合匹配,那么特定的 bean 就为每一个参数来选择。最简单 的情形,这可以是普通描述性的值:

注意

JSR 330 的@Qualifier 注解仅仅能作为元注解来用,而不像 Spring 的@Qualifier 可以 使用字符串属性来在多个注入的候选者之间区别,并可以放在注解,类型,字段,方法,构 造方法和参数中。

public class MovieRecommender {
    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;
    // ...
}

@Qualifier 注解也可以在独立构造方法参数或方法参数中来指定:

public class MovieRecommender {
    private MovieCatalog movieCatalog;
    private CustomerPreferenceDao customerPreferenceDao;
    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }
    // ...
}

对应的 bean 的定义可以按如下所示。限定符值是“main”的 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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring -beans-3.0.xsd http://www.springframework.org/schema/contex)t http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:annotation-config/>
    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/>
        <!-- 注入这个bean需要的任何依赖 -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/>
        <!-- 注入这个bean需要的任何依赖 -->
    </bean>
    <bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>

对于后备匹配,bean 名称被认为是默认的限定符值。因此你可以使用 id 为“main”来 定义 bean,来替代嵌套的限定符元素,这也会达到相同的匹配结果。然而,尽管你使用这 种规约来通过名称参照特定的 bean,@Autowired 从根本上来说就是关于类型驱动注入和可 选语义限定符的。这就是说限定符的值,即便有 bean 的名称作为后备,通常在类型匹配时 也会缩小语义;它们不会在语义上表达对唯一 bean 的 id 的引用。好的限定符的值是“main” 或“EMEA”或“persistent”,特定组件的表达特性是和 bean 的 id 独立的,在比如前面示例 之一的匿名 bean 的情况下它是可能自动被创建的。

限定符也可以用于类型集合,正如上面讨论过的,比如 Set<MovieCatalog>。在这 种情况下,根据声明的限定符,所有匹配的 bean 都会被注入到集合中。这就说明了限定符 不必是唯一的;它们只是构成了筛选条件。比如,你可以使用相同的限定符“action”来定 义多个 MovieCatalog bean;它们全部都会通过@Qualifier("action")注解注入到 Set<MovieCatalog>中。

提示

如果你想通过名称来表达注解驱动注入,主要是不使用@Autowired,即便在技术上来说能够通过@Qualifier 值指向一个 bean 的名称。相反 ,使用 JSR-250 的 @Resource 注解,在语义上定义了通过它的唯一名称去确定一个具体的目标组件,声 明的类型和匹配过程无关。

由于这种语义区别的特定后果,bean 本身被定义为集合或 map 类型,是不能通过 @Autowired 来进行注入的,因为类型匹配用于它们不太合适。对于这样的 bean 使用 @Resource,通过唯一的名称指向特定的集合或 map 类型的 bean。

@Autowired 可以用于字段,构造方法和多参数方法,在参数级允许通过限定符 注解缩小。相比之下,@Resource 仅支持字段和仅有一个参数的 bea n 属性的 setter 方法。因此,如果你的注入目标是构造方法或多参数的方法,那么就坚持使用限定符。 你可以创建你自定义的限定符注解。只需定义一个注解并在你的定义中提供 @Qualifier 注解即可。 注意你可以以下面描述的方式来使用 JSR 330 的@Qualifier 注解,用于替换 Spring 的 @Qualifier 注解。如果在类路径中有 JSR 330 的 jar 包,那么这种行为是自动开启的。

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre { String value(); }

之后你可以在自动装配的字段和参数上提供自定义的限定符:

public class MovieRecommender {
    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;
    private MovieCatalog comedyCatalog;
    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }
    // ...
}

之后,为候选 bean 提供信息,你可以添加<qualifier/>标签来作为<bean/>标签的 子元素,然后指定 type 和 value 值来匹配你自定义的限定符注解。这种类型匹配是基于 注解类的完全限定名。否则,作为一种简便的形式,如果没有名称冲突存在的风险,你可以 使用短类名。这两种方法都会在下面的示例中来展示。

<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring -beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:annotation-config/>
    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!— 注入这个bean所需要的任何依赖 -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/>
        <!— 注入这个bean所需要的任何依赖 -->
    </bean>
    <bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>

在 4.10 节,“类路径扫描和管理的组件”中,你会看到基于注解的替代,在 XML 中来 提供限定符元数据。特别是在 4.10.7 节,“使用注解提供限定符元数据”中。

在一些示例中,使用无值的注解就足够了。这当注解服务于多个通用目的时是很有用的,

而且也可以用于集中不同类型的依赖关系。比如,当没有因特网连接时,你可以提供脱机目 录来由于搜索。首先定义简单的注解:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}

之后将注解添加到要被自动装配的字段或属性上:

public class MovieRecommender {
    @Autowired
    @Offline
    private MovieCatalog offlineCatalog;
    // ...
}

现在来定义 bean 的限定符的 type:

<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!— 注入这个bean所需要的任何依赖 -->
</bean>

你也可以定义自定义的限定符注解来接受命名属性,除了或替代简单的 value 属性。 如果多个属性值之后在要不自动装配的字段或参数上来指定,那么要考虑 bean 的定义必须 匹配自动装备候选者的所有属性值。示例,考虑下面的注解定义:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier { String genre();
Format format();
}

在本例中,Format 是枚举类型:

public enum Format { VHS, DVD, BLURAY }

要自动装配的字段使用自定义的限定符和包含 genre 和 format 两个属性的值来注解。

public class MovieRecommender {
    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;
    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;
    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;
    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;
    // ...
}

最后,bean 的定义应该包含匹配的限定符值。这个示例也展示了 bean 的元属性可能用 于替代<qualifier/>子元素。如果可用,<qualifier/>和它的属性优先,但是如果目 前没有限定符,自动装配机制就会在<meta/>标签提供的值上失效,就像下面这个示例中的 最后两个 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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring -beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:annotation-config/>
    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!— 注入这个bean所需要的任何依赖 -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!— 注入这个bean所需要的任何依赖 -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!— 注入这个bean所需要的任何依赖 -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!— 注入这个bean所需要的任何依赖 -->
    </bean>
</beans>

4.9.4 CustomAutowireConfigurer

CustomAutowireConfigurer 是 BeanFactoryPostProcessor 的一种,它使得 你可以注册你自己定义的限定符注解类型,即便它们没有使用 Spring 的@Qualifier 注解 也是可以的。

<bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAu
    towireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

AutowireCandidateResolver 的特别实现类对基于 Java 版本的应用程序上下文激 活。在 Java 5 之前的版本中,限定符注解是不支持的,因此自动装配候选者仅由每个 bean 定义中的 autowire-candidate 值来决 定,还有 在<beans/>元 素中的任 何可用的 default-autowire-candidates 模 式 也 是 可 以 的 。 在 Java 5 之 后 的 版 本 中 , @Qualifier 注解的存在还有任意使用 CustomAutowireConfigurer 注册的自定义注解也将发挥作用。

不管 Java 的版本,当多个 bean 作为自动装配的候选者,决定“主要”候选者的方式也 是相同的:如果候选者中一个 bean 的定义有 primary 属性精确地设置为 true,那么它就会被选择。

4.9.5 @Resource

Spring 也支持使用 JSR 250 的@Resource 注解在字段或 bean 属性的 setter 方法上的注 入。这在 Java EE 5 和 6 中是一个通用的模式,比如在 JSF 1.2 中管理的 bean 或 JAX-WS 2.0 端点。Spring 也为其所管理的对象支持这种模式。

@Resource 使用名称属性,默认情况下 Spring 解释这个值作为要注入的 bean 的名称。

换句话说,如果遵循 by-name 语义,正如在这个示例所展示的:

public class SimpleMovieLister {
    private MovieFinder movieFinder;
    @Resource(name="myMovieFinder")
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果没有明确地指定名称,那么默认的名称就从字段名称或 setter 方法中派生出来。以字段为例,它会选用字段名称;以 setter 方法为例,它会选用 bean 的属性名称。所以下面 的示例中有名为“movieFinder”的 bean 通过 setter 方法来注入:

public class SimpleMovieLister {
    private MovieFinder movieFinder;
    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

注意

使用注解提供的名称被感知 CommonAnnotationBeanPostProcessor 的 ApplicationContext 解析成 bean 的名称。名称可以通过 JNDI 方式来解析,只要你明确 地配置了 Spring 的 SimpleJndiBeanFactory。然而,还是推荐你基于默认行为并仅使用 Spring 的 JNDI 查找能力来保存间接的水平。

在使用@Resource 并没有 明确指 定名称的 独占情况 下,和 @Autowired 相似, @Resource 发现主要类型匹配,而不是特定名称 bean 并解析了熟知的可解析的依赖关系: BeanFactory , ApplicationContext , ResourceLoader , ApplicationEventPublisher,MessageSource 和接口。

因 此 , 在 下 面 的 示 例 中 , customerPreferenceDao 字 段 首 先 寻 找 名 为 customerPreferenceDao 的 bean,之后回到匹配 CustomerPreferenceDao 的主类型。 “context”字段基于已知的可解析的依赖类型 ApplicationContext 注入。

public class MovieRecommender {
    @Resource
    private CustomerPreferenceDao customerPreferenceDao;
    @Resource
    private ApplicationContext context;
    public MovieRecommender() {

    }
    // ...
}

4.9.6 @PostConstruct 和@PreDestroy

CommonAnnotationBeanPostProcessor 不但能识别@Resource 注解,而且还能 识别 JSR-250 生命周期注解。在 Spring 2.5 中引入,对这些注解的支持提供了在初始化回调( 4.6.1.1 节 ) 和 销 毁 回 调 ( 4.6.1.2 节 ) 中 的 另 一 种 选 择 。 只 要 CommonAnnotationBeanPostProcessor 在 Spring 的 ApplicationContext 中注册, 一个携带这些注解之一的方法就同时被调用了,和 Spring 生命周期接口方法或明确地声明回调方法相对应。在下面的示例中,在初始化后缓存会预先填充,在销毁后会清理。

public class CachingMovieLister {
    @PostConstruct
    public void populateMovieCache() {
        // 在初始化时填充movie cache...
    }
    @PreDestroy
    public void clearMovieCache() {
        // 在销毁后清理movie cache...
    }
}

注意

关于组合多个生命周期机制影响的细节内容,请参考 4.6.1.4 节,“组合生命周期机制”。