@Autowired、@Resource、@Inject和@Service

一、什麼是註解

Annotation(註解)是JDK1.5及以後版本引入的。它可以用於創建文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查。註解是以@註解名在代碼中存在的。根據註解參數的個數,我們可以將註解分爲:標記註解、單值註解、完整註解三類。它們都不會直接影響到程序的語義,只是作爲註解(標識)存在,可以通過反射機制編程實現對這些元數據(用來描述數據的數據)的訪問。另外,可以在編譯時選擇代碼裏的註解是否只存在於源代碼級,或者它也能在class文件、或者運行時中出現(SOURCE/CLASS/RUNTIME)。

二、註解的效應

傳統的Spring做法是使用.xml文件來對bean進行注入或者是配置aop、事務,這麼做有兩個缺點:

  1. 如果所有的內容都配置在.xml文件中,會導致.xml文件過大;如果按需求分開.xml文件,又會導致.xml文件過多。總之這會使得配置文件的可讀性與可維護性變得很低。
  2. 開發中,在.java文件和.xml文件之間不斷切換,是一件麻煩的事。同時這種思維上的不連貫也會降低開發的效率。

爲了解決這兩個問題,Spring引入了註解,通過@註解名的方式,讓註解與Java Bean緊密結合,既大大減少了配置文件的體積,又增加了Java Bean的可讀性與內聚性。

三、@Autowired、@Resource、@Inject和@Service

先看一個不使用註解的Spring示例,在這個示例的基礎上,改成註解版本的,這樣也能看出使用與不使用註解之間的區別,先定義一個老師:

public class Teacher{
        private String teacherName = "TW";
        public String toString() {
            return "TeacherName:" + teacherName;
        }
    }

再定義一個學生:

    public class Student{
        private String studentName = "SL";
        public String toString() {
            return "StudentName:" + studentName;
        }
    }

然後定義一個學校:

public class School{
        private Teacher teacher;
        private Student student;
        public void setTeacher(Teacher teacher){
            this.teacher = teacher;
        }
        public void setStudent(Student student){
            this.student = student;
        }
        public Teacher getTeacher(){
            return teacher;
        }
        public Student getStudent(){
            return student;
        }
        public String toString(){
            return teacher + "\n" + student;
        }
    }

spring的配置文件這麼寫:

<?xml version="1.0" encoding="UTF-8"?>
<bean id="school" class="com.zxt.bean.School" >
    <property name="teacher" ref="teacher" />
    <property name="student" ref="student" />
</bean>
<bean id="teacher" class="com.zxt.uu.Teacher" />
<bean id="student" class="com.zxt.uu.Student" />

這是最初始的.xml配置。

@Autowired

顧名思義,就是自動裝配。其作用是替代Java代碼裏面的getter/setter與bean屬性中的property。如果私有屬性需要對外提供的話,getter應當予以保留。引入@Autowired註解,先看一下spring配置文件怎麼寫:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
 3     xmlns="http://www.springframework.org/schema/beans"  
 4     xmlns:context="http://www.springframework.org/schema/context"  
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 6         http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
 7         http://www.springframework.org/schema/context
 8         http://www.springframework.org/schema/context/spring-context-4.2.xsd">
 9     
10     <context:component-scan base-package="com.zxt" />
11     
12     <bean id="school"  class="com.zxt.bean.School" />
13     <bean id="teacher" class="com.zxt.uu.Teacher" />
14     <bean id="student" class="com.zxt.uu.Student" />
15     
16 </beans>

注意第10行,爲了實現bean的自動載入,必須配置spring的掃描器。

在base-package指明一個包:

<context:component-scan base-package=“com.zxt”/>

表明com.zxt包及其子包中,如果某個類的頭上帶有特定的註解@Component@Repository@Service@Controller,就會將這個對象作爲Bean注入進spring容器。有了context:component-scan,另一個context:annotation-config標籤就可以移除掉,因爲已經被包含進去了。
context:component-scan提供兩個子標籤:context:include-filtercontext:exclude-filter。各代表引入和排除的過濾。

