spring 通過注解裝配Bean


  使用注解的方式可以減少XML的配置,注解功能更為強大,它既能實現XML的功能,也提供了自動裝配的功能,采用了自動裝配后,程序員所需要做的決斷就少了,更加有利於對程序的開發,這就是“約定優於配置”的開發原則。
  在Spring中,它提供了兩種方式來讓Spring IoC容器發現Bean。
  •組件掃描:通過定義資源的方式,讓Spring IoC容器掃描對應的包,從而把Bean裝配進來。
  •自動裝配:通過注解定義,使得一些依賴關系可以通過注解完成。
  通過掃描和自動裝配,大部分的工程都可以用Java配置完成,而不是XML,這樣可以有效減少配置和引入大量XML,它解決了在Spring 3之前的版本需要大量的XML配置的問題,這些問題曾被許多開發者詬病。由於目前注解已經成為Spring開發的主流,在之后的章節里,筆者也會以注解的方式為主介紹Spring的開發,但是請注意只是為主,而不是全部以注解的方式去實現。因為不使用XML也存在着一定的弊端,比如系統存在多個公共的配置文件(比如多個properties和XML文件),如果寫在注解里,那么那些公共資源的配置就會比較分散了,這樣不利於統一的管理,又或者一些類來自於第三方,而不是我們系統開發的配置文件,這時利用XML的方式來完成會更加明確一些,因此目前企業所流行的方式是,以注解為主,以XML為輔,本書的介紹也是如此。

使用@Component裝配Bean

@Component(value = "role")
public class Role {
    @Value("1")
    private Long id;
    @Value("role_name_1")
    private String roleName;
    @Value("role_note_1")
    private String note;
}

 

  •注解@Component代表Spring IoC會把這個類掃描生成Bean實例,而其中的value屬性代表這個類在Spring中的id,這就相當於XML方式定義的Bean的id,也可以簡寫成@Component("role"),甚至直接寫成@Component,對於不寫的,Spring IoC容器就默認類名,但是以首字母小寫的形式作為id,為其生成對象,配置到容器中。
  •注解@Value代表的是值的注入,這里只是簡單注入一些值,其中id是一個long型,注入的時候Spring會為其轉化類型。

  現在有了這個類,但是還不能進行測試,因為Spring IoC並不知道需要去哪里掃描對象,這個時候可以使用一個Java Config來去告訴它,如代碼清單所示。
  代碼清單:Java Config類

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

@Configuration
@ComponentScan
public class PojoConfig {
}

 

  這個類十分簡單,幾乎沒有邏輯,但是要注意兩處加粗的代碼。
  •包名和代碼清單的POJO保持一致。
  •@ComponentScan代表進行掃描,默認是掃描當前包的路徑,POJO的包名和它保持一致才能掃描,否則是沒有的。

  可以通過Spring定義好的Spring IoC容器的實現類——AnnotationConfigApplicationContext去生成IoC容器了。它十分簡單,如代碼清單所示。
  代碼清單:使用注解生成Spring IoC容器

ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
Role role = context.getBean(Role.class);
System.err.println(role.getId());

 

  @ComponentScan存在着兩個配置項:第1個是basePackages,它是由base和package兩個單詞組成的,而package還使用了復數,意味着它可以配置一個Java包的數組,Spring會根據它的配置掃描對應的包和子包,將配置好的Bean裝配進來;第2個是basePackageClasses,它由base、package和class三個單詞組成的,采用復數,意味着它可以配置多個類,Spring會根據配置的類所在的包,為包和子包進行掃描裝配對應配置的Bean。
  代碼清單:RoleService接口

import com.ssm.chapter10.annotation.pojo.Role;

public interface RoleService {
    public void printRoleInfo(Role role);
}

 

  代碼清單:RoleServiceImpl類

import org.springframework.stereotype.Component;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService;

@Component
public class RoleServiceImpl implements RoleService {

    // @Override
    public void printRoleInfo(Role role) {
        System.out.println("id =" + role.getId());
        System.out.println("roleName =" + role.getRoleName());
        System.out.println("note =" + role.getNote());
    }

}

 

  代碼清單:配置@ComponentScan制定包掃描

import org.springframework.context.annotation.ComponentScan;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.impl.RoleServiceImpl;

@ComponentScan(basePackageClasses = {Role.class, RoleServiceImpl.class})
// @ComponentScan(basePackages = {"com.ssm.chapter10.annotation.pojo", "com.ssm.chapter10.annotation.service"})
// @ComponentScan(basePackages = {"com.ssm.chapter10.annotation.pojo", "com.ssm.chapter10.annotation.service"}
//, basePackageClasses = {Role.class, RoleServiceImpl.class})
public class ApplicationConfig {
}

 

  •這是對掃描包的定義,可以采用任意一個@ComponentScan去定義,也可以取消代碼中的注釋。
  •如果采用多個@ComponentScan去定義對應的包,但是每定義一個@ComponentScan,Spring就會為所定義的類生成一個新的對象,也就是所配置的Bean將會生成多個實例,這往往不是我們的需要。
  •對於已定義了basePackages和basePackageClasses的@ComponentScan,Spring會進行專門的區分,也就是說在同一個@ComponentScan中即使重復定義相同的包或者存在其子包定義,也不會造成因同一個Bean的多次掃描,而導致一次配置生成多個對象。
  基於上述的幾點,建議不要采用多個@ComponentScan注解進行配置,因為一旦有重復的包和子包就會產生重復的對象,這往往不是真實的需求。對於basePackages和basePackageClasses的選擇問題,basePackages的可讀性會更好一些,因此在項目中會優先選擇使用它,但是在需要大量重構的工程中,盡量不要使用basePackages定義,因為很多時候重構修改包名需要反復地配置,而IDE不會給你任何的提示。而采用basePackageClasses,當你對包移動的時候,IDE會報錯提示,並且可以輕松處理這些錯誤。
  代碼清單:測試basePackages和basePackageClasses配置

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
Role role = context.getBean(Role.class);
RoleService roleService = context.getBean(RoleService.class);
roleService.printRoleInfo(role);
context.close();

 

