跳至主要內容

配置-注解

荒流2022年5月22日大约 28 分钟约 8404 字

1. 前言:Annotation or XML?

No matter the choice, annotations or XML, Spring can accommodate both styles and even mix them together. It is worth pointing out that through its JavaConfig option, Spring lets annotations be used in a non-invasive way, without touching the target components source code.

Annotation injection is performed before XML injection. Thus, the XML configuration overrides the annotations for properties wired through both approaches.

Spring 是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替 xml 配置文件可以简化配置,提高开发效率。

不过值得一提的是,注解配置不能完全脱离 XML 配置的存在,XML 需作为注解驱动的一个引子,Spring 仍需通过 XML 来告诉 Spring“我现在要使用注解来进行配置了”,不过这里的 XML 配置量就已经可以忽略不计了。

2. 常用注解速览

Spring “原始注解”与“新注解”主要是一种逻辑上的区分。

2.1 原始注解

使用原始注解进行开发时,需要在 applicationContext.xml 中配置组件扫描,其作用是指定哪个包(及其子包)下的 Bean 需要进行扫描以便识别使用注解配置的类、字段和方法。

<!--配置组件扫描-->
<context:component-scan base-package="com.itheima"></context:component-scan>
原始注解解释
@Component使用在类上,用于实例化 Bean
@Controller@Component 的衍生注解:使用在 Web 层上,用于实例化 Bean
@Service@Component 的衍生注解:使用在 Service 层类上,用于实例化 Bean
@Repository@Component 的衍生注解:使用在 DAO 层类上,用于实例化 Bean
@Autowired通常使用在字段上,用于根据 Bean 的类型进行依赖注入
@Qualifier结合 @Autowired 一起使用用于同时根据类型和名称进行依赖注入
@Resource相当于 @Autowired+@Qualifier,按照名称进行注入
@Value注入普通属性
@Scope标注 Bean 的作用范围
@PostConstruct使用在方法上,标注该方法是 Bean 的初始化方法
@PreDestroy使用在方法上,标注该方法是 Bean 的销毁方法

For @Component, Spring provides further stereotype annotations: @Repository,@Service, and @Controller.

stereotype: 刻板印象的。在 Spring 中用来表示一种可望文生义的注解?

  • @Component is a generic stereotype for any Spring-managed component.
  • @Repository, @Service, and @Controller are specializations of @Component for more specific use cases (in the persistence, service, and presentation layers, respectively).

2.2 新注解

使用原始注解还不能全部替代 xml 配置文件,还需要使用注解替代的配置如下:

新注解解释
@Configuration用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan用于指定 Spring 在初始化容器时要扫描的包,作用和在 Spring 的 xml 配置文件中的<context:component-scan base-package="com.itheima"/>一样
@Bean使用在方法上,表示将该方法的返回值存储到 Spring 容器中,并可赋予指定名称
@PropertySource用于加载 properties 文件中的配置
@Import用于导入其他配置类

以下二者等价:

2.3 复合注解

Spring 提供的很多注解同时也是元注解,所谓元注解指的是该注解能够修饰另一个注解。

比如, @Service 注解即被元注解 @Component 修饰了,故而 @Service 会被当作@Component 来对待:

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

可以组合元注解来创造复合注解,例如,Spring MVC 的@RestController 注解就是由 @Controller@ResponseBody 复合形成的:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    @AliasFor(
        annotation = Controller.class
    )
    String value() default "";
}

复合注解可以结合@AliasFor注解重新声明元注解中的属性,从而可以满足一些定制化的场景,例如,你希望某个元注解的一些属性固定不变,而只暴露某些特定的属性供开发者使用。Spring 的 @SessionScope 注解便是这样一个例子,它硬编码了@ScopescopeName属性为"session",只允许使用者修改其proxyMode属性:

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

}

3. post-processors

As always, you can register the post-processors as individual bean definitions, but they can also be implicitly registered by including the following tag in an XML-based Spring configuration (notice the inclusion of the context namespace):

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

The <context:annotation-config/> element implicitly registers the following post-processors:

<context:annotation-config/> only looks for annotations on beans in the same application context in which it is defined.

