Spring核心技术(八)——Spring自动装载的注解

本文针对自动装载的一些注解进行描述。

基于注解的容器配置

@Required注解

@Required注解需要应用到Bean的属性的setter方法上面,如下面的例子:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...

}

当Bean的属性配置了这个注解时,该Bean的属性必须在配置阶段就拥有明确的值,通过精确地Bean定义,或者通过自动装载。如果Bean的属性没有配置,容器会抛出异常。这一机制可以避免之后出现的空指针异常问题。当然,仍然推荐在Bean的类中使用断言,比如,在初始化方法中。这么做可以确保请求的引用或者值在容器外使用时是正确的。

@Autowired

JSR 330 的 @Inject注解在下面的例子中可以用来取代Spring的@Autowired注解。

开发者可以在构造函数上面使用@Autowired注解:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...

}

在Spring 4.3的版本中,@Autowired在构造函数上面的注解就已经不需要了,如果Bean定义了构造函数,会自动扫描直接DI的。如果配置了多个构造函数,则需要配置@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 {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

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

    // ...

}

当然,也可以根据类型,将所有的ApplicationContext中的类型的Bean进行依赖注入,可以通过注解将多个Bean注入到Array类型中。

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...

}

也可以注入到集合类型之中:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

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

    // ...

}

开发者所定义的Bean可以通过实现org.springframework.core.Ordered接口或者通过使用@Order或者标准的@Priority注解来确定注入到array或者list中的顺序。

Map类型也可以注入进去,只要key的类型为String就可以注入进去。Map的值可以是任何一种容器中的Bean的类型,key当然就是对应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;
    }

    // ...

}

每个类仅有一个注解构造函数可以被标记为必须的,但是非必须的够早函数可以注解多个。在存在多个非必须注解(required=false)的情况下,Spring会选择一个最贪婪的构造函数(满足最多的依赖的)。
@Autowired注解要优于@Required注解。required属性来表明其属性是否为必须的,而当依赖不能被注入的话,就会被忽略掉。而@Required朱姐,是比@Autowired要强的,它会要求依赖必须注入。如果没有对应的依赖进行注入,就会抛出异常。

开发者可以使用@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方法来定义。

通过@Primary来自动装载

由于通过类型来装载可能导致多个候选者,通常很有必要来控制选择依赖的过程,一种方式就是通过Spring的@Primary注解。@Primary注解表明当一个Bean需要进行依赖注入的时候,如果有多个候选者可能注入到单值的依赖之中,那么该Bean拥有优先权。如果只有一个primary的Bean的话,那么这个Bean将成为自动装载的依赖。

参考如下代码:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...

}

上面的代码中,firstMovieCatalog作为主要的MovieCatalog
当使用了上面的配置后,下面的Bean在自动装载的时候,会装载firstMovieCatalog

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...

}

如果使用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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

通过限定符的自动装载

@Primary在相同类型的几个实例之间选择选择依赖的时候,是一种很高效的方式。但是如果想要更多的控制自动装载的过程,@Qualifier注解就更为有用了。开发者可以通过将趣分期的值和参数来关联起来,以缩小类型匹配的范围,这样就能够自动装配到特值得Bean了。代码如下:

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的在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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/>

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/>

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

在匹配中,Bean的名字就相当于默认的限定符的值。这样,开发者可以定义一个Bean的id为main而不使用限定符元素,也会有一样的匹配结果的。然而,尽管开发者可以通过这个便利来通过名字引用指定的Bean,@Autowired在根本上仍然是通过类型来进行依赖注入的,只不过有着可选的限定符。这也意味着限定符的值,甚至Bean的名字,总是能缩小值匹配的集合,但是并不能明确的如Bean的id一样指向一个唯一的Bean。好的限定符的值比如main或者EMEA或者persistent,所表示的是特定的组件,跟Bean的id是相互独立的。Bean的id,在前面匿名Bean中是会自动生成的。

限定符也能够应用到集合类型的,如前面的例子中,Set<MovieCatalog>。在这种情况下,所有根据声明的趣分期的匹配的Bean都会被注入到集合之中。这也说明趣分期并不是唯一的,限定符更类似一种过滤标准。比如:开发者可以定义多个MovieCatalogBean而且配置其相同的限定符的值,比如说action,那么所有含有@Qualifier("action")的Bean都会注入到Set<MovieCatalog>之中。

如果开发者希望通过名字来进行依赖注入,甚至仅仅使用@Qualifier的值进行注入,那么可以不用@Autowired而是使用JSR-250@Resource注解,这个注解是通过名字来进行依赖注入的,而与类型无关。
对于本身定义为集合类型或者数组类型的Bean来说,@Resource是个很好的解决方案,可以通过名字来引用特定的集合或者数组元素。当然,在Spring4.3的版本中,集合类型,map以及数组类型也能通过Spring的@Autowired的类型匹配算法匹配到了。
@Autowired可以用在实例变量,构造函数,或者多参数的方法上,允许通过限定符注解来缩小匹配范围。相比之下,@Resource仅仅在实例变量和Setter方法的情况下支持依赖注入。因此,如果需要在多参数的方法或者构造函数上约束注入的依赖,需要使用限定符而不要用@Resource