自動裝配——@Autowired

  通過學習Spring IoC容器,我們知道Spring是先完成Bean的定義和生成,然后尋找需要注入的資源。也就是當Spring生成所有的Bean后,如果發現這個注解,它就會在Bean中查找,然后找到對應的類型,將其注入進來,這樣就完成依賴注入了。所謂自動裝配技術是一種由Spring自己發現對應的Bean,自動完成裝配工作的方式,它會應用到一個十分常用的注解@Autowired上,這個時候Spring會根據類型去尋找定義的Bean然后將其注入,這里需要留意按類型(Role)的方式。

public interface RoleService2 {
    public void printRoleInfo();
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService2;

@Component("RoleService2")
public class RoleServiceImpl2 implements RoleService2 {

    @Autowired
    private Role role = null;

    // @Autowired
    // public void setRole(Role role) {
    //     this.role = role;
    // }

    // @Override
    public void printRoleInfo() {
        System.out.println("id =" + role.getId());
        System.out.println("roleName =" + role.getRoleName());
        System.out.println("note =" + role.getNote());
    }

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

 

 

  這里的@Autowired注解,表示在Spring IoC定位所有的Bean后,這個字段需要按類型注入,這樣IoC容器就會尋找資源,然后將其注入。比如代碼清單10-15定義的Role和代碼清單10-23定義RoleServiceImpl2的兩個Bean,假設將其定義,那么Spring IoC容器會為它們先生成對應的實例,然后依據@Au-towired注解,按照類型找到定義的實例,將其注入。
  IoC容器有時候會尋找失敗,在默認的情況下尋找失敗它就會拋出異常,也就是說默認情況下,Spring IoC容器會認為一定要找到對應的Bean來注入這個字段,有些時候這並不是一個真實的需要,比如日志,有時候我們會覺得這是可有可無的,這個時候可以通過@Autowired的配置項required來改變它,比如@Autowired(required=false)。
  正如之前所談到的在默認情況下是必須注入成功的,所以這里的required的默認值為true。當把配置修改為了false時,就告訴Spring IoC容器,假如在已經定義好的Bean中找不到對應的類型,允許不注入,這樣也就沒有了異常拋出,只是這樣這個字段可能為空,讀者要自行校驗,以避免發生空指針異常。在大部分的情況下,都不需要這樣修改。
  @Autowired除可以配置在屬性之外,還允許方法配置,常見的Bean的setter方法也可以使用它來完成注入

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
RoleService2 roleService2 = context.getBean(RoleService2.class);
roleService2.printRoleInfo();
context.close();

 

自動裝配的歧義性(@Primary和@Qualifier)

  @Autowired注解,它可以完成一些自動裝配的功能,並且使用方式十分簡單,但是有時候這樣的方式並不能使用。這一切的根源來自於按類型的方式,按照Spring的建議,在大部分情況下會使用接口編程,但是定義一個接口,並不一定只有與之對應的一個實現類。換句話說,一個接口可以有多個實現類

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService;

@Component("roleService3")
// @Primary//方法1
public class RoleServiceImpl3 implements RoleService {

    // @Override
    public void printRoleInfo(Role role) {
        System.out.print("{id =" + role.getId());
        System.out.print(", roleName =" + role.getRoleName());
        System.out.println(", note =" + role.getNote() + "}");
    }

}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import com.ssm.chapter10.annotation.pojo.Role;
import com.ssm.chapter10.annotation.service.RoleService;

@Component
public class RoleController {

    @Autowired
    // @Qualifier("roleService3")//方法2
    private RoleService roleService = null;

    public void printRole(Role role) {
        roleService.printRoleInfo(role);
    }

}

 

  這里的字段roleService是一個RoleService接口類型。RoleService有兩個實現類,分別是RoleServiceImpl和RoleServiceImpl3,這個時候Spring IoC容器就會犯糊塗了,它無法判斷把哪個對象注入進來,於是就會拋出異常,這樣@Autowired注入就失敗了。
  通過上面的分析,可以知道產生這樣的狀況是因為它采用的是按類型來注入對象,而在Java中接口可以有多個實現類,同樣的抽象類也可以有多個實例化的類,這樣就會造成通過類型(bytype)獲取Bean的不唯一,從而導致Spring IoC類似於按類型的方法無法獲得唯一的實例化類。
  為了消除歧義性,Spring提供了兩個注解@Primary和@Qualifier,這是兩個不同的注解,其消除歧義性的理念不太一樣

  1. 注解@Primary
注解@Primary代表首要的,當Spring IoC通過一個接口或者抽象類注入對象的時候,由於存在多個實現類或者具體類,就會犯糊塗,不知道采用哪個類注入為好。注解@Primary則是告訴Spring IoC容器,請優先使用該類注入。
  2. 注解@Qualifier正如上面所談及的歧義性,一個重要的原因是Spring在尋找依賴注入的時候采用按類型注入引起的。除了按類型查找Bean,Spring IoC容器最底層的接口BeanFactory,也定義了按名稱查找的方法,如果采用名稱查找的方法,而不是采用按類型查找的方法,那么不就可以消除歧義性了嗎?答案是肯定的,而注解@Qualifier就是這樣的一個注解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM