Spring 中 Bean 的装配(注入)Autowired Resource Inject 三种模式对比
基础知识
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 覆盖。
Annotation injection is performed before XML injection. Thus, the XML configuration overrides the annotations for properties wired through both approaches.
装配(autowiring ) 约等于 注入(injection )
自动装配的模式
模式 | 描述 |
---|---|
no | (默认) 不自动装配。 Bean 的引用关系必须考定义 ref 元素来完成。 |
byName | 根据属性的 name 自动装配。Spring 根据属性 name 查找同名的 bean 进行装配。 如果使用by name 模式,一个属性被定义为master , Spring 查找名为 master 的 bean 并赋值给该属性。 |
byType | 如果只有一个明确与属性类型一致的 bean 在 container 中存在,就会为属性自动装配。 如果有多个存在,将会抛出一个 fatal exception ,这意味着你不能使用 byType 来自动装配这个 bean。 如果没有找到匹配的 bean, 反而什么都不会发生,属性不会被赋值。 |
constructor | 与 byType 类似,但是仅作用于构造函数的参数。 如果容器中没有与构造函数中参数类型一样的 bean 存在,会产生一个 fatal error。 |
关键字
忽略下面的 “-”,会被超链接。
- @Autowired
- -@Primary-
- -@Required---formally deprecated as of Spring Framework 5.1
- @Qualifier
- -@Nullable-
JSR-250
- @Resource
- @PostConstruct
- @PreDestroy
- @ManagedBean
JSR-330
- @Inject
- @Named
对比结论
情景\注解 | @Autowired | @Resource | @Inject |
---|---|---|---|
模式 | byType | byName,找不到用类型找 | byType |
空 | @Nullable Optional | -- | @Nullable Optional 配合 Provider 使用 |
没有匹配的 Bean | 不报异常 | 报异常 | 不报异常 |
有多个匹配的 Bean | 报异常 | 报异常 | 报异常 |
多个匹配的 Bean 指定唯一 | @Primary | @Primary | @Primary |
其他 | 可以配合 @Qualifier 使用,但是 @Qualifier 不保证唯一 | -- | 指定 name 属性,指定后不会按类型找可以配合 @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 ,能完成工作就好。
后面的详细讲解大多数来自 Spring 5.1.6.RELEASE 官方文档的翻译和实例
@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) { ... }
注意:虽然 Spring 提供了这么多方式来支持不在启动时注入,但是 Spring 还是推荐启动时就注入成功。 We still recommend that you put assertions into the bean class itself (for example, into an init method). Doing so enforces those required references and values even when you use the class outside of a container.
@Autowired 甚至还可以装配 Spring 的一些核心组件,且不需要任何额外的设置。
-
BeanFactory
-
ApplicationContext
-
Environment
-
ResourceLoader
-
ApplicationEventPublisher
-
MessageSource
-
ConfigurableApplicationContext
-
ResourcePatternResolver
核心常用组件 @Autowired private ApplicationContext context;
注意:@Autowired, @Inject, @Value 和 @Resource 注解都是由 Spring BeanPostProcessor 的实现类处理的。这意味着我们不能在 BeanPostProcessor 或者 BeanFactoryPostProcessor 这样的类型上使用上述注解。这样的类型必须用 XML 或者是 Spring @Bean 来装配。
由于 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
TODO:关于如何扩展 Qualifier,如果使用自定义 Qualifier,以及 Qualifier 与泛型的结合使用等内容先不在这里讲解。
@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 关于生命周期的另外两个注解。
@Resource,@PostConstruct,@PreDestroy 这三个注解都是 JDK 6 到 8 标准 Java libraries 中的类型。但是,整个 javax.annotation package 已经从 JDK 9 开始,从 core Java modules 中分离出来,在 JDK 11 中已经被永久移除。如果需要,你可以从 Maven Central 中获取 javax.annotation-api 并加入到项目的 jar 包依赖中。
从 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 变体
Spring | javax.inject.* | javax.inject restrictions / comments |
---|---|---|
@Autowired | @Inject | @Inject 没有 'required' 属性。可以使用 Java 8 的 Optional 替代。 |
@Component | @Named / @ManagedBean | JSR-330 不支持通用注入,只支持以 name 注入。 |
@Scope("singleton") | @Singleton | JSR-330 默认的 scope 是 prototype。但是为了和 Spring 的通用默认值保持一致,在 Spring 容器中使用 JSR-330, bean 会被设置为 singleton。如果要使用其他 scope,你可以使用 Spring 的 @Scope 注解。 javax.inject 也提供一个 @Scope 注解。然而这个注解只能作用于你自己的 annotations。 |
@Qualifier | @Qualifier / @Named | javax.inject.Qualifier 是一个 meta-annotation,可以用来创建自定义的qualifiers。 配合 javax.inject.Named 来指定名称 (就像 Spring 的 @Qualifier 一样,可以带一个 value) 。 |
@Value | - | no equivalent |
@Required | - | no equivalent |
@Lazy | - | no equivalent |
ObjectFactory | Provider | javax.inject.Provider 是 Spring ObjectFactory 的替代者,只有一个 get() 方法用于 method name. 它还可以与 Spring 的 @Autowired 配合使用,或者直接用于普通没有注解的 constructors 和 setter 方法。 |

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
程序员随想-快速熟悉业务
前言 作为一名开发,经常面临着主动或被动切换业务做,有些时候切换至有一定相关联的另一个业务,本来做余额宝的被调到做证券。一些情况下是切换至完全相关的业务,如从商品切换到交易,甚至从电商业务切换至金融业务。在经常遇到这种的情况下,建立一个“快速熟悉陌生业务”的方法论就很重要了。下面经过个人思考,提出的一些不完善的想法。 一、划分模块 人的记忆和关系型数据库有些类型,较容易记住的大的关键点,而具体的细节可能想很久,甚至记不住。根据人的这种记忆方式,我们接触一个新的事务最好的方式应该就是分层,按照一定逻辑分几层,每层分多个模块,了解每层各个模块概念,然后再细分下一层子模块概念。这种思维方式有点像数据库的索引,符合人的思维习惯。 划分模块方式 模块是基于一定逻辑组织形式来划分的,只要按照逻辑来划分的话,划分模块的方式肯定有很多种,这里我提两种最容易的方式:业务功能,应用架构。 业务功能 按照业务功能性,对业务进行拆解,把复杂的业务进行拆分成功能单元,各功能单元再根据场景进行更细粒度的拆分。拆分有一个原则,要做到高内聚,低耦合。 应用架构 基本上应用都是分层的,下图就是最常见的应用的组织形式,当...
- 下一篇
经典分布式算法 —— 浅显易懂的 Raft 算法实现
一、Raft概念 copy一下其他小伙伴写的文章: Raft算法详解 不同于Paxos算法直接从分布式一致性问题出发推导出来,Raft算法则是从多副本状态机的角度提出,用于管理多副本状态机的日志复制。Raft实现了和Paxos相同的功能,它将一致性分解为多个子问题:Leader选举(Leader election)、日志同步(Log replication)、安全性(Safety)、日志压缩(Log compaction)、成员变更(Membership change)等。同时,Raft算法使用了更强的假设来减少了需要考虑的状态,使之变的易于理解和实现。 Raft将系统中的角色分为领导者(Leader)、跟从者(Follower)和候选人(Candidate): Leader:接受客户端请求,并向Follower同步请求日志,当日志同步到大多数节点上后告诉Follower提交日志。 Follower:接受并持久化Leader同步的日志,在Leader告之日志可以提交之后,提交日志。 Candidate:Leader选举过程中的临时角色。 本文不过多赘述 raft 算法是个什么东西......
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker使用Oracle官方镜像安装(12C,18C,19C)