3.11. 基于注解(Annotation-based)的配置

Spring Framework

3.11. 基于注解(Annotation-based)的配置

第 3.7.1.2 节 “RequiredAnnotationBeanPostProcessor示例”一节中我们提到了基于注解的配置方式,使用BeanPostProcessor与注解是 Spring IoC 容器的一个普通扩展方法。例如,Spring 2.0 对必须的属性引入了@Required注解。在 Spring 2.5中已经可以用注解的方式去驱动 Spring 的依赖注射了。更重要的是,@Autowired注解提供了与第 3.3.5 节 “自动装配(autowire)协作者”一节中描述的同样功能,并且提供了更细致的控制与更好的适应性。Spring 2.5 也支持 JSR-250 中的一些注解,例如@Resource@PostConstruct,以及@PreDestroy。当然,要使注解可用,您必须使用 Java 5 (Tiger)或更新的版本,以使得可以访问源代码层次的注解。这些注解可以被注册为独立 bean 的定义,但它们也可以被隐式地注册,通过基于 XML 的配置方式,如下例(请注意包含 '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/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd">
               
     <context:annotation-config/>
     
</beans>

(隐式注册 post-processors 包括了 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessorPersistenceAnnotationBeanPostProcessor,也包括了前面提到的 RequiredAnnotationBeanPostProcessor。)

3.11.1. @Autowired

@Autowired 注解可以用于“传统的”setter 方法,如下例:

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;
    }

    // ...
}

@Autowired注解甚至可以用于构造器与字段:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;
    
    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

也可以一种提供来自ApplicationContext的特殊类型的所有 beans,注解字段或者方法,例如:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

这同样适用于集合类型:

public class MovieRecommender {

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

    // ...
}

甚至是 Maps 也可以这样注解,只要这个 Map 的 key 类型为 String。这个 Map 的 values 应该是已知的类型,并且 keys 应该包含符合 bean 的命名:

public class MovieRecommender {

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

    // ...
}

在缺省情况下,当出现0个候选的 beans时自动连接将失败;缺省行为把连接方法,构造器,字段假设为 required 的依赖。这样的行为如下所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

注意

虽然当 一个类只有一个连接构造器时它将被标记为 required, 但是还是可以标记多个构造器的。在这种情况下,每一个构造器都有可能被认为是连接构造器, Spring 将会把依赖关系能够满足的构造器认为是greediest 的构造器。

@Autowired也能用于总所周知的“可解决的依赖”:BeanFactory接口,ApplicationContext接口,ResourceLoader接口,ApplicationEventPublisher接口,还有MessageSource接口。这些接口(还有它们的扩展,例如ConfigurableApplicationContext或者ResourcePatternResolver)将可以自动解决依赖,没有任何特殊必须的其它步骤需要。

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

3.11.2. 基于注解的自动连接微调

因为通过类型的自动连接可能会有多个候选,因此经常需要在选择过程中加以控制。一种方法去完成这个控制就是使用@Qualifier注解。在最简单的情况下,您能够通过命名方式去实现这个自动连接:

public class MovieRecommender {

    @Autowired
    @Qualifier("mainCatalog")
    private MovieCatalog movieCatalog;

    // ...
}

@Qualifier注解也能够被指定为构造器的参数或者方法的参数:

public class MovieRecommender {

    private MovieCatalog movieCatalog;
    
    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("mainCatalog") MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

您也可以创建您自定义的限定器注解。您只要在定义一个注解时提供@Qualifier注解就可以了:

@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-2.5.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>
    
    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

在下一节,题目是第 3.12 节 “对受管组件的Classpath扫描”,您将看到使用XML提供给限定器元数据且基于注解的可选解决方案。特别地,请参看:第 3.12.6 节 “用注解提供限定符元数据”

在某些情况下,有足够充分的理由去使用不带值的注解。这使得注解可以提供更多解决不同类型依赖的能力。例如,在 Internet 连接不可用时,您可以提供一个离线的搜索目录。首先就要定义一个简单的注解:

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

}

然后添加这个注解给字段作为自动连接:

public class MovieRecommender {

    @Autowired
    @Offline
    private MovieCatalog offlineCatalog;

    // ...
}

现在,这个 bean 的定影只组要一个限定器了:

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/>
    <!-- inject any dependencies required by this 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/>以及它的属性将优先考虑,但是如果没有限定器的话(参看如下定义的后两个 bean ),自动连接机制将取消以<meta/>标签标记的值。

<?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-2.5.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute name="format" value="VHS"/>
            <attribute name="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute name="format" value="VHS"/>
            <attribute name="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>
    
    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>

3.11.3. CustomAutowireConfigurer

CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它可以使得在自动连接过程中做更多的自定义选择。特殊地,它允许您注册您自己的自定义限定器注解类型,甚至是它们没有使用 Spring 的@Qualifier注解标注它们自己。

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

请注意,AutowireCandidateResolver的实现将依赖于 Java 版本。如果在 Java 5 以下,限定器注解是不被支持的,因此自动连接后选将被'autowire-candidate'的值或者在<beans/>中元素'default-autowire-candidates'可用的模式所决定。如果运行在 Java 5 或者更新的版本上,@Qualifier注解或者任何自定义并在CustomAutowireConfigurer上注册过的注解都将正常工作。

忽略 Java 版本,决定“主要”的后选(当多个 beans 都配置为自动连接后选时)都是一样的:在这些后选中只要一个 bean 的'primary'属性定义为'true'即可。

3.11.4. @Resource

Spring 也提供了使用 JSR-250 bean 属性支持的注射方式。这是一种在 Java EE 5 与 Java 6 中普遍使用的方式(例如,在 JSF 1.2 中映射 beans 或者 JAX-WS 2.0 端点),对于Spring 托管的对象 Spring 可以以这种方式支持映射。

@Resource有一个‘name’属性,缺省时,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" 注射到它的 setter 方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

注意

注解提供的名字将被BeanFactory解析为 bean 名。请注意,这些名字也可能通过 JNDI 被解析(需要配置 Spring 的SimpleJndiBeanFactory)。不过,建议您依靠缺省行为与 Spring 的 JNDI 查找功能。

@Autowired类似,@Resource可以回退为与标准 bean 类型匹配(例如,使用原始类型匹配取代特殊命名 bean)来解决著名的"resolvable dependencies":BeanFactory 接口,ApplicationContext 接口,ResourceLoader 接口,ApplicationEventPublisher 接口以及 MessageSource 接口。请注意:这只有适用于未指定命名的@Resource

下面的例子有一个customerPreferenceDao字段,首先要查找一个名叫 “customerPreferenceDao” 的 bean,然后回退为一个原始类型以匹配类型CustomerPreferenceDao。"context" 字段将基于已知解决的依赖类型ApplicationContext而被注入。

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

3.11.5. @PostConstruct@PreDestroy

CommonAnnotationBeanPostProcessor 不只是能识别@Resource注解,而且也能识别 JSR-250 lifecycle注解。在 Spring 2.5 中,这些注解的描述请参看initialization callbacksdestruction callbacks节。CommonAnnotationBeanPostProcessor已经在 Spring 的ApplicationContext中注册,当一个方法带有这些注解之一时,将被在其生命周期与 Spring 生命周期接口的方法或者显式声明回调方法同一刻上调用。下面的例子里,缓存将预置于初始化与销毁阶段。

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }
    
    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

注意

关于组合不同的生命周期机制,请查看第 3.5.1.4 节 “组合生命周期机制”