xinrong2019 / xinrong2019.github.io

My Blog
https://xinrong2019.github.io
1 stars 1 forks source link

20190719 Spring Framework体系化学习之IOC容器(下) #92

Open xinrong2019 opened 5 years ago

xinrong2019 commented 5 years ago

1.9. Annotation-based Container Configuration

Are annotations better than XML for configuring Spring?

基于注释的配置的引入引发了这种方法是否比XML“更好”的问题。简短的回答是“它取决于。”长期的答案是每种方法都有其优点和缺点,通常,由开发人员决定哪种策略更适合他们。由于它们的定义方式,注释在其声明中提供了大量上下文,从而导致更短更简洁的配置。但是,XML擅长在不触及源代码或重新编译它们的情况下连接组件。一些开发人员更喜欢将布线靠近源,而另一些开发人员则认为注释类不再是POJO,而且配置变得分散且难以控制。

无论选择如何,Spring都可以兼顾两种风格,甚至可以将它们混合在一起。值得指出的是,通过其JavaConfig选项,Spring允许以非侵入方式使用注释,而无需触及目标组件源代码,并且在工具方面,Spring Tool Suite支持所有配置样式。

基于注释的配置提供了XML设置的替代方案,该配置依赖于字节码元数据来连接组件而不是角括号声明。开发人员不是使用XML来描述bean连接,而是通过在相关的类,方法或字段声明上使用注释将配置移动到组件类本身。如示例中所述:RequiredAnnotationBeanPostProcessor,将BeanPostProcessor与注释结合使用是扩展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required注释强制执行所需属性的可能性。 Spring 2.5使得有可能采用相同的通用方法来驱动Spring的依赖注入。从本质上讲,@ Autowired注释提供的功能与自动装配协作者中描述的相同,但具有更细粒度的控制和更广泛的适用性。 Spring 2.5还增加了对JSR-250注释的支持,例如@ PostConstruct和@PreDestroy。 Spring 3.0增加了对javax.inject包中包含的JSR-330(Java的依赖注入)注释的支持,例如@Inject和@Named。有关这些注释的详细信息,请参阅相关章节。

注释注入在XML注入之前执行。因此,XML配置会覆盖通过这两种方法连接的属性的注释。

与往常一样,您可以将它们注册为单独的bean定义,但也可以通过在基于XML的Spring配置中包含以下标记来隐式注册它们(请注意包含上下文命名空间):

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

</beans>

(隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,PersistenceAnnotationBeanPostProcessor和前面提到的RequiredAnnotationBeanPostProcessor。)

<context:annotation-config />仅在定义它的同一应用程序上下文中查找bean上的注释。这意味着,如果将<context:annotation-config />放在DispatcherServletWebApplicationContext中,它只检查控制器中的@Autowired bean,而不检查您的服务。有关更多信息,请参阅DispatcherServlet

xinrong2019 commented 5 years ago

1.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类本身(例如,转换为init方法)。即使您在容器外部使用类,这样做也会强制执行那些必需的引用和值。

@Required注释从Spring Framework 5.1开始正式弃用,支持使用构造函数注入所需的设置(或者自定义实现InitializingBean.afterPropertiesSet()以及bean属性setter方法)。

1.9.2. Using @Autowired

在本节所包含的示例中,可以使用JSR 330的@Inject注释代替Spring的@Autowired注释。有关详细信息,请参见此处

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

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

从Spring Framework 4.3开始,如果目标bean只定义了一个开头的构造函数,则不再需要在这样的构造函数上使用@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;
    }

    // ...
}

确保您的目标组件(例如,MovieCatalog或CustomerPreferenceDao)始终按照用于@Autowired-annotated注入​​点的类型声明。否则,由于在运行时未找到类型匹配,注入可能会失败。

对于通过类路径扫描找到的XML定义的bean或组件类,容器通常预先知道具体类型。但是,对于@Bean工厂方法,您需要确保声明的返回类型具有足够的表现力。对于实现多个接口的组件或可能由其实现类型引用的组件,请考虑在工厂方法上声明最具体的返回类型(至少与引用bean的注入点所需的特定类型一致)。

您还可以通过将注释添加到需要该类型数组的字段或方法,从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;
    }

    // ...
}

如果希望数组或列表中的项按特定顺序排序,则目标bean可以实现org.springframework.core.Ordered接口或使用@Order或标准@Priority批注。否则,它们的顺序遵循容器中相应目标bean定义的注册顺序。

您可以在目标类级别和@Bean方法上声明@Order注释,可能是通过单个bean定义(在多个定义使用相同bean类的情况下)。 @Order值可能影响注入点的优先级,但要注意它们不会影响单例启动顺序,这是由依赖关系和@DependsOn声明确定的正交关注点。

