一、什麼是註解
Annotation(註解)是JDK1.5及以後版本引入的。它可以用於創建文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查。註解是以@註解名
在代碼中存在的。根據註解參數的個數,我們可以將註解分爲:標記註解、單值註解、完整註解三類。它們都不會直接影響到程序的語義,只是作爲註解(標識)存在,可以通過反射機制編程實現對這些元數據(用來描述數據的數據)的訪問。另外,可以在編譯時選擇代碼裏的註解是否只存在於源代碼級,或者它也能在class文件、或者運行時中出現(SOURCE/CLASS/RUNTIME)。
二、註解的效應
傳統的Spring做法是使用.xml文件來對bean進行注入或者是配置aop、事務,這麼做有兩個缺點:
- 如果所有的內容都配置在.xml文件中,會導致.xml文件過大;如果按需求分開.xml文件,又會導致.xml文件過多。總之這會使得配置文件的可讀性與可維護性變得很低。
- 開發中,在.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-filter
和context: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並不知道應當引用哪個實現類,代碼報錯。這種情況通常有兩個解決辦法:
- 刪除其中一個實現類,Spring會自動去base-package下尋找Person接口的實現類,發現Person接口只有一個實現類,便會直接引用這個實現類。
- 實現類就是有多個該怎麼辦?此時可以使用@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的裝配順序:
- @Resource後面沒有任何內容,默認通過name屬性去匹配bean,找不到再按type去匹配。
- 指定了name或者type則根據指定的類型去匹配bean。
- 指定了name和type則根據指定的name和type去匹配bean,任何一個不匹配都會報錯。
@Autowired和@Resource兩個註解的區別:
- @Autowired默認按照byType方式進行bean匹配,@Resource默認按照byName方式進行bean匹配
- @Autowired是Spring的註解,@Resource是J2EE的註解,根據導入註解的包名就可以知道。
- Spring屬於第三方的,J2EE是Java自己的東西。因此,建議使用@Resource註解,以減少代碼和Spring之間的耦合。
@Inject
- @Inject是JSR330 (Dependency Injection for Java)中的規範,需要導入javax.inject.Inject jar包 ,才能實現注入。
- @Inject可以作用constructor、method、field上。
- @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註解,其實做了兩件事情:
- 聲明School.java是一個bean。這點很重要,因爲School.java是一個bean,其他的類纔可以使用@Autowired將School作爲一個成員變量自動注入。
- 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️⃣@Autowired是Spring自帶的,通過AutowiredAnnotationBeanPostProcessor 類實現的依賴注入,Spring屬於第三方的。
2️⃣@Resource是JSR250規範的實現,J2EE的註解,在javax.annotation包下,根據導入註解的包名就可以知道。J2EE是Java自己的東西。因此,建議使用@Resource註解,以減少代碼和Spring之間的耦合。
3️⃣@Inject是JSR330規範實現的,需要導入javax.inject.Inject jar包 ,才能實現注入。 - 1️⃣@Autowired可以作用在constructor、method、parameter、field、annotation_type上。
2️⃣@Resource可以作用method、field、type上。
3️⃣@Inject可以作用constructor、method、field上。 - 1️⃣@Autowired默認按照byType方式進行bean匹配。
2️⃣@Resource默認根據屬性名稱(byName)方式進行自動裝配。如果有多個類型一樣的Bean候選者,則可以通過name指定進行注入。
3️⃣@Inject是根據類型進行自動裝配的,如果需要按名稱進行裝配,則需要配合@Named。 - @Autowired如果有多個類型一樣的Bean候選者,需要指定按照名稱(byName)進行裝配,則需要配合@Qualifier。指定名稱後,如果Spring IOC容器中沒有對應的組件bean拋出NoSuchBeanDefinitionException。也可以將@Autowired中required配置爲false,當找不到相應bean的時候,系統不會拋異常。
- @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();
}
}