The @Autowired, @Inject, @Value, and @Resource annotations are handled by Spring BeanPostProcessor implementations. This means that you cannot apply these annotations within your own BeanPostProcessor or BeanFactoryPostProcessor types (if any). These types must be 'wired up' explicitly by using XML or a Spring @Bean method.

4. 注解逐解

@Required

The @Required annotation and RequiredAnnotationBeanPostProcessor are formally deprecated as of Spring Framework 5.1.

/** @deprecated */
@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Required {
}

The @Required annotation applies to bean property setter methods, as in the following example:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

This annotation indicates that the affected bean property must be populated at configuration time, through an explicit property value in a bean definition or through autowiring.

@Autowired

很多情况下,JSR 330 提供的@Inject注解可以替换@Autowired的作用。

@Autowired根据类型注入 Bean,对应类型的 Bean 需要是单例的。

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

注意,由@Autowired的定义可知,其作用范围为构造器、方法、形参、字段、注解,最常见的字段注入的方式只是其中之一而已。当然了,当用在方法上时,此注解肯定不可能用在静态方法上。

当在方法上使用@Autowired时,spring 会在项目启动的过程中,自动调用一次加了@Autowired注解的方法,我们可以在该方法做一些初始化的工作(如此一来,与@PostConstruct的区别?)

多个 Bean 类型相同

如果对应的类型有多个,@Autowired会失败,此时有三种解决方式:

注入 Bean 集合

@Autowired注入的字段为集合类型时:

关于 required

对于普通方法:

不过,对于构造方法和工厂方法,规则略有不同,因为 Spring 的构造解析算法需要处理存在多个构造方法的场景:

Only one constructor of any given bean class may declare @Autowired with the required attribute set to true, indicating the constructor to autowire when used as a Spring bean.

Alternatively, you can express the non-required nature of a particular dependency through Java 8’s java.util.Optional, as the following example shows:

public class SimpleMovieLister {

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

As of Spring Framework 5.0, you can also use a @Nullable annotation:

public class SimpleMovieLister {

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

注入 Spring 框架相关 Bean

You can also use @Autowired for interfaces that are well-known resolvable dependencies:

These interfaces and their extended interfaces, such as ConfigurableApplicationContext or ResourcePatternResolver, are automatically resolved, with no special setup necessary.

self injection

Note that self injection is a fallback.

自 Spring 4.3 开始,@Autowired 也支持了“自注入(self injection)”,即注入自己。

Regular dependencies on other components always have precedence:

实践中,应该将自注入作为一种最后的不得已的手段(for example, for calling other methods on the same instance through the bean’s transactional proxy),通常更建议将需要用到自注入的方法提取到另外一个委托 Bean 里。

Alternatively, you can use @Resource, which may obtain a proxy back to the current bean by its unique name.

通过@Bean方法来实现自注入也是一种有效的方法。但是,建议要么在实际需要的地方在方法签名中惰性地解析这些引用(与@Autowired正好相反),要么将受影响的@Bean方法声明为静态的,从而将它们与母体及其生命周期解耦。否则,此类 Bean 只会在 fallback phase 被考虑,而优先考虑其他的符合条件的 Bean。

隐式的 qualfier

除了直接使用 @Qualifier 注解来修饰 Bean 外,Spring 也支持借助 Java 的泛型信息来作为一种隐式的修饰语,从而区别不同的 Bean。

例如,假设有如下的配置类:

@Configuration
public class MyConfiguration {

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

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

假定上述的 Bean 都实现了一个泛型接口,即Store<String>Store<Integer>,那么可以直接使用 @Autowire 来注入相应的泛型接口从而注入相应的 Bean,即:

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

失效原因排查

对象未被注入的可能原因:

@Qualifier

package org.springframework.beans.factory.annotation;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
    String value() default "";
}

You can associate qualifier values with specific arguments, narrowing the set of type matches so that a specific bean is chosen for each argument.

In the simplest case, this can be a plain descriptive value, as shown in the following example:

public class MovieRecommender {

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

    // ...
}

You can also specify the @Qualifier annotation on individual constructor arguments or method parameters:

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 的 id 是唯一的,但@Qualfier也始终只是在@Autowired圈定的范围内进行挑选。

Good qualifier values are main or EMEA or persistent, expressing characteristics of a specific component that are independent from the bean id, which may be auto-generated in case of an anonymous bean definition.

@Qualfier的值不需要是唯一的,因此也可以据此来注入 Bean 集合。

当需要根据名称来注入 Bean 时,@Qualfier并不一定是必需的,因为默认情况下,当有多个候选项时(且没有其他限定条件,如优先级),Spring 会根据字段或形参的名称来匹配同名的 Bean。

另外,如果就是为了根据修饰性的名称来注入 Bean,往往更推荐使用 JSR-250 提供的@Resource 注解,@Resource本意即是根据唯一的限定名称来注入 Bean,不考虑 Bean 的类型。

自定义@Qualifier

You can create your own custom qualifier annotations. To do so, define an annotation and provide the @Qualifier annotation within your definition, as the following example shows:

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

Then you can provide the custom qualifier on autowired fields and parameters, as the following example shows:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}

CustomAutowireConfigurer

CustomAutowireConfigurer is a BeanFactoryPostProcessor that lets you register your own custom qualifier annotation types, even if they are not annotated with Spring’s @Qualifier annotation. The following example shows how to use CustomAutowireConfigurer:

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

The AutowireCandidateResolver determines autowire candidates by:

When multiple beans qualify as autowire candidates, the determination of a “primary” is as follows: If exactly one bean definition among the candidates has a primary attribute set to true, it is selected.

@Resource

@Resource takes a name attribute. By default, Spring interprets that value as the bean name to be injected. In other words, it follows by-name semantics

@Autowired 是 Spring 提供的注解,@Resource 是 Java(JSR-250)提供的注解。

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {

    String name() default "";

    String lookup() default "";

    Class<?> type() default java.lang.Object.class;

    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;

    boolean shareable() default true;

    String mappedName() default "";

    String description() default "";
    
    enum AuthenticationType {
            CONTAINER,
            APPLICATION
    }
}

@Order, @Priority, @Primary

这三个注解总的来说都是用来做 bean(注入时?)的排序。

@Value

定义

package org.springframework.beans.factory.annotation;
    
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
    String value();
}

释义

@Value is typically used to inject externalized properties:

@Component
public class MovieRecommender {

    private final String catalog;

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

With the following configuration:

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

And the following application.properties file:

catalog.name=MovieCatalog

In that case, the catalog parameter and field will be equal to the MovieCatalog value.

Spring 提供的内置转换器支持自动处理简单的类型转换(例如 Integer 或 int),同时多个逗号分隔的值可以自动转换为 String 数组,而无需处理。

设置默认值

@Value 也可以设置默认值,如下:

@Component
public class MovieRecommender {

    private final String catalog;

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

使用 SpEL 表达式

When @Value contains a SpEL expression the value will be dynamically computed at runtime as the following example shows:

@Component
public class MovieRecommender {