请注意,标准的javax.annotation.Priority注释在@Bean级别不可用,因为它无法在方法上声明。它的语义可以通过@Order值与@Primary在每个类型的单个bean上进行建模。

只要预期的键类型是String,即使是类型化的Map实例也可以自动装配。 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;
    }

    // ...
}

如果不可用的依赖项(或多个参数情况下的依赖项之一),则根本不会调用非必需的方法。在这种情况下,根本不会填充非必填字段,保留其默认值。

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

每个类只能标记一个带注释的构造函数,但可以注释多个非必需的构造函数。在这种情况下,每个都被认为是候选者之一,Spring使用最贪婪的构造函数,其依赖性可以得到满足 - 也就是说,具有最多参数的构造函数。构造函数解析算法与具有重载构造函数的非注释类相同,只是将候选者缩小到带注释的构造函数。

建议使用@Autowired的'required'属性而不是setter方法的@Required注释。 “required”属性表示该属性不是自动装配所必需的。如果无法自动装配,则会忽略该属性。另一方面,@ Required更强大,因为它强制通过容器支持的任何方式设置属性。如果未定义任何值,则会引发相应的异常。

或者,您可以通过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):

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

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

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

@Autowired,@ Inject,@ Value和@Resource注释由Spring BeanPostProcessor实现处理。这意味着您无法在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有)中应用这些注释。必须使用XML或Spring @Bean方法显式地“连接”这些类型。

xinrong2019 commented 5 years ago

1.9.3 使用@Primary微调基于注释的自动装配

由于按类型自动装配可能会导致多个候选人,因此通常需要对选择过程进行更多控制。实现这一目标的一种方法是使用Spring的@Primary注释。 @Primary指示当多个bean可以自动装配到单值依赖项时,应该优先选择特定的bean。如果候选者中只存在一个主bean,则它将成为自动装配的值。

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

@Configuration
public class MovieConfiguration {

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

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

    // ...
}

使用上述配置,以下MovieRecommender将与firstMovieCatalog一起自动装配:

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>
xinrong2019 commented 5 years ago

1.9.4.使用限定符微调基于注释的自动装配

@Primary是一种有效的方法,可以在确定一个主要候选者的情况下按类型使用自动装配。当您需要更多控制选择过程时,可以使用Spring的@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 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"/> 

        <!-- 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名称被视为默认限定符值。因此,您可以使用id而不是嵌套的限定符元素定义bean,从而得到相同的匹配结果。但是,虽然您可以使用此约定来按名称引用特定bean,但@Autowired基本上是关于具有可选语义限定符的类型驱动注入。这意味着即使使用bean名称回退,限定符值在类型匹配集中也总是具有缩小的语义。它们在语义上不表示对唯一bean id的引用。良好的限定符值是main或EMEA或persistent,表示独立于bean id的特定组件的特征,可以在匿名bean定义(例如前面示例中的定义)的情况下自动生成。

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

省略一堆Qualifier注解的描述,Qualifier注解是用于注入的精确描述的bean。Qualifier可用于ClassPath扫描时,精确的查找拥有某些注解的类,从而做一些事情,比如,可以定义一个Offline组合注解,用于在脱机状态启用某些功能:

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

}
public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

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

还可以使用Qualifier注解定义一个多属性的注解,

@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 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>
xinrong2019 commented 5 years ago

1.9.5. Using Generics as 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

通用限定符也适用于自动装配列表,Map实例和数组。以下示例自动装配通用List:

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

1.9.6. Using CustomAutowireConfigurer

CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它允许您注册自己的自定义限定符注释类型,即使它们没有使用Spring的@Qualifier注释进行注释。以下示例显示如何使用CustomAutowireConfigurer:

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

AutowireCandidateResolver通过以下方式确定autowire候选者:

当多个bean有资格作为autowire候选者时,“primary”的确定如下:如果候选者中只有一个bean定义的primary属性设置为true,则选择它。

1.9.7. Injection with @Resource

Spring还通过对字段或bean属性setter方法使用JSR-250 @Resource注释(javax.annotation.Resource)来支持注入。这是Java EE中的常见模式:例如,在JSF管理的bean和JAX-WS端点中。 Spring也支持Spring管理对象的这种模式。

@Resource采用name属性。默认情况下,Spring将该值解释为要注入的bean名称。换句话说,它遵循按名称语义,如以下示例所示:

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