开发者也可以定义自己的限定符注解,代码如下:

@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提供限定符信息。开发者可以通过在<bean/>的子标签<qualifier/>标签中来指定type的值来匹配自定义的限定符注解。匹配类型注解是完整的类名的,需要和自定义的类名一致。或者,如果没有命名冲突的话,可以仅仅使用类名。参考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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.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>

在很多情况下,仅仅使用注解就足够了。而且这一点在注解服务更为泛化的情况下更为有用。比如说,开发者需要提供一个线下的目录的话,可以参考如下的定义:

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

}

之后的使用不要指定值了,自动装载配置如下:

public class MovieRecommender {

    @Autowired
    @Offline
    private MovieCatalog offlineCatalog;

    // ...

}

XML中的配置也较为简单,如下:

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

当然,开发者也可以在一个value属性不足的情况下定义更多的限定符的参数来限定Bean的自动装载。如果在自定义的限定符注解中定义了多个限定的参数,那么Bean必须符合全部的参数才能被视为自动装载的候选者。参考如下的例子:

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

    String genre();

    Format format();

}

其中Format是一个枚举类型。

public enum Format {
    VHS, DVD, BLURAY
}

在前面自定义的限定符中,则必须满足两个属性的匹配才能够进行依赖注入:

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;

    // ...

}

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

    <context:annotation-config/>

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

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="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>

可以看出,限定符的定义是类似key-value的形式存在于<qualifier/>的子标签中的。除此之外,如果限定符有效,则优先选择匹配的Bean作为依赖进行注入,如果没有,自动装载的机制会在不存在限定符的时候自动退回到<meat/>,如上述代码的最后两个Bean。

使用泛型作为自动装载的限定符

关于@Qualifier注解,也可以使用Java泛型来作为限定符。比如说,有如下配置:

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }

}

假设上面提到的Bean实现了一个泛型接口比如说:Store<String>Store<Integer>,开发者可以使用@Autowire来自动装载,而泛型的类型则作为限定符,参考如下代码:

@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

泛型的限定同样可以用于List,Map或者Array上面,如下:

// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;

CustomAutowireConfigurer

CustomAutowireConfigurer是一个BeanFactoryPostProcessor,这个后置处理器可以注册开发者自己的限定符注解,让开发者的注解不依赖于Spring限定符注解。

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

AutowireCandidateResolver通过以下的几种方式来决定自动装载的候选Bean:

  • Bean定义中的autowire-candidate的值
  • 任何<beans/>标签中定义的default-autowire-candidates的值
  • @Qualifier注解和任何在CustomAutowireConfigurer中定义的自定义的限定符注解

当多个Bean限定为自动装载的候选时, 前文中提到的primary属性是优先考虑的。

@Resource注解

Spring支持JSR-250标准中的@Resource注解来注入实例变量或者setter方法。这也是Java EE 5和6之中的常见模式,举例来说,在JSF 1.2中管理的Bean或者是JAX-WS 2.0 endpoint等。Spring也支持这种模式来注入Spring管理的对象。

@Resource需要一个名字的属性,而默认的情况下,Spring会将Bean的名字注入。换言之,@Resource的自动装载是基于名字语义的,就如同如下的例子一样:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

}

如果没有明确指定名字的话,默认的名字就是实例变量的变量名,或者Setter方法中解析出来的名字。以实例变量为例,就取变量的名字作为默认的名字,如果是Setter的方法的话,会取Bean属性的名字。所以,如下的例子会注入Bean的名字的movieFinder的Bean实例。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

}

注解解析的名字是通过ApplicationContext来解析的。如果开发者配置了明确的Spring SimpleJndiBeanFactory的话,这些名字可以通过JNDI来解析。然而,Spring团队仍然建议开发者依赖其默认的行为并简单实用Spring的JNDI查找能力。

在使用@Resource而没有明确指定名字的情况,它比较类似@Autowired注解,@Resource会优先查找类型匹配而不是名字匹配,也能够解析一些常见的依赖:BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisher以及MessageSource等接口。

在如下的例子中,customerPreferenceDao实例变量会优先查找名字为customerPreferenceDao的Bean来进行依赖注入,然后在进行类型匹配CustomerPreferenceDaocontext实例变量作为常见的实例会自动通过ApplicationContext注入进去。

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...

}

@PostConstructPreDestroy注解

CommonAnnotationBeanPostProcessor不仅仅识别@Resource注解,也识别JSR-250标准中的生命周期注解。在 Spring核心技术IoC容器(六)中针对回调函数的描述了注解外其他的方法实现回调。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...
    }

}

关于Spring的生命周期的机制在前文 《Spring核心技术IoC容器(六)》之中有更详细的描述。

原文地址:https://www.cnblogs.com/qitian1/p/6461554.html