    private final String catalog;

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

PropertySourcesPlaceholderConfigurer

Spring 默认提供了一个规则宽容的属性值解析器,该解析器在解析属性值时,如果找不到对应的值,会将相应的属性名(例如上例的 ${catalog.name})作为值注入到对应的属性中。

如果想要对不存在的属性值设置一个更严格的规则,则应该声明一个 PropertySourcesPlaceholderConfigurer 的 Bean,如下:

使用@Bean方法来声明这个 Bean 时,必须是 static 方法。

@Configuration
public class AppConfig {

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

上述PropertySourcesPlaceholderConfigurer在发现无法解析到属性值时,会直接报错。此外,其也可以使用 setPlaceholderPrefix, setPlaceholderSuffix, setValueSeparator 方法来定制 placeholders。

Spring Boot configures by default a PropertySourcesPlaceholderConfigurer bean that will get properties from application.properties and application.yml files.

ConversionService

A Spring BeanPostProcessor uses a ConversionService behind the scenes to handle the process for converting the String value in @Value to the target type.

If you want to provide conversion support for your own custom type, you can provide your own ConversionService bean instance as the following example shows:

@Configuration
public class AppConfig {

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

@AliasFor

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface AliasFor {
    @AliasFor("attribute")
    String value() default "";

    @AliasFor("value")
    String attribute() default "";

    Class<? extends Annotation> annotation() default Annotation.class;
}

@AliasFor用于声明注解属性的别名,有两种应用场景:

@AliasFor注解在使用时,如果缺省annotation属性,表示其指向同一个注解中的属性;如果缺省value属性,则表示指向annotation字段关联的注解中的同名属性。

@PostConstruct, @PreDestroy

JSR-250 lifecycle annotations: javax.annotation.PostConstruct and javax.annotation.PreDestroy.

The entire javax.annotation package got separated from the core Java modules in JDK 9 and eventually removed in JDK 11.

If needed, the javax.annotation-api artifact needs to be obtained via Maven Central now, simply to be added to the application’s classpath like any other library.

package javax.annotation;

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}
package javax.annotation;

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
}

@Configuration

注意 @Configuration@Component 修饰了。

@Configuration is a class-level annotation indicating that an object is a source of bean definitions.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

Remember that @Configuration classes are ultimately only another bean in the container: This means that they can take advantage of @Autowired and @Value injection and other features the same as any other bean.

@ComponentScan

释义

package org.springframework.context.annotation;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    String resourcePattern() default "**/*.class";

    boolean useDefaultFilters() default true;

    ComponentScan.Filter[] includeFilters() default {};

    ComponentScan.Filter[] excludeFilters() default {};

    boolean lazyInit() default false;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
}

扫描过滤条件

@ComponentScan 注解的 includeFilters or excludeFilters 属性可以定义细化的包扫描的过滤条件。其中过滤条件的表达使用@Filter注解

Filter TypeExample ExpressionDescription
annotation (default)org.example.SomeAnnotationAn annotation to be present or meta-present at the type level in target components.
assignableorg.example.SomeClassA class (or interface) that the target components are assignable to (extend or implement).
aspectjorg.example..*Service+An AspectJ type expression to be matched by the target components.
regexorg\.example\.Default.*A regex expression to be matched by the target components' class names.
customorg.example.MyTypeFilterA custom implementation of the org.springframework.core.type.TypeFilter interface.

BeanNameGenerator

When a component is autodetected as part of the scanning process, its bean name is generated by the BeanNameGenerator strategy known to that scanner.

package org.springframework.beans.factory.support;

public interface BeanNameGenerator {
    String generateBeanName(BeanDefinition var1, BeanDefinitionRegistry var2);
}

默认情况下,对于自动扫描的 Bean,如果一个 Bean 在声明时没有设置它的名称,Spring 默认的名称生成器会返回首字母小写后的 Bean 类名,如下例分别返回myMovieListermovieFinderImpl:

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

如果不想使用 Spring 的默认实现,可以自定义 Bean 名称的生成规则。步骤如下:

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

ScopeMetadataResolver

To provide a custom strategy for scope resolution rather than relying on the annotation-based approach, you can implement the ScopeMetadataResolver interface.

When using certain non-singleton scopes, it may be necessary to generate proxies for the scoped objects. For this purpose, a scoped-proxy attribute is available on the @Component annotation.

The three possible values are: no, interfaces, and targetClass.

For example, the following configuration results in standard JDK dynamic proxies:

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

提升包扫描的速度

通过建立索引。

While classpath scanning is very fast, it is possible to improve the startup performance of large applications by creating a static list of candidates at compilation time. In this mode, all modules that are targets of component scanning must use this mechanism.

To generate the index, add an additional dependency to each module that contains components that are targets for component scan directives.

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

The spring-context-indexer artifact generates a META-INF/spring.components file that is included in the jar file.

@Bean

释义

@Bean is a method-level annotation and a direct analog of the XML <bean/> element. You can use the @Bean annotation in a @Configuration-annotated or in a @Component-annotated class.

package org.springframework.context.annotation;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    /** @deprecated */
    @Deprecated
    Autowire autowire() default Autowire.NO;

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

如果没有通过@Bean 的 name 或 value 属性来设置 Bean 的名称,默认情况下,@Bean 方法定义的 Bean 的名称就是@Bean 方法的名称。如下两者等价:

@Configuration
public class AppConfig {

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

@Bean也可以修饰接口的默认方法,从而可以让多个配置类实现同一个接口使得代码得到复用:

public interface BaseConfig {