随注释提供的名称由ApplicationContext解析为bean名称,CommonAnnotationBeanPostProcessor知道该名称。如果您显式配置Spring的SimpleJndiBeanFactory,则可以通过JNDI解析名称。但是,我们建议您依赖于默认行为并使用Spring的JNDI查找功能来保留间接级别。

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

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

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; 

    public MovieRecommender() {
    }

    // ...
}

1.9.8. Using @PostConstruct and @PreDestroy

CommonAnnotationBeanPostProcessor不仅识别@Resource注释,还识别JSR-250生命周期注释:javax.annotation.PostConstruct和javax.annotation.PreDestroy。在Spring 2.5中引入,对这些注释的支持提供了初始化回调和销毁回调中描述的生命周期回调机制的替代方法。如果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...
    }
}

有关组合各种生命周期机制的效果的详细信息,请参阅组合生命周期机制

与@Resource一样,@ PostConstruct和@PreDestroy注释类型是JDK 6到8的标准Java库的一部分。但是,整个javax.annotation包与JDK 9中的核心Java模块分离,最终在JDK 11中删除如果需要,现在需要通过Maven Central获取javax.annotation-api工件,只需像任何其他库一样添加到应用程序的类路径中。

xinrong2019 commented 5 years ago

1.10. Classpath Scanning and Managed Components(类路径扫描和托管组件)

本章中的大多数示例都使用XML来指定在Spring容器中生成每个BeanDefinition的配置元数据。上一节(基于注释的容器配置)演示了如何通过源级注释提供大量配置元数据。但是,即使在这些示例中,“基本”bean定义也在XML文件中显式定义,而注释仅驱动依赖项注入。本节介绍通过扫描类路径隐式检测候选组件的选项。候选组件是与筛选条件匹配的类,并且具有向容器注册的相应bean定义。这消除了使用XML执行bean注册的需要。相反,您可以使用注释(例如,@ Component),AspectJ类型表达式或您自己的自定义筛选条件来选择哪些类具有向容器注册的bean定义。

从Spring 3.0开始,Spring JavaConfig项目提供的许多功能都是核心Spring Framework的一部分。这允许您使用Java而不是使用传统的XML文件来定义bean。有关如何使用这些新功能的示例,请查看@Configuration,@ Both,@ Import和@DependsOn注释。

1.10.1. @Component and Further Stereotype Annotations

@Repository注释是任何满足存储库角色或构造型(也称为数据访问对象或DAO)的类的标记。此标记的用法之一是异常的自动转换,如异常转换中所述。

Spring提供了进一步的构造型注释:@ Component,@ Service和@Controller。 @Component是任何Spring管理组件的通用构造型。 @Repository,@ Service和@Controller是@Component的特殊化,用于更具体的用例(分别在持久性,服务和表示层中)。因此,您可以使用@Component注释组件类,但是,通过使用@ Repository,@ Service或@Controller注释它们,您的类更适合通过工具处理或与方面关联。例如,这些刻板印象注释成为切入点的理想目标。 @Repository,@ Service和@Controller还可以在Spring Framework的未来版本中携带其他语义。因此,如果您选择在服务层使用@Component或@Service,@ Service显然是更好的选择。同样,如前所述,已经支持@Repository作为持久层中自动异常转换的标记。

1.10.2. Using Meta-annotations and Composed Annotations(使用元注解和组合注解)

Spring提供的许多注释都可以在您自己的代码中用作元注释。元注释是可以应用于另一个注释的注释。例如,前面提到的@Service注释是使用@Component进行元注释的,如下例所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {

    // ....
}

您还可以组合元注释来创建“组合注释”。例如,Spring MVC的@RestController注释由@Controller和@ResponseBody组成。

此外,组合注释可以选择从元注释重新声明属性以允许自定义。当您只想公开元注释属性的子集时,这可能特别有用。例如,Spring的@SessionScope注释将范围名称硬编码到会话,但仍允许自定义proxyMode。以下清单显示了SessionScope批注的定义:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

然后,您可以使用@SessionScope而不声明proxyMode,如下所示:

@Service
@SessionScope
public class SessionScopedService {
    // ...
}

您还可以覆盖proxyMode的值,如以下示例所示:

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}

For further details, see the Spring Annotation Programming Model wiki page.

1.10.3. Automatically Detecting Classes and Registering Bean Definitions

Spring可以自动检测构造型类,并使用ApplicationContext注册相应的BeanDefinition实例。例如,以下两个类符合此类自动检测的条件:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

要自动检测这些类并注册相应的bean,需要将@ComponentScan添加到@Configuration类,其中basePackages属性是两个类的公共父包。 (或者,您可以指定包含每个类的父包的逗号或分号或空格分隔列表。)

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    ...
}

