《Spring5學習》 01 裝配Bean之自動化裝配

    Spring的自動化裝配就便利性方面遠遠優於其餘裝配方法,這也是業界目前主要採用的Bean裝配機制。Spring基於組建掃描和自動裝配實現自動化裝配,能將用戶的顯示配置降到最低。如下經過一段代碼瞭解自動裝配的基本使用(樓主關於Spring5講解的全部的代碼示例均是在一個Spring Boot項目)。java

1.建立組件Beanspring

package com.example.demo.service;

/**
 * @author zhangyanqing
 * @desc 
 * @date 2018/08/05
 */
public interface MessageService {
    void printMessage();
}

 

package com.example.demo.service;

import org.springframework.stereotype.Component;

/**
 * @author zhangyanqing
 * @desc
 * @date 2018/08/05
 */
@Component
public class MessageServiceImpl implements MessageService {
    @Override
    public void printMessage() {
        System.out.println("Method printMessage Invoke!");
    }
}

 

2.建立配置類啓動組件掃描安全

package com.example.demo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author zhangyanqing
 * @desc
 * @date 2018/08/05
 */
@Configuration
@ComponentScan(basePackages = {"com.example.demo.service"})
public class MessageConfig {
}

 

3.建立Junit測試類測試bean自動裝配maven

package com.example.demo.test;

import com.example.demo.config.MessageConfig;
import com.example.demo.service.MessageService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author zhangyanqing
 * @desc
 * @date 2018/08/05
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MessageConfig.class)
public class MessageServicecTest {
    @Autowired
    private MessageService messageServiceImpl;

    @Test
    public void printMessage(){
        messageServiceImpl.printMessage();
    }
}

    

    這個Bean自動裝配的例子裏Spring用到了Component註解代表該類會做爲組件類,在Spring進行組件掃描時,它會去掃描配置類MessageConfig的@ComponentScan註解中定義的組建掃描包,掃描包下全部帶有Component註解的類,在Spring容器中自動爲該類建立一個實例。在測試類中messageServiceImpl被自動裝配註解AutoWired修飾,Spring會在上下文容器中找到該類的實例並將它注入進去。ide

 

1、基於註解爲Spring Bean命名

    Spring上下問容器的全部Bean都會有一個ID,若是沒有設置,默認是將類名的第一個字符替換爲小寫以後的字符串做爲ID,若是想本身命名Bean ID,能夠在Component註解中指定memcached

@Component("myMessageService")
public class MessageServiceImpl implements MessageService {
    @Override
    public void printMessage() {
        System.out.println("Method printMessage Invoke!");
    }
}

    也可使用java定義的@Named註解爲SpringBean命名,使用方式與效果與@Component相似,這裏不作討論。此外Spring基於Bean的不一樣功能還定義了相似@Service、@Repository、@Controller、@Configuration註解其實質和Component同樣,只是爲了區別不一樣Bean的功能起到表意做用而做的區分,他們在Spring上下文容器都以相同的方式存在。函數

2、設置組件掃描的基礎包或類

    在上述例子中咱們經過在配置類MessageConfig的ComponentScan註解中設置了組件掃描的包限制了Spring組件掃描的範圍,那麼若是ComponentScan註解沒有指定掃描包Spring會如何處理呢,答案是目前Spring會去掃描該本類(MessageConfig)所屬包下全部類。測試

1 - 在ComponentScan註解中指定多個組件掃描包

@Configuration
@ComponentScan(basePackages = {"com.example.demo.service","com.example.demo.dao","com.example.demo.facade"})
public class MessageConfig {
}

2 - 在ComponentScan註解中指定多個註解掃描類

@Configuration
@ComponentScan(basePackageClasses = MessageServiceImpl.class)
public class MessageConfig {
}

 

3、自動化裝配註解

    Spring5實現自動化裝配的註解有三種,他們是Autowired,Inject,Namedui

1 - 使用@Autowired註解實現自動化裝配

    @Autowired是Spring自帶的註解,下面是Spring5中該註解的源碼this

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

    經過註解源碼咱們知道@Autowired能夠在構造函數、類的全部方法、成員變量上使用,@Autowired註解的requied屬性若是設置爲true若是Spring容器中不存在該類的實例會當即拋出異常。下面咱們分別爲每一種使用場景給出實例,注意咱們目前只討論裝配Bean設置爲單例的狀況。

1)在構造函數中使用Autowired

@Component
public class UserService {
    private MessageService messageService;

    @Autowired
    public UserService(MessageService messageService) {
        this.messageService = messageService;
    }

    public String play(){
        return "UserService "+messageService.printMessage();
    }

}

    UserService類中被Autowired註解修飾的構造函數,當Spring實例化Bean的的時候會經過這個構造器進行實例化,而且在Spring容器中找到一個MessageService實例注入到成員變量messageService中