    @Bean
    default TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

@Configuration
public class AppConfig implements BaseConfig {

}

A @Bean-annotated method can have an arbitrary number of parameters that describe the dependencies required to build that bean.

@Bean 定义 Bean 的配置信息

使用@Bean注解,在 Spring 的组件(Components)中也可以定义 Bean 的配置信息,就像在@Configureation 配置类中所做的那样。例如:

@Component
public class FactoryMethodComponent {

    private static int i;
    
    public void doWork() {
        // Component method implementation omitted
    }

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

@Bean标识了创建 Bean 的工厂方法及其他的 Bean 定义属性信息,例如 @Qualifier, @Scope, @Lazy 等。

InjectionPoint

自 Spring 4.3 以后,也可以在工厂方法中声明InjectionPoint类型的参数,从而可以获取该 Bean 在创建过程中的 Inject Point.

Lifecycle callback

@Bean方法定义的 Bean 支持正常的生命周期回调,这里不再过多赘述了。

示例:

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

对于BeanOne,其等同于如下配置:

@Configuration
public class AppConfig {

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

    // ...
}

值得一提的是,@Bean注解的destroyMethod属性提供了一个默认值"(inferred)",如果你不想在 IoC 容器 shutdown 的时候执行相应 Bean 的 close 或 shutdown 方法,需要设置destroyMethod=""

@Configuration vs @Component

同样一个@Bean方法,当其位于常规的@Component类中与位于@Configuration类中时,Spring 的处理方式有所不同:

由上,关于@Bean方法的访问权限:

有时候,分别将定义在@Configuration类中与@Component类中的 @Bean 方法称之为 full 模式lite 模式。通常情况下都应该使用 full 模式,这样能够保证跨方法引用 bean 时实际上是从管理着 bean 的生命周期的 IoC 容器中获取的,而 lite 模式的 @Bean 方法不能声明“inter-bean dependencies”:

static @Bean 方法

@Bean方法也可以声明为static的,当需要定义 post-processor 的 bean 时这一点特别有用,比如 BeanFactoryPostProcessorBeanPostProcessor,因为这样的 bean 是在 IoC 容器生命周期的早期被实例化的,应该避免在那个时候触发 Spring 配置的其他部分。

对静态的@Bean方法的调用永远不会被 IoC 容器拦截到,即便该方法定义在 @Configuration 类中,根本原因是由于 CGLIB 的继承机制意味着只可能覆盖非静态的方法。

同一个类,多个@Bean 方法,返回同一个 Bean

同一个类中可以含有多个返回同一个 Bean 的@Bean方法。

This is the same algorithm as for choosing the “greediest” constructor or factory method in other configuration scenarios: The variant with the largest number of satisfiable dependencies is picked at construction time, analogous to how the container selects between multiple @Autowired constructors.

@Scope

通过@Scope注解可以定义 Bean 的 scope。

@Scope annotations are only introspected on the concrete bean class (for annotated components) or the factory method (for @Bean methods).

package org.springframework.context.annotation;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
    @AliasFor("scopeName")
    String value() default "";

    @AliasFor("value")
    String scopeName() default "";

    /**
	 * Specifies whether a component should be configured as a scoped proxy
	 * and if so, whether the proxy should be interface-based or subclass-based.
	 * <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
	 * that no scoped proxy should be created unless a different default
	 * has been configured at the component-scan instruction level.
	 * <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
     */
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

对于Scope的翻译,我暂且称之为生存域,其作用作如下阐述:

@Import

The @Import annotation allows for loading @Bean definitions from another configuration class, as the following example shows:

@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();
    }
}

Now, rather than needing to specify both ConfigA.class and ConfigB.class when instantiating the context, only ConfigB needs to be supplied explicitly, as the following example shows:

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

As of Spring Framework 4.2, @Import also supports references to regular component classes, analogous to the AnnotationConfigApplicationContext.register method.

Injecting Dependencies on Imported @Bean Definitions:

@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");
}

@DependsOn

此注解可以用来调整 Bean 的初始化顺序。比如如果一个 BeanB 依赖于 BeanA 的初始化,那么可以给 BeanB 加上注解@DependsOn("beanA"),让 BeanA 先于 BeanB 初始化。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DependsOn {
    String[] value() default {};
}

@Conditional

@Conditional的作用是,在向 spring 容器中注册组件时,必须判断某个条件满足时才能注册。其定义为:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

如果一个@Configuration配置类被标识为@Conditional,意味着该配置类所关联的@Bean方法、@Import注解、@ComponentScan注解都会受限于该@Conditional的条件。

关于其中的Condition接口:

package org.springframework.context.annotation;

import org.springframework.core.type.AnnotatedTypeMetadata;

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

@EnableAspectJAutoProxy

此注解作用在配置类上(@Component 中不知道行不行?),用于开启 Spring AOP 的功能。其中两个参数:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;
}

@ControllerAdvice 与 @ExceptionHandler、@InitBinder、@ModelAttribute

@ControllerAdvice 可以看成是 Spring MVC 提供的一个特殊的 controller 拦截器标识注解,能够拦截到所有 @RequestMapping 方法。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] assignableTypes() default {};

    Class<? extends Annotation>[] annotations() default {};
}

@ControllerAdvice通常与@ExceptionHandler@InitBinder@ModelAttribute一起使用,其作用分别为:

@ExceptionHandler

示例代码:

@RestControllerAdvice
public class ControllerExceptionHandler {
    /**
     * 全局异常捕捉处理
     * @ExceptionHandler 用来定义拦截的异常类型,如果为空则是拦截所有异常
     */
    @ResponseBody
    @ExceptionHandler(value = RuntimeException.class)
    public String errorHandler(RuntimeException ex){
        return "出错啦!";
    }
}

@ExceptionHandler源码:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
    Class<? extends Throwable>[] value() default {};
}

@InitBinder

示例代码:

@RestControllerAdvice
public class ControllerInitBinderHandler {
    @InitBinder
    public void handleInitBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
        binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
    }
}

@InitBinder源码:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {
    String[] value() default {};
}

@ModelAttribute

使用示例:

@ControllerAdvice
class ControllerAdviceTest {
    /**
     * 可以拿到 Model,做一些全局处理,比如使全局@RequestMapping 可以获取某些特定的值
     */
    @ModelAttribute
    public void handleModelAttribute(Model model) {
        model.addAttribute("userName", "chuan");
    }
}

@RestController
class TestController {
    @GetMapping("/controllerAdvice/modelAttribute")
    public String testModelAttribute(Model model) {
        return (String) model.getAttribute("userName");
    }
}

注解源码:

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean binding() default true;
}

JSR-330 的注解

Starting with Spring 3.0, Spring offers support for JSR-330 standard annotations (Dependency Injection). To use them, you need to have the relevant jars in your classpath.

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

When you work with standard annotations, you should know that some significant features are not available, as the following table shows:

Springjavax.inject.*javax.inject restrictions / comments
@Autowired@Inject@Inject has no 'required' attribute. Can be used with Java 8’s Optional instead.
@Component@Named / @ManagedBeanJSR-330 does not provide a composable model, only a way to identify named components.
@Scope("singleton")@SingletonThe JSR-330 default scope is like Spring’s prototype. However, in order to keep it consistent with Spring’s general defaults, a JSR-330 bean declared in the Spring container is a singleton by default. In order to use a scope other than singleton, you should use Spring’s @Scope annotation. javax.inject also provides a @Scope annotation. Nevertheless, this one is only intended to be used for creating your own annotations.
@Qualifier@Qualifier / @Namedjavax.inject.Qualifier is just a meta-annotation for building custom qualifiers. Concrete String qualifiers (like Spring’s @Qualifier with a value) can be associated through javax.inject.Named.
@Value-no equivalent
@Required-no equivalent
@Lazy-no equivalent
ObjectFactoryProviderjavax.inject.Provider is a direct alternative to Spring’s ObjectFactory, only with a shorter get() method name. It can also be used in combination with Spring’s @Autowired or with non-annotated constructors and setter methods.