为简洁起见,前面的示例可能使用了注释的value属性(即@ComponentScan(“org.example”))。

以下替代方法使用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
        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:component-scan base-package="org.example"/>

</beans>

使用<context:component-scan>隐式启用<context:annotation-config>的功能。使用<context:component-scan>时,通常不需要包含<context:annotation-config>元素。

扫描类路径包需要在类路径中存在相应的目录条目。使用Ant构建JAR时,请确保不要激活JAR任务的仅文件开关。此外,在某些环境中,可能不会基于安全策略公开类路径目录 - 例如,JDK 1.7.0_45及更高版本上的独立应用程序(需要在清单中设置“Trusted-Library” - 请参阅https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常按预期工作。但是,请确保在module-info描述符中导出组件类。如果您希望Spring调用类的非公共成员,请确保它们已“打开”(即,它们在module-info描述符中使用opens声明而不是exports声明)。

此外,使用component-scan元素时,将隐式包含AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor。这意味着这两个组件是自动检测并连接在一起的 - 所有这些都没有在XML中提供任何bean配置元数据。

您可以通过包含值为false的annotation-config属性来禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。

xinrong2019 commented 5 years ago

1.10.4. Using Filters to Customize Scanning

默认情况下,使用@Component,@ Repository,@ Service,@ Controller注释的类或自身使用@Component注释的自定义注释是唯一检测到的候选组件。但是,您可以通过应用自定义筛选器来修改和扩展此行为。将它们添加为@ComponentScan注释的includeFilters或excludeFilters参数(或作为component-scan元素的include-filter或exclude-filter子元素)。每个filter元素都需要type和expression属性。下表介绍了筛选选项:

以下示例显示忽略所有@Repository注释并使用“存根”存储库的配置:

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

以下清单显示了等效的XML:

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

您还可以通过在注释上设置useDefaultFilters = false或通过提供use-default-filters =“false”作为元素的属性来禁用默认过滤器。实际上,这会禁用自动检测使用@ Component,@ Repository,@ Service,@ Controller或@Configuration注释的类。

1.10.5. Defining Bean Metadata within Components

Spring组件还可以向容器提供bean定义元数据。您可以使用用于在@Configuration注释类中定义bean元数据的相同@Bean注释来执行此操作。以下示例显示了如何执行此操作:

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
}

上面的类是一个Spring组件,在其doWork()方法中具有特定于应用程序的代码。但是,它还提供了一个bean定义,它具有引用publicInstance()方法的工厂方法。@Bean批注标识工厂方法和其他bean定义属性,例如通过@Qualifier批注的限定符值。可以指定的其他方法级注释是@Scope,@ Lazy和自定义限定符注释。

除了它的组件初始化角色之外,您还可以将@Lazy注释放在标有@Autowired或@Inject的注入点上。在这种情况下,它会导致注入惰性解析代理。

如前所述,支持自动装配的字段和方法,以及对@Bean方法的自动装配的额外支持。以下示例显示了如何执行此操作:

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}

该示例将String方法参数country自动装配到另一个名为privateInstance的bean上的age属性值。Spring Expression Language元素通过符号#{<expression>}定义属性的值。对于@Value注释,表达式解析器预先配置为在解析表达式文本时查找bean名称。

从Spring Framework 4.3开始,您还可以声明一个类型为InjectionPoint的工厂方法参数(或其更具体的子类:DependencyDescriptor)来访问触发创建当前bean的请求注入点。请注意,这仅适用于实例创建bean实例,而不适用于注入现有实例。因此,此功能对原型范围的bean最有意义。对于其他作用域,工厂方法只能看到触发在给定作用域中创建新bean实例的注入点(例如,触发创建惰性单例bean的依赖项)。在这种情况下,您可以使用提供的注入点元数据和语义关注。以下示例显示了如何使用InjectionPoint:

@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}

常规Spring组件中的@Bean方法的处理方式与Spring @Configuration类中的对应方式不同。不同之处在于,使用CGLIB不会增强@Component类来拦截方法和字段的调用。 CGLIB代理是调用@Configuration类中的@Bean方法中的方法或字段创建对协作对象的bean元数据引用的方法。这些方法不是用普通的Java语义调用的,而是通过容器来提供通常的生命周期管理和Spring bean的代理,即使在通过对@Bean方法的编程调用引用其他bean时也是如此。相反,在普通@Component类中调用@Bean方法中的方法或字段具有标准Java语义,不应用特殊的CGLIB处理或其他约束。

您可以将@Bean方法声明为static,允许在不创建包含配置类作为实例的情况下调用它们。这在定义后处理器bean(例如,类型为BeanFactoryPostProcessor或BeanPostProcessor)时特别有意义,因为这样的bean在容器生命周期的早期就会初始化,并且应避免在此时触发配置的其他部分。

