在阅读本文前,需要对Spring中的依赖注入机制有一定了解。本文的内容以代码为主,每个标题对应一种具体的使用案例,文字描述较少,详细内容可查看:Spring–基于注解的IoC容器配置(详细版)
1. 使用@Autowired
下面的代码展示了@Autowired
的多种使用方式。
1.1 构造函数
@Component
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
bean定义
@Configuration
public class BeanConfiguration {
@Bean
public CustomerPreferenceDao customerPreferenceDao() {
return new CustomerPreferenceDao();
}
}
1.2 setter方法
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
bean定义
@Configuration
public class BeanConfiguration {
@Bean
public MovieFinder movieFinder() {
return new MovieFinder();
}
}
1.3 任意名称和多个参数的方法
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
bean定义
@Configuration
public class BeanConfiguration {
@Bean
public CustomerPreferenceDao customerPreferenceDao() {
return new CustomerPreferenceDao();
}
@Bean
public MovieCatalog movieCatalog() {
return new MovieCatalog();
}
}
1.4 属性字段
@Autowired
可以同时存在于字段和构造函数上。
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
1.5 数组、集合类型
通过将@Autowired
注解添加到需要该类型数组的字段或方法上,让Spring从ApplicationContext
中提供特定类型的所有bean:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
这对泛型集合也适用:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
key中存放的是在bean的名称,如果bean通过方法定义,则key为方法名称。
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
1.6 可选依赖
- 默认情况下,当给定注入点没有匹配的候选 bean 时,自动装配会失败。对于声明的数组、集合或map,至少需要一个匹配元素。
@Autowired
中required值默认为true
。你可以更改此行为,通过将@Autowired
中的 required 属性设置为false
,跳过无法满足的注入点:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
或者,也可以通过Java 8的java.util.Optional
来表示特定依赖关系的非必须属性:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从 Spring Framework 5.0 开始,还可以使用 @Nullable
注解:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
1.7 可解析依赖
可以将 @Autowired
用于众所周知的可解析的依赖:BeanFactory
、ApplicationContext
、Environment
、ResourceLoader
、ApplicationEventPublisher
和 MessageSource
。这些接口及其扩展接口(例如 ConfigurableApplicationContext
或 ResourcePatternResolver
)会被自动解析,无需特殊设置。
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
2. 使用@Primary
以下配置将firstMovieCatalog
定义为primaryMovieCatalog
类型:
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
通过上面的配置,MovieReecommender
将使用firstMovieCatalog
来装配:
@Component
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
3. 使用Qualifiers
3.1 @Qualifier注解
可以将qualifier的值与特定参数相关联,从而缩小类型匹配集的范围,以便为每个参数选择特定的 bean。
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
可以在单独的构造器参数或setter方法参数上使用@Qualifier
注解,如下例所示:
@Component
public class MovieRecommender {
private final MovieCatalog movieCatalog;
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
下面的代码展示了对应的bean定义:
@Configuration
public class BeanConfiguration {
@Bean
public CustomerPreferenceDao customerPreferenceDao() {
return new CustomerPreferenceDao();
}
@Bean
@Qualifier("main")
public MovieCatalog movieCatalog() {
return new MovieCatalog();
}
}
如果匹配失败,bean的名称被视为默认qualifier值。
qualifier也适用于泛型集合,例如 Set<MovieCatalog>
。在这种情况下,根据声明的qualifier,所有匹配的 bean 都会作为集合注入。这意味着qualifier不必是唯一的。相反,它们构成了过滤标准。例如,你可以使用相同的qualifier值“action”定义多个 MovieCatalog
bean,所有这些 bean 都被注入到使用 @Qualifier("action")
注解的 Set<MovieCatalog>
中。
3.2 自定义Qualifier注解
你可以创建自定义的qualifier注解。为此,请定义一个注解并在定义中提供 @Qualifier
注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后,你可以在自动装配字段和参数上提供自定义qualifier,如下例所示:
@Component
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
bean定义代码如下:
@Configuration
public class BeanConfiguration {
@Bean
@Genre("action")
public MovieCatalog actionCatalog() {
MovieCatalog movieCatalog = new MovieCatalog();
movieCatalog.setName("action");
return movieCatalog;
}
@Bean
@Genre("comedy")
public MovieCatalog comedyCatalog() {
MovieCatalog movieCatalog = new MovieCatalog();
movieCatalog.setName("comedy");
return movieCatalog;
}
}
a. 无属性注解
在某些情况下,使用不带值的注解就足够了。当注解服务于更通用的目的,并且可以应用于多种不同类型的依赖关系时,这可能很有用。例如,你可以提供一个离线目录,可以在没有可用 Internet 连接时进行搜索。首先,定义简单的注解,如下例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后将注解添加到要自动装配的字段或属性中:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
以下是bean定义:
@Configuration
public class BeanConfiguration {
@Bean
@Offline
public MovieCatalog offlineCatalog() {
MovieCatalog movieCatalog = new MovieCatalog();
movieCatalog.setName("offline");
return movieCatalog;
}
}
b. 多属性注解
除了简单的value属性,还可以定义命名的属性。如果之后在要自动装配的字段或参数上指定了多个属性值,则 Bean 定义必须与所有属性值匹配才能被视为自动装配的候选者。
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
Format 是一个枚举,定义如下:
public enum Format {
VHS, DVD, BLURAY
}
要自动装配的字段使用自定义qualifier进行注解,并包含两个属性的值:genre和format,如下例所示:
@Component
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
最后,bean 定义中应该包含匹配的qualifier值:
@Configuration
public class BeanConfiguration {
@Bean
@MovieQualifier(format=Format.VHS, genre="Action")
public MovieCatalog actionVhsCatalog() {
MovieCatalog movieCatalog = new MovieCatalog();
movieCatalog.setName("actionVhsCatalog");
return movieCatalog;
}
@Bean
@MovieQualifier(format=Format.VHS, genre="Comedy")
public MovieCatalog comedyVhsCatalog() {
MovieCatalog movieCatalog = new MovieCatalog();
movieCatalog.setName("comedyVhsCatalog");
return movieCatalog;
}
@Bean
@MovieQualifier(format=Format.DVD, genre="Action")
public MovieCatalog actionDvdCatalog() {
MovieCatalog movieCatalog = new MovieCatalog();
movieCatalog.setName("actionDvdCatalog");
return movieCatalog;
}
@Bean
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
public MovieCatalog comedyBlurayCatalog() {
MovieCatalog movieCatalog = new MovieCatalog();
movieCatalog.setName("comedyBlurayCatalog");
return movieCatalog;
}
}
4. 泛型作为Qualifiers
除了 @Qualifier
注解之外,还可以使用 Java 泛型作为隐式的Qualifier。
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
假设上面的 beans 实现了通用接口(即 Store<String>
和 Store<Integer>
):
public class StringStore implements Store<String> {
}
public class IntegerStore implements Store<Integer> {
}
你可以 @Autowire
Store 接口,泛型将作为qualifier:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
qualifier也适用于自动装配列表、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;
5. 使用CustomAutowireConfigurer
CustomAutowireConfigurer
是一个 BeanFactoryPostProcessor
,它允许你注册自定义的qualifier注解类型,即使它们没有使用 Spring 的 @Qualifier
注解。以下示例展示如何使用 CustomAutowireConfigurer
:
- 定义一个注解
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomQualifier {
String value();
}
- 将该注解注册为qualifier注解
@Configuration
public class MyConfiguration {
@Bean
public CustomAutowireConfigurer customAutowireConfigurer() {
CustomAutowireConfigurer customConfigurer = new CustomAutowireConfigurer();
customConfigurer.setCustomQualifierTypes(Collections.singleton(CustomQualifier.class));
return customConfigurer;
}
}
以上代码定义了一个名为@CustomQualifier
的qualifier注解。
假设有一个Driver
接口有两个实现类NetworkDriver
和MemoryDriver
:
@Component
@CustomQualifier("net")
public class NetworkDriver implements Driver {
// ...
}
@Component
@CustomQualifier("mem")
public class MemoryDriver implements Driver {
// ...
}
在需要注入Driver
实例的位置,可以使用@Autowired
注解和自定义的qualifier进行装配,下面代码中注入的实例将是NetworkDriver
类型:
@Autowired
@CustomQualifier("net")
private Driver driver;
6. 使用@Resource
@Resource
通过名称实现依赖注入。默认情况下,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;
}
}
使用@Resource
时,如果没有指定名称,@Resource
将通过类型进行匹配,而不是特定名称。@Resource
也会解析可解析的依赖,包括以下接口:BeanFactory
、ApplicationContext
、ResourceLoader
、ApplicationEventPublisher
和 MessageSource
,这与@Autowired
类似。
下面这个例子中,customerPreferenceDao
字段首先寻找一个名为customerPreferenceDao的bean,然后寻找能和CustomerPreferenceDao
相匹配的primary类型:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
7. 使用@Value
@Value
通常被用来注入外部属性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
使用以下配置:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
和对应的配置文件application.properties
:
catalog.name=MovieCatalog
在这个例子中,catalog
参数和字段的值将等于MovieCatalog(配置文件中定义的值)
a. 值解析器配置
Spring 提供了默认的内置值解析器,解析规则较为宽松。它尝试解析属性值,如果无法解析,则将注入属性名称(例如 ${catalog.name}
)作为值。如果你想对不存在的值保持严格控制,你应该声明一个 PropertySourcesPlaceholderConfigurer
bean,如下例所示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
使用 JavaConfig 配置
PropertySourcesPlaceholderConfigurer
时,@Bean 方法必须是静态的。
如果任何 ${}
占位符无法解析,使用上述配置可确保 Spring 初始化失败。还可以使用 setPlaceholderPrefix
、setPlaceholderSuffix
或 setValueSeparator
等方法来自定义占位符。
Spring Boot 默认配置了一个
PropertySourcesPlaceholderConfigurer
bean,它会从application.properties
和application.yml
文件获取属性。
b. 设置默认值
可以为属性提供默认值,如下例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
c. 自定义类型转换
Spring 提供的内置转换器可以自动处理简单的类型转换(例如,转换到 Integer 或 int)。多个逗号分隔值可以自动转换为字符串数组,无需额外的代码。
Spring BeanPostProcessor
在后台使用 ConversionService
来将 @Value
中的 String
值转换为目标类型。如果你想为你自定义的类型提供转换支持,你可以提供你自己的 ConversionService
bean 实例,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
d. 使用SpEL表达式
当@Value
包含SpEL
表达式 时,其中的值将会在运行时动态计算获得,如下例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
SpEL 还允许使用更复杂的数据结构:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
8. 使用@PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor
不仅可以识别@Resource
注解,还支持JSR-250生命周期注解:jakarta.annotation.PostConstruct
和jakarta.annotation.PreDestroy
。Spring2.5提供了对这些注解的支持,为 initialization callbacks 和 destruction callbacks中描述的生命周期回调机制提供了替代方案。只要在Spring ApplicationContext
中注册了CommonAnnotationBeanPostProcessor
,拥有这些注解(@PostConstruct
和@PreDestroy
)的方法将作为接口方法或显示声明的回调方法,在Spring生命周期对应的时间点上被调用。
在下面的示例中,cache在初始化后预先填充数据,并在销毁时清除数据:
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中删除。从 Jakarta EE 9 开始,该包现在位于 jakarta.annotation 中。如果要使用,现在需通过 Maven Central 获取 jakarta.annotation-api 工件,只需像任何其他库一样将其添加到应用程序的类路径中即可。