基础知识
JSR 250: Common Annotations for the JavaTM Platform
JSR 330: Dependency Injection for Java
JSR 305: Annotations for Software Defect Detection
本文讲解的是 Spring 基于注解的 Bean 装载,XML 形式的配置与 Annotation 形式的配置实现功能都是一样的,且可以混用,但是要注意的是 Annotation 先于 XML 执行,注解的配置可能会被 XML 覆盖。
装配(autowiring ) 约等于 注入(injection )
自动装配的模式
关键字
- @Autowired
- -@Primary-
- -@Required---formally deprecated as of Spring Framework 5.1
- @Qualifier
- -@Nullable-
JSR-250
- @Resource
- @PostConstruct
- @PreDestroy
- @ManagedBean
JSR-330
- @Inject
- @Named
对比结论
使用实例一、多数据源配置
public class JPAMongoConfigBizLog {
@Autowired
@Qualifier("bizLogMongoProperties")
private MongoProperties bizLogMongoProperties;
@Bean(name = "bizLogMongo")
@Qualifier("bizLogMongo")
public MongoTemplate bizLogMongoTemplate() {
return new MongoTemplate(bizLogFactory(this.bizLogMongoProperties));
}
@Bean
public MongoDbFactory bizLogFactory(MongoProperties bizLogMongoProperties) {
MongoClientURI mongoClientURI = new MongoClientURI(bizLogMongoProperties.getUri());
return new SimpleMongoDbFactory(new MongoClient(mongoClientURI), bizLogMongoProperties.getDatabase());
}
}
public class JPAMongoConfigMain {
@Autowired
@Qualifier("mainMongoProperties")
private MongoProperties mainMongoProperties;
@Bean(name = "mainMongo")
@Qualifier("mainMongo")
@Primary
public MongoTemplate mainMongoTemplate() {
return new MongoTemplate(mainFactory(this.mainMongoProperties));
}
@Primary
@Bean
public MongoDbFactory mainFactory(MongoProperties mainMongoProperties) {
MongoClientURI mongoClientURI = new MongoClientURI(mainMongoProperties.getUri());
return new SimpleMongoDbFactory(new MongoClient(mongoClientURI), mainMongoProperties.getDatabase());
}
}
方式一、
@Autowired
@Qualifier("bizLogMongo")--------必须要指定Qualifier,不指定默认获取到的是 mainMongo,因为 mainMongo 使用了 @Primary
MongoTemplate bizLogMongo;----bizLogMongo这个名字不影响
方式二、
@Resource
MongoTemplate bizLogMongo;
方式三、
@Resource(nbame="bizLogMongo")
MongoTemplate mongo;
使用实例一、多个实现类
package com.example.service;
public interface DemoService {
}
package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class ADemoServiceImpl implements DemoService {
}
package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class BDemoServiceImpl implements DemoService {
}
下面两种模式OK
@Resource
DemoService ADemoServiceImpl;
@Resource(name="ADemoServiceImpl")
DemoService demo;
@Resource
DemoService demo;
会报异常:Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.service.DemoService' available: expected single matching bean but found 2: ADemoServiceImpl,BDemoServiceImpl
原因是 @Resource 默认按 name 找 demo 这个 bean,没找到,然后按类型找发现有两个实现类,报错。
使用实例一、单个实例
@Bean
public RedisTemplate<String, String> stringRedisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
四种模式都可以
@Resource
StringRedisTemplate redis;
@Autowired
StringRedisTemplate redis;
@Resource(name = "stringRedisTemplate")
RedisTemplate redis;
@Resource
RedisTemplate stringRedisTemplate;
到底应该用哪一种?
给出一个很官方的回答,都可以用,至于选择哪一种或者还是混用,根据团队技术栈合理选择即可。
相信大部分还是在使用 @Autowired 和 @Resource ,能完成工作就好。
@Required
在注入的时候发现没有 Bean 可填充的时候将抛出异常。
该注解在 Spring Framework 5.1 版本中已经被正式声明为弃用 Deprecated
Spring 也推荐使用 Autowired 的 required 属性。
@Autowired
使用 Spring 时候,装载 Bean 最常用的注解,JSR-330 的 @Inject 注解可以替代 @Autowired。
@Autowired 可以作用于 属性, setter 方法, 构造方法。
属性
@Autowired
private MovieCatalog movieCatalog;
setter 方法
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
构造方法
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
用于对个参数装配也是没有问题的
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
属性与构造方法混用也是没问题的
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
当然也支持数组和集合形式的注入
@Autowired
private MovieCatalog[] movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
可以使用 @Order 或者 @Priority 实现排序,如果没有指定,就按照 Bean 注册的顺序或者是容器中定义的顺序排序。
默认情况下,@Autowired 注解在装配时候没有找到对应的 Bean 会失败。如果要允许启动时候不直接装载,可以将 required 属性设置为 false
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
另一种变通的方式是使用 Java 8 的 java.util.Optional
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
Spring Framework 5.0 以上的版本中,还可以使用 @Nullable (javax.annotation.Nullable 来自于 JSR-305)
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
@Autowired 甚至还可以装配 Spring 的一些核心组件,且不需要任何额外的设置。
BeanFactory
ApplicationContext
Environment
ResourceLoader
ApplicationEventPublisher
MessageSource
ConfigurableApplicationContext
ResourcePatternResolver
核心常用组件
@Autowired
private ApplicationContext context;
由于 Spring 的 Autowired 使用的是基于类型的组装,那么我们就会遇到同时存在两个 Bean 的情况。Spring 为我们了提供了几种方案来处理。
@Primary
当存在多个候选 Bean 且只要装配一个的时候,使用 @Primary 的 Bean 成为优先装配的 Bean。
下面定义了两个 MovieCatalog 对象,firstMovieCatalog 被标志为 @Primary
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
下面的使用实例中 movieCatalog 就是 firstMovieCatalog
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
上面的例子还会引发另一个问题,就是当我们希望 movieCatalog 是 secondMovieCatalog 的时候,我们应该怎么处理呢?Spring 提供了另一个注解 Qualifiers
@Qualifiers
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
如果我们没有指定 Bean 的 Qualifier,Spring 默认使用对象的名字为 Qualifier。Autowired 从根本上来说是按 type 装配的,虽然我们可以指定 Qualifier 或者用默认的 Bean 的名字来区分,但是从语义上来说并不能保证 Bean 的唯一性。所以在起名字的时候,最好是能明确标志 Bean 的实际意义的名字,而不是仅仅是一个 id 这样的简单名字。
Qualifier 也可以作用于集合类型,这意味着限定符不是惟一的。
你可以用 “action” 定义多个 MovieCatalog,所有的 action 都被注入到
@Qualifier("action")
Set<MovieCatalog>
注意: 说到这里可能大家也看到问题。虽然使用 Autowired 和 Qualifier 这个组合也能在一定程度上解决多个 Bean 的识别问题,但是 Qualifier 并不保证唯一。 所以在有多个同 type 的 Bean 的时候,我们尽量不要使用 Autowired,我们可以使用 JSR-250 @Resource
@Resource
@Resource (JSR-250 javax.annotation.Resource)可以用他的 name 属性来唯一标识一个目标 Bean。而声明的类型判断反而是其次的。所以说 @Resource 是 byName 来注入的。
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
如果没有指定 name 属性,@Resource 使用属性或者是参数名作为默认名称查找 Bean。
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
@Resource 在没有明确指定 name 的情况下,跟 @Autowired 类似,也会在没有找到默认名字 bean 的情况下,根据类型去查找。
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
customerPreferenceDao 会按 name 查找。
context 会按 ApplicationContext 类型查找。
@PostConstruct 和 @PreDestroy
@PostConstruct (javax.annotation.PostConstruct)和 @PreDestroy(javax.annotation.PreDestroy)是 JSR-250 关于生命周期的另外两个注解。
从 Spring 3.0 开始,Spring 支持 JSR-330 standard annotations (Dependency Injection) 。但是需要自己加入到 jar 包依赖。
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
你可以使用 @javax.inject.Inject 替代 @Autowired。
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
@Inject 跟 @Autowired 一样,也可以作用于属性, setter函数,构造函数,还可以配合 Provider 使用。
private Provider<MovieFinder> movieFinder;
@Inject
public void setMovieFinder(Provider<MovieFinder> movieFinder) {
this.movieFinder = movieFinder;
}
当然,你可以配合 @Named 使用。
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
@Inject 还可以配合 java.util.Optional 或者 @Nullable 使用,但是 @Inject 不再支持 required 属性。
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
@Named 和 @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;
}
// ...
}
要使用 @Named 或者 @ManagedBean,也需要加入自动扫描。
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
}
JSR-330 注解的局限性
Spring组件模型元素 VS JSR-330 变体