由于技术限制,对静态@Bean方法的调用永远不会被容器拦截,甚至在@Configuration类中也没有(如本节前面所述):CGLIB子类化只能覆盖非静态方法。因此,直接调用另一个@Bean方法具有标准的Java语义,从而导致直接从工厂方法本身返回一个独立的实例。

@Bean方法的Java语言可见性对Spring容器中生成的bean定义没有立即影响。您可以根据需要在非@Configuration类中自由声明工厂方法,也可以在任何地方自由声明静态方法。但是,@ Conffiguration类中的常规@Bean方法需要可以覆盖 - 也就是说,它们不能声明为private或final。

@Bean方法也可以在给定组件或配置类的基类上发现,也可以在组件或配置类实现的接口中声明的Java 8默认方法上发现。这使得在编写复杂的配置安排时具有很大的灵活性,从Spring 4.2开始,甚至可以通过Java 8默认方法实现多重继承。

最后,单个类可以为同一个bean保存多个@Bean方法,作为根据运行时可用依赖项使用的多个工厂方法的安排。这与在其他配置方案中选择“最贪婪”构造函数或工厂方法的算法相同:在构造时选择具有最多可满足依赖项的变体,类似于容器在多个@Autowired构造函数之间进行选择的方式。

1.10.6. Naming Autodetected Components

当组件作为扫描过程的一部分自动检测时,其bean名称由该扫描程序已知的BeanNameGenerator策略生成。默认情况下,任何包含名称值的Spring构造型注释(@ Component,@ Repository,@ Service和@Controller)都会将该名称提供给相应的bean定义。

如果此类注释不包含名称值或任何其他检测到的组件(例如自定义过滤器发现的那些组件),则默认bean名称生成器将返回未大写的非限定类名称。例如,如果检测到以下组件类,则名称将为myMovieLister和movieFinderImpl:

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

如果您不想依赖默认的bean命名策略,则可以提供自定义bean命名策略。首先,实现BeanNameGenerator接口,并确保包含默认的无参数构造函数。然后,在配置扫描程序时提供完全限定的类名,如以下示例注释和bean定义所示:

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

作为一般规则,考虑在其他组件可能对其进行显式引用时使用注释指定名称。另一方面,只要容器负责接线,自动生成的名称就足够了。

1.10.7. Providing a Scope for Autodetected Components

与Spring管理的组件一样,自动检测组件的默认和最常见的范围是单例。但是,有时您需要一个可由@Scope注释指定的不同范围。您可以在注释中提供范围的名称,如以下示例所示:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

@Scope注释仅在具体bean类(对于带注释的组件)或工厂方法(对于@Bean方法)上进行了内省。与XML bean定义相比,没有bean定义继承的概念,类级别的继承层次结构与元数据目的无关。

有关特定于Web的范围(如Spring上下文中的“request”或“session”)的详细信息,请参阅请求,会话,应用程序和WebSocket范围。与这些范围的预构建注释一样,您也可以使用Spring的元注释方法编写自己的范围注释:例如,使用@Scope(“prototype”)进行元注释的自定义注释,可能还会声明自定义范围代理模式。

要为范围解析提供自定义策略而不是依赖基于注释的方法,可以实现ScopeMetadataResolver接口。请确保包含默认的无参数构造函数。然后,您可以在配置扫描程序时提供完全限定的类名,因为以下注释和bean定义示例显示:

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

使用某些非单例作用域时,可能需要为作用域对象生成代理。这种推理在Scoped Beans中描述为Dependencies。为此,component-scan元素上提供了scoped-proxy属性。三个可能的值是:no,interfaces和targetClass。例如,以下配置导致标准JDK动态代理:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    ...
}

<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

1.10.8. Providing Qualifier Metadata with Annotations

在使用限定符微调基于注释的自动装配中讨论了@Qualifier注释。该部分中的示例演示了在解析自动线候选时使用@Qualifier注释和自定义限定符注释来提供细粒度控制。因为这些示例基于XML bean定义,所以通过使用XML中bean元素的限定符或元子元素,在候选bean定义上提供限定符元数据。当依靠类路径扫描来自动检测组件时,可以在候选类上为类型级注释提供限定符元数据。以下三个示例演示了此技术:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}

使用大多数基于注释的备选方案时,请记住注释元数据绑定到类定义本身,而使用XML允许多个相同类型的bean在其限定符元数据中提供变体,因为每个元数据都是按照 - 实例而不是类。

1.10.9. Generating an Index of Candidate Components

虽然类路径扫描速度非常快,但可以通过在编译时创建候选的静态列表来提高大型应用程序的启动性能。在此模式下,所有作为组件扫描目标的模块都必须使用此机制。

您现有的@ComponentScan或<context:component-scan指令必须保持原样,以请求上下文扫描某些包中的候选项。当ApplicationContext检测到这样的索引时,它会自动使用它而不是扫描类路径。

要生成索引,请为包含组件扫描指令目标的组件的每个模块添加其他依赖项。以下示例显示了如何使用Maven执行此操作:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.1.8.RELEASE</version>
        <optional>true</optional>
    </dependency>
</dependencies>

使用Gradle 4.5及更早版本时,应在compileOnly配置中声明依赖项,如以下示例所示:

dependencies {
    compileOnly "org.springframework:spring-context-indexer:5.1.8.RELEASE"
}

使用Gradle 4.6及更高版本时,应在annotationProcessor配置中声明依赖项,如以下示例所示:

dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:5.1.8.RELEASE"
}

该过程生成包含在jar文件中的META-INF / spring.components文件。

在IDE中使用此模式时,必须将spring-context-indexer注册为注释处理器,以确保在更新候选组件时索引是最新的。

在类路径上找到META-INF / spring.components时,将自动启用索引。如果索引部分可用于某些库(或用例)但无法为整个应用程序构建,则可以通过将spring.index.ignore设置为回退到常规类路径排列(就好像根本没有索引)无论是作为系统属性还是在类路径根目录下的spring.properties文件中。

xinrong2019 commented 5 years ago

1.11. Using JSR 330 Standard Annotations

从Spring 3.0开始,Spring提供对JSR-330标准注释(依赖注入)的支持。这些注释的扫描方式与Spring注释相同。要使用它们,您需要在类路径中包含相关的jar。

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

1.11.1. Dependency Injection with @Inject and @Named

您可以使用@ javax.inject.Inject代替@Autowired,如下所示:

import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    public void listMovies() {
        this.movieFinder.findMovies(...);
        ...
    }
}

与@Autowired一样,您可以在字段级别,方法级别和构造函数 - 参数级别使用@Inject。此外,您可以将注入点声明为提供者,允许按需访问较短范围的bean或通过Provider.get()调用对其他bean的延迟访问。以下示例提供了上述示例的变体:

import javax.inject.Inject;
import javax.inject.Provider;

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

    @Inject
    public void setMovieFinder(Provider<MovieFinder> movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.get().findMovies(...);
        ...
    }
}

如果要为应注入的依赖项使用限定名称,则应使用@Named批注,如以下示例所示:

import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

与@Autowired一样,@ Inject也可以与java.util.Optional或@Nullable一起使用。这在这里更适用,因为@Inject没有required属性。以下一对示例显示了如何使用@Inject和@Nullable:

public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}
public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

1.11.2. @Named and @ManagedBean: Standard Equivalents to the @Component Annotation

您可以使用@ javax.inject.Named或javax.annotation.ManagedBean代替@Component,如以下示例所示:

import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

在不指定组件名称的情况下使用@Component是很常见的。 @Named可以以类似的方式使用,如下例所示:

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

使用@Named或@ManagedBean时,可以使用与使用Spring注释时完全相同的方式使用组件扫描,如以下示例所示:

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    ...
}

与@Component相比,JSR-330 @Named和JSR-250 ManagedBean注释不可组合。您应该使用Spring的构造型模型来构建自定义组件注释。

1.11.3. Limitations of JSR-330 Standard Annotations

使用标准注释时,您应该知道某些重要功能不可用,如下表所示:

Table 6. Spring component model elements versus JSR-330 variants

xinrong2019 commented 5 years ago

1.12. Java-based Container Configuration

本节介绍如何在Java代码中使用注释来配置Spring容器。它包括以下主题:

1.12.1. Basic Concepts: @Bean and @Configuration

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

以上写法等同于下面的的xml写法

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

完整@Configuration vs“lite”@Bean模式?

当@Bean方法在未使用@Configuration注释的类中声明时,它们被称为以“精简”模式处理。在@Component或甚至普通旧类中声明的Bean方法被认为是“lite”,包含类的主要目的不同,而@Bean方法在那里是一种奖励。例如,服务组件可以通过每个适用的组件类上的附加@Bean方法将管理视图公开给容器。在这种情况下,@ Bean方法是一种通用的工厂方法机制。

与完整的@Configuration不同,lite @Bean方法不能声明bean间依赖关系。相反,它们对其包含组件的内部状态进行操作,并且可选地,对它们可以声明的参数进行操作。因此,这样的@Bean方法不应该调用其他@Bean方法。每个这样的方法实际上只是特定bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极副作用是不必在运行时应用CGLIB子类,因此在类设计方面没有限制(即,包含类可能是最终的,等等)。