2)在類其餘方法中使用Autowired

@Component
public class UserService {
    private MessageService messageService;

    @Autowired
    public void setMessageService(MessageService messageService){
        this.messageService = messageService;
    }

    public String play(){
        String message = "UserService "+messageService.printMessage();
        System.out.println(message);
        return message;
    }

}

    本例被Auwired註解修飾的類方法會在Bean默認構造函數(注意調用的是類的默認構造函數若是類中沒有定義會默認實現一個無參構造函數)調用完以後被調用,Spring會從容器中找到一個MessageService對象注入到messageService中

3)在類成員變量上使用Autowired

@Component
public class UserService {
    @Autowired
    private MessageService messageService;
    
    public String play(){
        String message = "UserService "+messageService.printMessage();
        System.out.println(message);
        return message;
    }

}

 

2 - 使用@Inject註解實現自動化裝配

@Component
public class UserService {
    @Inject
    private MessageService messageService;

    public String play(){
        String message = "UserService "+messageService.printMessage();
        System.out.println(message);
        return message;
    }

}

    使用以前須要導入如下maven依賴

<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>

    @Inject註解實現Bean依賴自動裝配的使用方法和Autowired相似,它也能夠做用在構造函數、方法和成員變量,這裏就不對它的全部使用場景多作講述了。

4、條件化Bean

    假如咱們有這樣的需求,某個Bean只有在知足必定條件的狀況下才會被建立,例如必須在某個Bean存在的條件下才能建立,在Spring5中咱們可使用@Conditional註解實現,他能夠和Bean註解一塊兒使用,若是知足條件化註解@Conditional上配置的規則那麼建立這個Bean不然放棄建立Bean。

    例如咱們要建立一個類MemcacheService他必須在容器中MemcacheClient實例存在的狀況下才能被建立。以這個例子咱們來分析下Spring怎麼實現條件化Bean

1)建立MemcachedClient和MemcacheService類

public class MemcacheService {
    @Autowired
    private MemcachedClient client;

    public void saveKey(){
        client.saveKey();
    }
}

@Service
public class MemcachedClient {
    public void saveKey(){
        System.out.println("key saved succeed!");
    }
}

 

2)建立一個實現Condition的類封裝條件化Bean的規則

public class ConditionalOnExistBean implements Condition{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return conditionContext.getBeanFactory().getBean(MemcachedClient.class) != null;
    }
}

    當條件化註解@Conditional設置了ConditionalOnExistBean時,他會檢查Spring容器中是否存在MemcachedClient類實例,若是不存在拒絕爲被條件化註解修飾的類MemcachedService建立實例,這個也是Spring Boot實現依賴自動化配置採用的方式。

3)建立配置類MemcachedConfig

@Configuration
@ComponentScan(basePackageClasses = {MemcachedClient.class})
public class MemcachedConfig {
    @Bean
    @Conditional(ConditionalOnExistBean.class)
    public MemcachedService memcacheService(){
        return new MemcachedService();
    }

}

4)建立測試類

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MemcachedConfig.class)
public class MemcachedServiceTest {
    @Autowired
    private MemcachedService memcachedService;

    @Test
    public void testExistBean(){
        memcachedService.saveKey();
        Assert.assertTrue(memcachedService != null);
    }
}

    測試經過,由於咱們設計類的時候把MemcachedClient用@Service註解聲明爲Spring組件類且在配置類MemcachedConfig中設置掃描類中包含了它,所以它會被Spring識別併爲他在容器中建立實例。咱們試試去除MemcachedClient的@Service註解這時候由於該類沒法被識別Spring容器中就不存在該類實例,這時候Spring在建立MemcachedService以前發現容器中不存在MemcachedClient實例也就不會建立MemcachedService實例。

    咱們能夠經過在Spring項目中本身去實現Condition類定製本身條件化Bean的規則

5、處理自動裝配的歧義性

    在使用@Autowired註解實現自動裝配的過程當中若是Spring容器中僅有一個Bean匹配結果時不會有問題,可是若是被@Autowired註解修飾的是一個接口且該接口有多個實現類時Spring在試圖自動裝配時就沒法作出選擇,此時Spring會直接拋出一個NoUniqueBeanDefinitionException異常。這解決這種歧義性的時候Spring提供了以下方案。

1 - @Primary註解標識首選Bean

    當有多個可選Bean時,經過在bean聲明中將其中一個可選Bean設置爲首選Bean,這樣Spring在自動裝配遇到歧義性的時候會首先使用首選Bean。設置Bean爲首選Bean的方式包含如下三種

    1)在組件掃描的Bean上組合使用@Primary註解與@Component

@Component
@Primary
public class PrimaryMessageService implements MessageService{
    @Override
    public String printMessage() {
        System.out.println("method PrimaryMessageService.printMessage invoke!");
        return "PrimaryMessage";
    }
}

    2)在Java配置類中@Bean與@Primary組合使用