看到第12行,原來school裏面應當注入兩個屬性teacher、student,現在不需要注入了。再看下,School.java也很簡練,把getter/setter都可以去掉:

public class School{
        @Autowired
        private Teacher teacher;
        @Autowired
        private Student student;
        public String toString(){
            return teacher + "\n" + student;
        }
    }

這裏@Autowired註解的意思就是,當Spring發現@Autowired註解時,將自動在代碼上下文中找到與其匹配(默認是類型匹配)的Bean,並自動注入到相應的地方去。

xml優先原則
值得注意的是,假如.xml文件bean裏面有property,而School.java裏面卻去掉了屬性的getter/setter,並使用@Autowired註解標註這兩個屬性會怎麼樣?答案是Spring會按照xml優先的原則去School.java中尋找這兩個屬性的getter/setter,導致的結果就是初始化bean報錯。

假設此時把.xml文件的13行、14行兩行再給去掉,運行會拋出異常:
Exception in thread “main” org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘School’: Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.zxt.uu.Teacher com.zxt.bean.School.teacher; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.zxt.uu.Teacher] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) …
因爲,@Autowired註解要去尋找的是一個Bean,Teacher和 Student的Bean定義都給去掉了,自然就不是一個Bean了,Spring容器找不到也很好理解。那麼,如果屬性找不到又不想讓Spring容器拋出異常,而就是顯示null,可以嗎?可以,其實異常信息裏面也給出了提示,就是將@Autowired註解的required屬性設置爲false 即可:

public class School{
        @Autowired(required = false)
        private Teacher teacher;
        @Autowired(required = false)
        private Student student;
        public String toString(){
            return teacher + "\n" + student;
        }
    }

此時,找不到teacher、student兩個屬性,Spring容器不再拋出異常而是認爲這兩個屬性爲null。

@Autowired接口注入----->@Qualifier

上面僅僅只是注入一個Java類,比較簡單。那麼如果有一個接口,有多個實現,Bean裏引用的是接口名,又該怎麼做呢?比如有一個Person接口:

public interface Person{
        public String personName();
    }

兩個實現類Doctor和Police:

    @Service
    public class Doctor implements Person{
        public String docName(){
            return “Doctor person”;
        }
    }

    @Service
    public class Police implements Person{
        public String personName(){
            return “Police person”;
        }
    }

寫一個PersonFactory,引用Person:

    @Service
    public class PersonFactory{
        @Autowired
        private Person person;
        public String toString(){
            return person.personName();
        }
    }

Person接口有兩個實現類,Spring並不知道應當引用哪個實現類,代碼報錯。這種情況通常有兩個解決辦法:

  1. 刪除其中一個實現類,Spring會自動去base-package下尋找Person接口的實現類,發現Person接口只有一個實現類,便會直接引用這個實現類。
  2. 實現類就是有多個該怎麼辦?此時可以使用@Qualifier註解:
@Service
public class PersonFactory{
    @Autowired
    @Qualifier("Doctor")
    private Person person;
    public String toString(){
        return person.personName();
    }
}

注意@Qualifier註解括號裏面的必須是Person接口實現類的類名,不是bean的名字。寫成"doctor",結果會報錯。

@Resource

@Resource註解作用與@Autowired非常相似。先看一下@Resource,直接寫School.java了:

@Service
public class School{
    @Resource(name = "teacher")
    private Teacher teacher;
    @Resource(type = Student.class)
    private Student student;
    public String toString(){
        return teacher + "\n" + student;
    }
}

這是詳細一些的用法,說一下@Resource的裝配順序:

  1. @Resource後面沒有任何內容,默認通過name屬性去匹配bean,找不到再按type去匹配。
  2. 指定了name或者type則根據指定的類型去匹配bean。
  3. 指定了name和type則根據指定的name和type去匹配bean,任何一個不匹配都會報錯。

@Autowired和@Resource兩個註解的區別:

  1. @Autowired默認按照byType方式進行bean匹配,@Resource默認按照byName方式進行bean匹配
  2. @Autowired是Spring的註解,@Resource是J2EE的註解,根據導入註解的包名就可以知道。
  3. Spring屬於第三方的,J2EE是Java自己的東西。因此,建議使用@Resource註解,以減少代碼和Spring之間的耦合。

@Inject

  1. @Inject是JSR330 (Dependency Injection for Java)中的規範,需要導入javax.inject.Inject jar包 ,才能實現注入。
  2. @Inject可以作用constructor、method、field上。
  3. @Inject是根據類型進行自動裝配的,如果需要按名稱進行裝配,則需要配合@Named;

簡單示例:

@Inject
private Car car;

指定加入BMW組件:

@Inject
@Named("BMW")
private Car car;

@Named 的作用類似 @Qualifier!

@Service

使用@Service,可以更加簡化.xml文件配置。因爲spring的配置文件裏面還有12行~14行三個bean,應用spring配置文件裏面一個自動掃描的標籤,可以把這三個bean也給去掉,增強Java代碼的內聚性並進一步減少配置文件。先看一下配置文件:

<context:component-scan base-package="com.zxt" />

配置文件看起來特別清爽。下面以School.java爲例,其餘的Teacher.java和Student.java都一樣:

  @Service 
  public class School{ 
       @Autowired 
       private Teacher teacher; 
      @Autowired 
       private Student student; 
       public String toString(){ 
             return teacher + "\n" + student; 
       } 
} 

這樣,School.java在Spring容器中存在的形式就是"school",即可以通過ApplicationContext的getBean("school")方法來得到School.java。@Service註解,其實做了兩件事情:

  1. 聲明School.java是一個bean。這點很重要,因爲School.java是一個bean,其他的類纔可以使用@Autowired將School作爲一個成員變量自動注入。
  2. School.java在bean中的id是"school",即類名且首字母小寫。

如果不想用這種形式怎麼辦,就想讓School.java在Spring容器中的名字叫做"School",可以的:

@Service
@Scope("prototype")
public class School{
    @Autowired
    private Teacher teacher;
    @Autowired
    private Student student;
    public String toString(){
        return "TeacherName:" + teacher + "\nStudentName:" + student;
    }
}

這樣,就可以通過ApplicationContext的getBean(“school”)方法來得到School.java了。

這裏說下@Scope註解。因爲Spring默認產生的bean是單例的,如果不想使用單例,xml文件裏面可以在bean裏面配置scope屬性。註解也一 樣,配置@Scope即可,默認是"singleton"即單例,"prototype"表示原型即每次都會new一個新的出來。

注意:
假如school包下有Teacher、uu包下也有Teacher,它們二者都加了@Service註解,那麼在School.java中即使明確表示我要引用的是uu包下的Teacher,程序運行的時候依然會報錯。
這是因爲,兩個Teacher都使用@Service註解標註,意味着兩個Bean的名字都是"teacher",那麼我在School.java中自動裝配的是哪個Teacher呢?不明確,因此,Spring容器會拋出BeanDefinitionStoreException異常,Caused by:
org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name ‘teacher’ for bean class [com.zxt.uu.Teacher] conflicts with existing, non-compatible bean definition of same name and class [com.zxt.school.Teacher]

四、總結:

  1. 1️⃣@Autowired是Spring自帶的,通過AutowiredAnnotationBeanPostProcessor 類實現的依賴注入,Spring屬於第三方的。
    2️⃣@Resource是JSR250規範的實現,J2EE的註解,在javax.annotation包下,根據導入註解的包名就可以知道。J2EE是Java自己的東西。因此,建議使用@Resource註解,以減少代碼和Spring之間的耦合。
    3️⃣@Inject是JSR330規範實現的,需要導入javax.inject.Inject jar包 ,才能實現注入。
  2. 1️⃣@Autowired可以作用在constructor、method、parameter、field、annotation_type上。
    2️⃣@Resource可以作用method、field、type上。
    3️⃣@Inject可以作用constructor、method、field上。
  3. 1️⃣@Autowired默認按照byType方式進行bean匹配。
    2️⃣@Resource默認根據屬性名稱(byName)方式進行自動裝配。如果有多個類型一樣的Bean候選者,則可以通過name指定進行注入。
    3️⃣@Inject是根據類型進行自動裝配的,如果需要按名稱進行裝配,則需要配合@Named。
  4. @Autowired如果有多個類型一樣的Bean候選者,需要指定按照名稱(byName)進行裝配,則需要配合@Qualifier。指定名稱後,如果Spring IOC容器中沒有對應的組件bean拋出NoSuchBeanDefinitionException。也可以將@Autowired中required配置爲false,當找不到相應bean的時候,系統不會拋異常。
  5. @Autowired、@Inject用法基本一樣,不同的是@Inject沒有一個request屬性。



