配置-注解
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 配置文件,还需要使用注解替代的配置如下:
- 非自定义的 Bean 的配置:
<bean>
- 加载 properties 文件的配置:
<context:property-placeholder>
- 组件扫描的配置:
<context:component-scan>
- 引入其他文件:
<import>
新注解 | 解释 |
---|---|
@Configuration | 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解 |
@ComponentScan | 用于指定 Spring 在初始化容器时要扫描的包,作用和在 Spring 的 xml 配置文件中的<context:component-scan base-package="com.itheima"/> 一样 |
@Bean | 使用在方法上,表示将该方法的返回值存储到 Spring 容器中,并可赋予指定名称 |
@PropertySource | 用于加载 properties 文件中的配置 |
@Import | 用于导入其他配置类 |
以下二者等价:
注解配置:
@Configuration @ComponentScan(basePackages = "org.example") public class AppConfig { // ... }
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>
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
注解便是这样一个例子,它硬编码了@Scope
的scopeName
属性为"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:
ConfigurationClassPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
EventListenerMethodProcessor
<context:annotation-config/>
only looks for annotations on beans in the same application context in which it is defined.
- This means that, if you put
<context:annotation-config/>
in aWebApplicationContext
for aDispatcherServlet
, it only checks for@Autowired
beans in your controllers, and not your services. - See The DispatcherServlet for more information.
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 andRequiredAnnotationBeanPostProcessor
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
会失败,此时有三种解决方式:
与
@Qualifier
(合格者、修饰词)结合,在同类型的基础上通过名称再进行取舍;在被注入的 Bean 上加入优先级,如指定某一个 Bean 为
@Primary
;另外,实际上可以将注入的字段改为集合类型:
当集合为 Map 时,其 key 必须为 String 类型,含义为 Bean 的名字;其 value 为对应的 Bean 实例。
@Service public class UserService { @Autowired private List<IUser> userList; @Autowired private Set<IUser> userSet; @Autowired private Map<String, IUser> userMap; public void test() { // userList:[User1@2513a118, User2@2bfb583b] System.out.println("userList:" + userList); // userSet:[User1@2513a118, User2@2bfb583b] System.out.println("userSet:" + userSet); // {user1=User1@2513a118, user2=User2@2bfb583b} System.out.println("userMap:" + userMap); } }
注入 Bean 集合
@Autowired
注入的字段为集合类型时:
- 集合不能为空,其中必须至少能注入一个 Bean,否则 Spring 将抛异常。
- 如果想控制集合中 Bean 出现的顺序,可以令相关的 Bean 实现
Ordered
接口,或通过@Order, @Priority
注解来定义相对的顺序。 - 如果没有显式的顺序定义,其排列顺序将按照在 IoC 容器中注册的顺序。
关于 required
对于普通方法:
- 如果
@Autowired
修饰的是方法且@Autowired(required = false)
,当 Spring 发现存在某一个要注入的 Bean 不存在时,会导致该方法完全不会被执行。
不过,对于构造方法和工厂方法,规则略有不同,因为 Spring 的构造解析算法需要处理存在多个构造方法的场景:
- 默认情况下,构造方法和工厂方法的参数都是必须存在的;
- 但在只存在单个构造函数的场景中有一些特殊规则,例如,如果没有匹配的 bean 可用,则会将相应的“多元素注入点”(数组、集合、映射)解析为空对象。
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.
- If multiple constructors declare the annotation, they will all have to declare
required=false
in order to be considered as candidates for autowiring.- The constructor with the greatest number of dependencies that can be satisfied by matching beans in the Spring container will be chosen.
- If none of the candidates can be satisfied, then a primary/default constructor (if present) will be used.
- Similarly, if a class declares multiple constructors but none of them is annotated with
@Autowired
, then a primary/default constructor (if present) will be used. If a class only declares a single constructor to begin with, it will always be used, even if not annotated. - Note that an annotated constructor does not have to be
public
.
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:
BeanFactory
ApplicationContext
Environment
ResourceLoader
ApplicationEventPublisher
MessageSource
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:
- In that sense, self references do not participate in regular candidate selection and are therefore in particular never primary.
- On the contrary, they always end up as lowest 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;
失效原因排查
对象未被注入的可能原因:
- 使用
@Autowired
的类不在 IoC 容器中(没有加@Componet
,@Controller
之类的注解) - 包未被 spring 扫描
@Autowired
的required = false
.- 在
listener
和filter
里面@Autowired
某个 bean:由于 web 应用启动的顺序是:listener
->filter
->servlet
,而 SpringMVC 的启动是在DisptachServlet
里面做的,执行在对应的 bean 还没有初始化,无法自动装配。此时可以通过其他途径来实现。 - 循环依赖问题(在单例情况下多数没问题)
@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
orEMEA
orpersistent
, expressing characteristics of a specific component that are independent from the beanid
, 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:
- The
autowire-candidate
value of each bean definition - Any
default-autowire-candidates
patterns available on the<beans/>
element - The presence of
@Qualifier
annotations and any custom annotations registered with theCustomAutowireConfigurer
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)提供的注解。
- @Autowired 只包含一个参数:required,表示是否开启自动准入,默认是 true;而@Resource 包含七个参数,其中最重要的两个参数是:name 和 type。
- @Resource 只能用在类、成员变量和方法上。
- @Resource 的搜索路径:
- 未给定任何参数:首先根据名称匹配(即字段名或 setter 方法名),如果找不到再根据类型匹配
- 仅指定了 name:根据名称装配
- 仅指定了 type:根据类型装配
- 同时指定了 name 和 type:寻找名称和类型都匹配的 Bean 进行装配
@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(注入时?)的排序。
@Order
是 Spring 提供的注解,里面存储了一个代表顺序的值,默认为Integer.MAX_VALUE
,值越小优先级越高。@Order
只控制 Bean 的注入顺序,不影响单例 Bean 的启动顺序,后者是由依赖关系和@DependsOn
声明决定的。@Order
能控制 List 等有序集合里面存放的 Bean 的顺序,因为 Spring 的DefaultListableBeanFactory
类会在注入时调用AnnotationAwareOrderComparator.sort(listA)
,该方法根据@Order
或者Ordered
接口返回的值排序。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Documented public @interface Order { int value() default 2147483647; }
@Priority
是 JSR 250 标准,与@Order
类似,同样接收一个表示顺序的值,值越小优先级越高。@Priority
优先级比@Order
更高,两者共存时优先加载@Priority
。@Target({TYPE,PARAMETER}) @Retention(RUNTIME) @Documented public @interface Priority { /** * The priority value. */ int value(); }
Note that the standard
@Priority
annotation is not available at the@Bean
level, since it cannot be declared on methods. Its semantics can be modeled through@Order
values in combination with@Primary
on a single bean for each type.@Primary
同样是表达顺序的注解,它是 Spring 提供的注解,其表达的优先级最高的,如果同时有@Primary
以及其他几个的话,@Primary
注解的 Bean 会优先注入。@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Primary { }
@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 fromapplication.properties
andapplication.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
注解的定义源码本身便是这样一个例子,其属性value
和attribute
互为别名- 也就是说,
@AliasFor(value="xxx")
和@AliasFor(attribute=“xxx”)
是等价的 - 这种用法有几点注意事项:
- 别名属性必须声明相同的返回类型
- 别名属性必须声明默认值且默认值必须相同
“继承的”元注解中的属性的别名
元注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface BaseAnt { int value(); String home() default ""; }
组合注解
@BaseAnt(17) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface CrossAntAliasTester { @AliasFor(value = "home", annotation = BaseAnt.class) // 需声明对应的注解类型 String address(); }
@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 Type | Example Expression | Description |
---|---|---|
annotation (default) | org.example.SomeAnnotation | An annotation to be present or meta-present at the type level in target components. |
assignable | org.example.SomeClass | A class (or interface) that the target components are assignable to (extend or implement). |
aspectj | org.example..*Service+ | An AspectJ type expression to be matched by the target components. |
regex | org\.example\.Default.* | A regex expression to be matched by the target components' class names. |
custom | org.example.MyTypeFilter | A custom implementation of the org.springframework.core.type.TypeFilter interface. |
The following example shows the configuration ignoring all
@Repository
annotations and using “stub” repositories instead:@Configuration @ComponentScan(basePackages = "org.example", includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"), excludeFilters = @Filter(Repository.class)) public class AppConfig { // ... }
You can also disable the default filters by setting
useDefaultFilters=false
on the annotation. This effectively disables automatic detection of classes annotated or meta-annotated with@Component
,@Repository
,@Service
,@Controller
,@RestController
, or@Configuration
.
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 类名,如下例分别返回myMovieLister
和 movieFinderImpl
:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果不想使用 Spring 的默认实现,可以自定义 Bean 名称的生成规则。步骤如下:
- 实现
BeanNameGenerator
接口,并保证至少有一个无参构造方法; - 在
@ComponentScan
注解中配置上nameGenerator
属性。
@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.
Be sure to include a default no-arg constructor.
Then you can provide the fully qualified class name when configuring the scanner, as the following:
@Configuration @ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class) public class AppConfig { // ... }
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
, andtargetClass
.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.
Note that this applies only to the actual creation of bean instances, not to the injection of existing instances.
As a consequence, this feature makes most sense for beans of prototype scope.
@Component public class FactoryMethodComponent { @Bean @Scope("prototype") public TestBean prototypeInstance(InjectionPoint injectionPoint) { return new TestBean("prototypeInstance for " + injectionPoint.getMember()); } }
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 的处理方式有所不同:
- 核心的不同之处在于
@Component
类没有使用 CGLIB 进行增强以拦截方法和字段的调用。 - CGLIB proxying is the means by which invoking methods or fields within
@Bean
methods in@Configuration
classes creates bean metadata references to collaborating objects. - In contrast, invoking a method or field in a
@Bean
method within a plain@Component
class has standard Java semantics, with no special CGLIB processing or other constraints applying.
由上,关于@Bean
方法的访问权限:
- 在
@Configuration
类中,@Bean
方法必须是“可以被覆盖的”,即它们不能是private
或final
的。 - 在
@Component
类中,@Bean
方法的访问权限没有限制。
有时候,分别将定义在@Configuration
类中与@Component
类中的 @Bean
方法称之为 full 模式与 lite 模式。通常情况下都应该使用 full 模式,这样能够保证跨方法引用 bean 时实际上是从管理着 bean 的生命周期的 IoC 容器中获取的,而 lite 模式的 @Bean
方法不能声明“inter-bean dependencies”:
- Such a
@Bean
method should therefore not invoke other@Bean
methods. Each such method is literally only a factory method for a particular bean reference, without any special runtime semantics. - The positive side-effect here is that no CGLIB subclassing has to be applied at runtime, so there are no limitations in terms of class design (that is, the containing class may be
final
and so forth).
static @Bean 方法
@Bean
方法也可以声明为static
的,当需要定义 post-processor 的 bean 时这一点特别有用,比如 BeanFactoryPostProcessor
和 BeanPostProcessor
,因为这样的 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
的翻译,我暂且称之为生存域,其作用作如下阐述:
scopeName
:当其为singleton
和prototype
的时候自不必多言,当它为request
的时候,表示其修饰的 Bean 的生命周期被限定在一次 HTTP 请求中,也就是说,每当有一个 HTTP 请求打进来,会创造一个属于该次请求的 bean 实例,在该请求中该 bean 会触发一次完整的生命周期——从被创建到被销毁。proxyMode
:限定一个 Bean 是否需要被 Spring 产生其代理。为什么会有这种场景存在?- 假设在一个
singleton
的 bean 中注入了request
域的 bean,由于singleton
的 bean 在项目启动过程中即会随着 IoC 容器一起创建,其依赖的request
域的 bean 自然也需要一起被创建,然后request
又限定了该 bean 只能随着 HTTP 请求被创建,那总不能让项目启动的时候就报错吧? - 因此,Spring 通过提供一个代理,来避免项目启动阶段的报错问题:先注入一个代理的 bean,当接收到实际的 HTTP 请求而调用
request
bean 时,代理对象内部实际执行的会是原目标对象的 bean。
- 假设在一个
@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);
}
Conditions
are checked immediately before the bean-definition is due to be registered and are free to veto registration based on any criteria that can be determined at that point.Conditions
must follow the same restrictions asBeanFactoryPostProcessor
and take care to never interact with bean instances. For more fine-grained control of conditions that interact with@Configuration
beans consider implementing theConfigurationCondition
interface.
@EnableAspectJAutoProxy
此注解作用在配置类上(@Component
中不知道行不行?),用于开启 Spring AOP 的功能。其中两个参数:
proxyTargetClass
:控制 AOP 的具体实现机制。false
:如果一个 Bean 有实现的接口,则使用 JDK 动态代理;如果一个 Bean 没有实现任何接口,则使用 CGLIB 动态代理;true
:强制所有 Bean 都使用 CGLIB 动态代理。
exposeProxy
:是否暴露代理对象,当为true
时,在某个目标对象 Bean 中可通过AopContext.currentProxy()
获取当前目标对象在被代理后产生的代理对象。
@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
:为所有 controller 封装统一异常处理代码@ModelAttribute
:为所有 controller 设置全局变量@InitBinder
:为所有 controller 设置某个类型的数据转换器
@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:
Spring | javax.inject.* | javax.inject restrictions / comments |
---|---|---|
@Autowired | @Inject | @Inject has no 'required' attribute. Can be used with Java 8’s Optional instead. |
@Component | @Named / @ManagedBean | JSR-330 does not provide a composable model, only a way to identify named components. |
@Scope("singleton") | @Singleton | The 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 / @Named | javax.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 |
ObjectFactory | Provider | javax.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. |