Spring IOC容器注解 @Autowired、@Resource、@Primary、@Value的用法

时间:2021-01-01 00:58:06

一、使用@Autowired

您可以将@Autowired注释应用于构造函数,如下例所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}
从Spring Framework 4.3开始,如果目标bean只定义了一个构造函数开始,则不再需要在这样的构造函数上添加@Autowired注释。但是,如果有多个构造函数可用,并且没有主/默认构造函数,则必须用@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;
    }

    // ...
}

您还可以通过向需要该类型数组的字段或方法添加@Autowired注释,指示Spring从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。映射值包含预期类型的所有bean,键包含相应的bean名称,如下例所示:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}

默认情况下,当给定的注入点没有匹配的候选bean时,自动连线会失败。对于声明的数组、集合或映射,至少需要一个匹配元素。

默认行为是将带注释的方法和字段视为指示所需的依赖项。您可以如以下示例所示更改此行为,通过将不可满足的注入点标记为非必需(即,通过将@Autowired中的必需属性设置为false),使框架能够跳过该注入点:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

注入的构造函数和工厂方法参数是一种特殊情况,因为@Autowired中的必需属性具有某种不同的含义,因为Spring的构造函数解析算法可能会处理多个构造函数。默认情况下,构造函数和工厂方法参数实际上是必需的,但在单个构造函数场景中有一些特殊规则,如多元素注入点(数组、集合、映射),如果没有匹配的bean可用,则解析为空实例。这允许一个通用的实现模式,其中所有依赖项都可以在一个唯一的多参数构造函数中声明,例如,声明为一个没有@Autowired注释的公共构造函数。

或者,您可以通过Java 8的Java.util.Optional来表达特定依赖项的非必需性质,如下例所示:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

从Spring Framework 5.0开始,您还可以使用@Nullable注释(任何包中的任何类型 — 例如,JSR-305中的javax.annotation.Nullable)或仅利用Kotlin内置的null安全支持:

publicclassSimpleMovieLister{

    @AutowiredpublicvoidsetMovieFinder(@Nullable MovieFinder movieFinder){
        ...
    }
}

您还可以将@Autowired用于众所周知的可解析依赖关系的接口:BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher和MessageSource。这些接口及其扩展接口(如ConfigurationApplicationContext或ResourcePatternResolver)将自动解析,无需特殊设置。以下示例自动关联ApplicationContext对象:

publicclassMovieRecommender{

    @Autowiredprivate ApplicationContext context;

    publicMovieRecommender(){
    }

    // ...
}

二、 @Resource

Spring还支持对字段或bean属性设置器方法使用JSR-250@Resource注释(jakarta.annotation.Resource)进行注入。这是Jakarta EE中常见的模式:例如,在JSF管理的bean和JAX-WS端点中。Spring也支持Spring托管对象的这种模式。

@资源具有名称属性。默认情况下,Spring将该值解释为要注入的bean名称。换句话说,它遵循名称语义,如下例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

(1)此行注入 .@Resource

如果未显式指定名称,则默认名称从字段名或setter方法派生。如果是字段,则采用字段名。对于setter方法,它采用bean属性名称。以下示例将名为movieFinder的bean注入其setter方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

在没有指定显式名称的@Resource用法的独占情况下,与@Autowired类似,@Resource查找主类型匹配而不是特定的命名bean,并解析众所周知的可解析依赖项:BeanFactory、ApplicationContext、ResourceLoader、ApplicationEventPublisher和MessageSource接口。

因此,在以下示例中,customerPreferenceDao字段首先查找名为“customerPreferencesDao”的bean,然后返回到customerPreferenceDao类型的主类型匹配:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; (1)

    public MovieRecommender() {
    }

    // ...
}

上下文字段是基于已知的可解析依赖类型ApplicationContext注入的。

三、微调基于注释的自动装配的@Primary

由于按类型自动布线可能会导致多个候选项,因此通常需要对选择过程进行更多控制。实现这一点的一种方法是使用Spring的@Primary注释@Primary表示当多个bean作为自动连接到单个值依赖项的候选时,应优先选择特定bean。如果候选对象中正好存在一个主bean,则它将成为自动连接的值。

考虑将firstMovieCatalog定义为主要MovieCatalog的以下配置:

@Configuration
public class MovieConfiguration {

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

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

    // ...
}

使用上述配置,以下MovieRecommender与第一个MovieCatalog自动连接:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

相应的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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://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>

四、微调基于注释的自动装配的@Qualifiers

@Primary是按类型使用自动布线的有效方法,当一个实例有多个实例时 可以确定主要候选人。当您需要更好地控制选择过程时, 您可以使用 Spring 的注释@Qualifier。可以关联限定符值 使用特定参数,缩小类型匹配集,以便特定 Bean 是 为每个参数选择。在最简单的情况下,这可以是一个普通的描述性值,如 如以下示例所示:

public class MovieRecommender {

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

    // ...
}

还可以在单个构造函数参数或方法参数上指定@Qualifier注释,如下例所示:

public class MovieRecommender {

    private final MovieCatalog movieCatalog;

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

下面的示例显示了相应的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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

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

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

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

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

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

</beans>
(1)具有主限定符值的bean与用相同值限定的构造函数参数关联。
(2)具有操作限定符值的bean与用相同值限定的构造函数参数关联。

对于回退匹配,bean名称被视为默认限定符值。因此,您可以使用id main而不是嵌套限定符元素来定义bean,从而得到相同的匹配结果。然而,尽管您可以使用此约定按名称引用特定的bean,@Autowired基本上是关于带有可选语义限定符的类型驱动注入。这意味着,即使使用bean名称回退,限定符值在类型匹配集中也始终具有收缩语义。它们不会在语义上表示对唯一bean id的引用。良好的限定符值是main、EMEA或持久的,表示独立于bean id的特定组件的特性,在匿名bean定义(如前一示例中的定义)的情况下,可以自动生成这些特性。

限定符也适用于类型化集合,如前所述 — 例如,设置<MovieCatalog>。在这种情况下,根据声明的限定符,所有匹配的bean都作为集合注入。这意味着限定符不必是唯一的。相反,它们构成了过滤标准。例如,您可以使用相同的限定符值“action”定义多个MovieCatalog bean,所有这些bean都被注入到一个用@qualifier(“action”)注释的Set<MovieCatalog>中。

也就是说,如果您打算通过名称来表示注释驱动的注入,请不要主要使用@Autowired,即使它能够通过bean名称在类型匹配候选中进行选择。相反,使用JSR-250@Resource注释,该注释在语义上定义为通过其唯一名称标识特定的目标组件,声明的类型与匹配过程无关@Autowired具有截然不同的语义:在按类型选择候选bean之后,指定的String限定符值仅在那些类型选择的候选bean中被考虑(例如,将帐户限定符与标记有相同限定符标签的bean匹配)。

对于本身定义为集合、映射或数组类型的bean,@Resource是一个很好的解决方案,通过唯一的名称引用特定的集合或数组bean。也就是说,从4.3开始,只要元素类型信息保存在@Bean返回类型签名或集合继承层次结构中,就可以通过Spring的@Autowired类型匹配算法匹配集合、Map和数组类型。在这种情况下,可以使用限定符值在相同类型的集合中进行选择,如前一段所述。

从4.3开始,@Autowired还考虑了注入的自引用(即,对当前注入的bean的引用)。注意,自注入是一种回退。对其他组件的常规依赖始终具有优先级。从这个意义上说,自我介绍不参与常规的候选人选择,因此特别是从不主要。相反,它们总是以最低优先级结束。在实践中,您应该使用自引用作为最后的手段(例如,通过bean的事务代理调用同一实例上的其他方法)。在这样的场景中,考虑将受影响的方法分解为单独的委托bean。或者,您可以使用@Resource,它可以通过其唯一名称获取返回到当前bean的代理。

@Autowired应用于字段、构造函数和多参数方法,允许在参数级别通过限定符注释进行缩小。相反,@Resource仅支持具有单个参数的字段和bean属性setter方法。因此,如果注入目标是构造函数或多参数方法,则应使用限定符。

您可以创建自己的自定义限定符注释。为此,请定义一个注释并在定义中提供@Qualifier注释,如下例所示:

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

    String value();
}

然后,您可以在Autowired字段和参数上提供自定义qualifier,如下例所示:

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/>标记的子元素,然后指定类型和值以匹配自定义限定符注释。该类型与批注的完全限定类名匹配。或者,为了方便起见,如果不存在名称冲突的风险,可以使用短类名。以下示例演示了两种方法:

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

在Classpath扫描和托管组件中,您可以看到一种基于注释的替代方法来提供XML中的限定符元数据。具体而言,请参阅为注释提供限定符元数据。

在某些情况下,使用没有值的注释就足够了。当注释用于更一般的用途并且可以应用于多种不同类型的依赖项时,这可能很有用。例如,您可以提供一个脱机目录,当没有可用的Internet连接时可以搜索该目录。首先,定义简单注释,如下例所示:

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

然后将注释添加到要自动关联的字段或属性,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Offline (1)
    private MovieCatalog offlineCatalog;

    // ...
}

(1)此行添加@Offline注释。

现在,bean定义只需要限定符类型,如下例所示:

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> (1)
    <!-- inject any dependencies required by this bean -->
</bean>
(1)此元素指定限定符。

您还可以定义自定义限定符注释,这些注释接受除简单值属性之外的命名属性,或者接受简单值属性。如果在要自动连线的字段或参数上指定了多个属性值,则bean定义必须匹配所有此类属性值,才能被视为自动连线候选。例如,考虑以下注释定义:

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

    String genre();

    Format format();
}

在这种情况下,Format是一个枚举,定义如下:

public enum Format {
    VHS, DVD, BLURAY
}

要自动连线的字段使用自定义限定符进行注释,并包含两个属性的值:gene和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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://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>

五、使用泛型作为Autowiring Qualifiers

除了@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 Store接口,泛型用作限定符,如下例所示:

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

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

当自动连接列表、映射实例和数组时,通用限定符也适用。以下示例自动关联通用列表:

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

六、@Value

@Value通常用于注入外部化属性:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

使用以下配置:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

以及以下application.properties文件:

catalog.name=MovieCatalog

在这种情况下,目录参数和字段将等于MovieCatalog值。

Spring提供了默认的宽松嵌入式值解析器。它将尝试解析属性值,如果无法解析,则会将属性名称(例如${catalog.name})作为值注入。如果要对不存在的值保持严格控制,应声明PropertySourcesPlaceholderConfigurer bean,如下例所示:

@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

如果无法解析任何${}占位符,使用上述配置可确保Spring初始化失败。也可以使用setPlaceholderPrefix、setPlaceholdersSuffix或setValueSeparator等方法自定义占位符。

Spring提供的内置转换器支持允许自动处理简单的类型转换(例如,到Integer或int)。多个逗号分隔的值可以自动转换为字符串数组,而无需额外的工作。

可以提供如下默认值:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

SpringBeanPostProcessor在幕后使用ConversionService来处理将@value中的String值转换为目标类型的过程。如果您想为自己的自定义类型提供转换支持,可以提供自己的ConversionService bean实例,如下例所示:

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

当@Value包含SpEL表达式时,将在运行时动态计算该值,如下例所示:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

SpEL还支持使用更复杂的数据结构:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}

七、@PostConstruct and @PreDestroy

CommonAnnotationBeanPostProcessor不仅识别@Resource注释,还识别JSR-250生命周期注释:jakara.annotation.PostConstruct和jakara.annotation.PreDestroy。在Spring 2.5中引入了对这些注释的支持,为初始化回调和销毁回调中描述的生命周期回调机制提供了一种替代方案。如果CommonAnnotationBeanPostProcessor在Spring ApplicationContext中注册,则在生命周期的同一点调用携带这些注释之一的方法,该方法与相应的Spring生命周期接口方法或显式声明的回调方法相同。在以下示例中,@PostConstruct注解的方法在项目启动的时候执行这个方法,被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法:

public class CachingMovieLister {

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

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

与@Resource一样,@PostConstruct和@PreDestroy注释类型是JDK6到8标准Java库的一部分。然而,整个javax.annotation包与JDK9中的核心Java模块分离,最终在JDK11中删除。从Jakarta EE 9开始,该包现在位于Jakarta.annotation中。如果需要,jakarta.notation-api工件现在需要通过MavenCentral获得,只需像任何其他库一样添加到应用程序的类路径。