@Configuration
@ComponentScan(basePackageClasses = {MemcachedClient.class})
public class MemcachedConfig {
    @Bean
    @Primary
    public MemcachedService memcacheService(){
        return new MemcachedService();
    }

}

    3)使用XML配置Bean,將bean元素節點的primary屬性設置爲true

<bean id="messageService" class="com.demo.service.MessageServiceImpl" primary="true" />

2 - 使用限定符限定自動裝配

    當爲咱們在Spring項目中錯誤地爲一個接口建立了多個實現類,且將兩個以上的實現類聲明爲首選Bean。此時Spring仍是沒法作出選擇會拋出以前相似的異常。這時候能夠經過使用限定符去解決這類歧義性。

    Spring中使用限定符的主要方式是@Qualifier,他能夠與@Autowired和@Inject註解協同使用,在@Qualifier註解中指定Bean的ID。

@Component
public class UserService {
    @Autowired
    @Qualifier("primaryMessageService")
    private MessageService primaryMessageService;

    public String play(){
        String message = "UserService "+primaryMessageService.printMessage();
        System.out.println(message);
        return message;
    }

}

 

 

3 - 爲指定Bean設置限定符

    咱們能夠不必定依賴於將BeanID做爲限定符,還可使用系統提供的@Qualifier註解或者自定義註解爲Bean設置限定符

1)@Qualifier註解與@Component或者@Bean協同使用實現Bean限定

@Component
@Qualifier("primaryMessageService")
public class PrimaryMessageService implements MessageService{
    @Override
    public String printMessage() {
        System.out.println("method PrimaryMessageService.printMessage invoke!");
        return "PrimaryMessage";
    }
}

 

@Configuration
@ComponentScan(basePackageClasses = {MessageServiceImpl.class, PrimaryMessageService.class, UserService.class})
public class MessageConfig {

    @Bean
    @Qualifier("pPrimaryMessageService")
    public MessageService messageService(){
        return new PrimaryMessageService();
    }
}

2)使用自定義註解

    上述爲Bean設置限定符的方式要優於直接基於Bean ID限定可是若是有多個Bean具備相同的特性,咱們要根據Bean特性去篩選Bean的話咱們可能須要多個限定註解可是Java中不容許在一個類中出現相同的多個註解,所以咱們只能自定義多個註解並在註解定義時添加@Qualifier註解讓它具備@Qualifier註解的特性,下面是使用示例:

    首先建立基於@Qualifier註解建立兩個限定符註解

@Component
@TypeQualifier("type1")
@SubTypeQualifier("subtype1")
public class MessageServiceImpl implements MessageService {
    @Override
    public String printMessage() {
        System.out.println("Method printMessage Invoke!");
        return "Message";
    }
}

@Component
@TypeQualifier("type2")
@SubTypeQualifier("subtype2")
public class PrimaryMessageService implements MessageService{
    @Override
    public String printMessage() {
        System.out.println("method PrimaryMessageService.printMessage invoke!");
        return "PrimaryMessage";
    }
}

    建立測試類測試Bean自定義限定符效果

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MessageConfig.class)
public class MessageServicecTest {

    @Autowired
    @TypeQualifier("type1")
    @SubTypeQualifier("subtype1")
    private MessageService messageService;

    @Test
    public void printUserMessage(){
        Assert.assertTrue(messageService.printMessage().indexOf("Message") != -1);
    }
}

    經過使用自定義限定符註解咱們能夠繞開Java的限制使用多層次限定符使用多個限定符,相對於原始的@Qualifier和基於Bean ID的限定這種方式無疑更加靈活和類型安全。而且結合條件化註解@Conditional咱們能夠在很是複雜的業務場景下使用。

 

6、Bean的做用域

在Spring中Bean默認的做用域時singleton(單例)也就是在整個應用中只會爲Bean建立一個實例。Spring中定義了多種做用域如列表所示:

做用域 說明
Singleton(單例) 在整個應用中,只建立一個Bean實例
Prototype(原型) 每次注入或者經過Spring上下文獲取的時候都會建立一個新的Bean實例
Session(會話) 在Web應用中,爲每一個會話建立一個Bean實例
Request(請求) 在Web應用中,爲每一個請求建立一個Bean實例

    在Spring中若是要爲Bean指定其餘做用域可使用@Scope與@Bean或@Component組合使用,使用示例以下:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MemcachedService {
    @Autowired
    private MemcachedClient client;

    public void saveKey(){
        client.saveKey();
    }
}

 

@Configuration
@ComponentScan(basePackageClasses = {MemcachedClient.class})
public class MemcachedConfig {
    @Bean
    @Scope(value= WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.INTERFACES)
    public MemcachedService memcacheService(){
        return new MemcachedService();
    }

}
相關文章
相關標籤/搜索