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 方法。
相關文章
相關標籤/搜索