Spring 中 Bean 的装配(注入)Autowired Resource Inject 三种模式对比

基础知识

JSR 250: Common Annotations for the JavaTM Platformhtml

JSR 330: Dependency Injection for Javajava

JSR 305: Annotations for Software Defect Detectionredis

beans-annotation-configspring

本文讲解的是 Spring 基于注解的 Bean 装载,XML 形式的配置与 Annotation 形式的配置实现功能都是同样的,且能够混用,可是要注意的是 Annotation 先于 XML 执行,注解的配置可能会被 XML 覆盖。api

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

装配(autowiring ) 约等于 注入(injection )app

自动装配的模式

模式 描述
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。

关键字

忽略下面的 “-”,会被超连接。ide

  • @Autowired
  • -@Primary-
  • -@Required---formally deprecated as of Spring Framework 5.1
  • @Qualifier
  • -@Nullable-

JSR-250函数

  • @Resource
  • @PostConstruct
  • @PreDestroy
  • @ManagedBean

JSR-330ui

  • @Inject
  • @Named

对比结论

情景\注解 @Autowired @Resource @Inject
模式 byType byName,找不到用类型找 byType
@Nullable Optional -- @Nullable Optional 配合 Provider 使用
没有匹配的 Bean 不报异常 报异常 不报异常
有多个匹配的 Bean 报异常 报异常 报异常
多个匹配的 Bean 指定惟一 @Primary @Primary @Primary
其余 能够配合 @Qualifier 使用,可是 @Qualifier 不保证惟一 -- 指定 name 属性,指定后不会按类型找能够配合 @Named 使用

使用实例1、多数据源配置

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());
    }
}
方式1、
    @Autowired
    @Qualifier("bizLogMongo")--------必需要指定Qualifier,不指定默认获取到的是 mainMongo,由于 mainMongo 使用了 @Primary
    MongoTemplate bizLogMongo;----bizLogMongo这个名字不影响

    方式2、
    @Resource
    MongoTemplate bizLogMongo;

    方式3、
    @Resource(nbame="bizLogMongo")
    MongoTemplate mongo;

使用实例1、多个实现类

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,没找到,而后按类型找发现有两个实现类,报错。

使用实例1、单个实例

@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 方法。
相关文章
相关标签/搜索