在常见的场景中,@ Bean方法将在@Configuration类中声明,确保始终使用“完整”模式,并因此将交叉方法引用重定向到容器的生命周期管理。这可以防止通过常规Java调用意外地调用相同的@Bean方法,这有助于减少在“lite”模式下操作时难以跟踪的细微错误。

1.12.2. Instantiating the Spring Container by Using AnnotationConfigApplicationContext

以下部分介绍了Spring的AnnotationConfigApplicationContext,它是在Spring 3.0中引入的。这个多功能的ApplicationContext实现不仅能够接受@Configuration类作为输入,还能接受使用JSR-330元数据注释的普通@Component类和类。

当@Configuration类作为输入提供时,@ Consfiguration类本身被注册为bean定义,并且类中所有声明的@Bean方法也被注册为bean定义。

当提供@Component和JSR-330类时,它们被注册为bean定义,并且假设在必要时在这些类中使用诸如@Autowired或@Inject之类的DI元数据。

Simple Construction

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

Building the Container Programmatically by Using register(Class<?>…​)

您可以使用no-arg构造函数实例化AnnotationConfigApplicationContext,然后使用register()方法对其进行配置。在以编程方式构建AnnotationConfigApplicationContext时,此方法特别有用。以下示例显示了如何执行此操作:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

Enabling Component Scanning with scan(String…​)

@Configuration
@ComponentScan(basePackages = "com.acme") 
public class AppConfig  {
    ...
}

上面的配置组件扫描的方式和一下xml形式的配置等价:

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

在前面的示例中,将扫描com.acme包以查找任何@ Component-annotated类,并将这些类注册为容器中的Spring bean定义。 AnnotationConfigApplicationContext公开scan(String ...)方法以允许相同的组件扫描功能,如以下示例所示:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

请记住@Configuration类是使用@Component进行元注释的,因此它们是组件扫描的候选者。在前面的示例中,假设在com.acme包(或下面的任何包)中声明了AppConfig,它将在调用scan()期间被拾取。在refresh()时,它的所有@Bean方法都被处理并在容器中注册为bean定义。

Support for Web Applications with AnnotationConfigWebApplicationContext

AnnotationConfigApplicationContext的WebApplicationContext变体与AnnotationConfigWebApplicationContext一起提供。配置Spring ContextLoaderListener servlet侦听器,Spring MVC DispatcherServlet等时,可以使用此实现。以下web.xml代码段配置典型的Spring MVC Web应用程序(请注意contextClass context-param和init-param的使用):


<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

1.12.3. Using the @Bean Annotation

Declaring a Bean

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}
<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

这两个声明都在ApplicationContext中创建一个名为transferService的bean,绑定到TransferServiceImpl类型的对象实例,如下图所示:

transferService -> com.acme.TransferServiceImpl

您还可以使用接口(或基类)返回类型声明您的@Bean方法,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

Bean Dependencies

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

Receiving Lifecycle Callbacks

使用@Bean注释定义的任何类都支持常规生命周期回调,并且可以使用JSR-250中的@PostConstruct和@PreDestroy注释。有关更多详细信息,请参阅JSR-250注释。

完全支持常规的Spring生命周期回调。如果bean实现InitializingBean,DisposableBean或Lifecycle,则容器会调用它们各自的方法。

还完全支持标准的* Aware接口集(例如BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等)。

@Bean批注支持指定任意初始化和销毁​​回调方法,就像bean元素上的Spring XML的init-method和destroy-method属性一样,如下例所示:

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}

Specifying Bean Scope

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}

Customizing Bean Naming

@Configuration
public class AppConfig {

    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
}

Bean Aliasing

@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

Bean Description

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}

1.12.4. Using the @Configuration annotation

Injecting Inter-bean Dependencies

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

Lookup Method Injection

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();
}
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

Further Information About How Java-based Configuration Works Internally

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

1.12.5. Composing Java-based Configurations(编写基于Java的配置)

Using the @Import Annotation

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

Injecting Dependencies on Imported @Bean Definitions(在导入的@Bean定义上注入依赖项)

@Configuration
public class ServiceConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

确保以这种方式注入的依赖项只是最简单的类型。 @Configuration类在上下文初始化期间很早就被处理,并且强制以这种方式注入依赖项可能导致意外的早期初始化。尽可能采用基于参数的注入,如前面的示例所示。

另外,通过@Bean特别注意BeanPostProcessor和BeanFactoryPostProcessor定义。这些通常应该声明为静态@Bean方法,而不是触发其包含配置类的实例化。否则,@ Autowired和@Value不能在配置类本身上工作,因为它过早地被创建为bean实例。

以下示例显示了如何将一个bean自动连接到另一个bean:

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    @Autowired
    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

仅在Spring Framework 4.3中支持@Configuration类中的构造函数注入。另请注意,如果目标bean仅定义了一个构造函数,则无需指定@Autowired。在前面的示例中,RepositoryConfig构造函数中不需要@Autowired。

Fully-qualifying imported beans for ease of navigation

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

现在,ServiceConfig与具体的DefaultRepositoryConfig松散耦合,内置的IDE工具仍然很有用:您可以轻松获得RepositoryConfig实现的类型层次结构。通过这种方式,导航@Configuration类及其依赖项与导航基于接口的代码的常规过程没有什么不同。

如果要影响某些bean的启动创建顺序,请考虑将其中一些声明为@Lazy(用于在首次访问时创建而不是在启动时)或@DependsOn某些其他bean(确保在其之前创建特定的其他bean)当前的bean,超出后者的直接依赖性所暗示的)。

Conditionally Include @Configuration Classes or @Bean Methods

基于某些任意系统状态,有条件地启用或禁用完整的@Configuration类甚至单个@Bean方法通常很有用。一个常见的例子是,只有在Spring环境中启用了特定的配置文件时才使用@Profile注释来激活bean(有关详细信息,请参阅Bean定义配置文件)。

@Profile注释实际上是通过使用更灵活的注释@Conditional实现的。 @Conditional注释表示在注册@Bean之前应该参考的特定org.springframework.context.annotation.Condition实现。

Condition接口的实现提供了一个返回true或false的matches(...)方法。例如,以下列表显示了用于@Profile的实际Condition实现:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() != null) {
        // Read the @Profile annotation attributes
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
    }
    return true;
}

See the @Conditional javadoc for more detail.

Combining Java and XML Configuration

Spring的@Configuration类支持并非旨在成为Spring XML的100%完全替代品。某些工具(如Spring XML命名空间)仍然是配置容器的理想方法。在XML方便或必要的情况下,您可以选择:使用例如ClassPathXmlApplicationContext以“以XML为中心”的方式实例化容器,或者使用AnnotationConfigApplicationContext以“以Java为中心”的方式实例化它。 @ImportResource注释,根据需要导入XML。 以XML为中心使用@Configuration类

最好从XML引导Spring容器,并以ad-hoc方式包含@Configuration类。例如,在使用Spring XML的大型现有代码库中,可以根据需要更轻松地创建@Configuration类,并将其包含在现有XML文件中。在本节的后面部分,我们将介绍在这种“以XML为中心”的情况下使用@Configuration类的选项。 将@Configuration类声明为普通的Spring <bean />元素

请记住,@ Configuration类最终是容器中的bean定义。在本系列示例中,我们创建一个名为AppConfig的@Configuration类,并将其作为<bean />定义包含在system-test-config.xml中。由于<context:annotation-config />已打开,容器会识别@Configuration批注并正确处理AppConfig中声明的@Bean方法。

以下示例显示了Java中的普通配置类:

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}

以下示例显示了示例system-test-config.xml文件的一部分:

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

以下示例显示了可能的jdbc.properties文件:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

在system-test-config.xml文件中,AppConfig 不声明id元素。虽然这样做是可以接受的,但是没有必要,因为没有其他bean引用它,并且不太可能通过名称从容器中明确地获取它。类似地,DataSource bean只是按类型自动装配,因此不严格要求显式的bean id。

Using to pick up @Configuration classes

因为@Configuration是使用@Component进行元注释的,所以@Configuration-annotated类自动成为组件扫描的候选者。使用与前一个示例中描述的相同的方案,我们可以重新定义system-test-config.xml以利用组件扫描。请注意,在这种情况下,我们不需要显式声明<context:annotation-config />,因为<context:component-scan />启用相同的功能。

以下示例显示了已修改的system-test-config.xml文件:

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

@Configuration以类为中心在@ImportResource中使用XML

在@Configuration类是配置容器的主要机制的应用程序中,仍然可能需要使用至少一些XML。在这些场景中,您可以使用@ImportResource并根据需要定义尽可能多的XML。这样做可以实现“以Java为中心”的方法来配置容器并将XML保持在最低限度。以下示例(包括配置类,定义bean的XML文件,属性文件和主类)显示了如何使用@ImportResource批注来实现根据需要使用XML的“以Java为中心”的配置:

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}

properties-config.xml

<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>

jdbc.properties

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}