作者:MChopin
鏈接:https://www.jianshu.com/p/931cdba58cf7
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

 

下面是測試代碼,根據代碼會比較直觀一點。

 package com.springmvc.bean;
 
 import org.springframework.stereotype.Component;
 
  @Component("arg1")
 public class AunotationBean {
 
     public AunotationBean() {
           System.out.println("constructors of com.springmvc.[bean].AunotationBean");
     }
      
      public void init() {
          System.out.println("init com.springmvc.[bean].AunotationBean");
      }
  }
  package com.springmvc.bean;
  
  public interface Temporary {
  
     void init();
  }
 package com.springmvc.bean;
  
 import org.springframework.stereotype.Component;
  
  @Component("temp1")
  public class Temporary1Impl implements Temporary {
 
      public Temporary1Impl() {
         System.out.println("constructors of Temporary[1]Impl");
     }
 
    @Override
     public void init() {
          System.out.println("init Temporary[1]Impl");
     }
  }
  package com.springmvc.bean;
 
  import org.springframework.stereotype.Component;
  
  @Component("temp2")
  public class Temporary2Impl implements Temporary {
  
       public Temporary2Impl() {
         System.out.println("constructors of Temporary[2]Impl");
     }
 
     @Override
      public void init() {
 
          System.out.println("init Temporary[2]Impl");
     }
 }
 package com.springmvc.demo;
  
 import org.springframework.stereotype.Component;
   
 @Component("arg2")
  public class AunotationBean {
 
      public AunotationBean() {
          System.out.println("constructors of com.springmvc.[demo].AunotationBean");
      }
    
     public void init() {
         System.out.println("init com.springmvc.[demo].AunotationBean");
     }
  }
  package com.springmvc.main;
  
  import javax.annotation.Resource;
 
  import org.springframework.stereotype.Component;
 import com.springmvc.bean.Temporary;
  
  @Component
  public class SpringmvcTest {
  
     @Resource(name = "arg1")
     com.springmvc.bean.AunotationBean arg1; // 定義了name屬性的值, 就只按照name值匹配
    
     @Resource
    com.springmvc.demo.AunotationBean arg2; // 根據屬性(arg2)匹配, 如果匹配不到就進行type匹配
     
     @Resource // 接口會去尋找其實現類
     Temporary temp1; // 如果此處是 Temporary temp11, 會先根據temp11尋找, 找不到的時候再根據Temporary尋找, 會尋找到2個實現類
      
     @Resource // 如果不定義name, 那麼會自動根據name(temp2)屬性值進行匹配
     Temporary temp2;
    
     // [個人猜測]裝載的時候是根據類名是否重複進行判斷, 同一個接口的多個實現類只要類名不同即可通過編譯, 但是沒有直接關聯的兩個類如果類名相同, 不會通過編譯, 匹配的時候如果是接口類型, 那麼會尋找其實現類, 這個時候會出現有多個選擇的問題
 }
 package com.springmvc.main;
 import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.stereotype.Component;
  
  @Component
  public class SpringmvcMain {
  
      public SpringmvcMain(@Autowired SpringmvcTest args) {
         args.arg1.init();
        args.arg2.init();
         args.temp1.init();
          args.temp2.init();
     }
  }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章