寫在前面
【Spring專題】停更一個多月,期間在更新其他專題的內容,不少小夥伴紛紛留言說:冰河,你【Spring專題】是不是停更了啊!其實並沒有停更,只是中途有很多小夥伴留言說急需學習一些知識技能,以便於跳槽,哈哈,大家都懂得!所以,中途停更了一段時間,寫了一些其他專題的文章。現在,繼續更新【String專題】。
關注 冰河技術 微信公眾號,訂閱更多技術乾貨!如果文章對你有所幫助,請不要吝惜你的點贊、在看、留言和轉發,你的支援是我持續創作的最大動力!
專案工程原始碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation
註解說明
@Autowired註解
@Autowired 註解,可以對類成員變數、方法和建構函式進行標註,完成自動裝配的工作。@Autowired 註解可以放在類,介面以及方法上。在使用@Autowired之前,我們對一個bean配置屬性時,是用如下xml檔案的形式進行配置的。
<property name="屬性名" value=" 屬性值"/>
@Autowired 註解的原始碼如下所示。
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
@Autowired 註解說明:
(1)預設優先按照型別去容器中找對應的元件,找到就賦值;
(2)如果找到多個相同型別的元件,再將屬性名稱作為元件的id,到 IOC 容器中進行查詢。
@Qualifier註解
@Autowired是根據型別進行自動裝配的,如果需要按名稱進行裝配,則需要配合@Qualifier 註解使用。
@Qualifier註解原始碼如下所示。
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
@Primary註解
在Spring 中使用註解,常使用@Autowired, 預設是根據型別Type來自動注入的。但有些特殊情況,對同一個介面,可能會有幾種不同的實現類,而預設只會採取其中一種實現的情況下, 就可以使用@Primary註解來標註優先使用哪一個實現類。
@Primary註解的原始碼如下所示。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {
}
自動裝配
在進行專案實戰之前,我們先來說說什麼是Spring元件的自動裝配。Spring元件的自動裝配就是:Spring利用依賴注入,也就是我們通常所說的DI,完成對IOC容器中各個元件的依賴關係賦值。
專案實戰
測試@Autowired註解
這裡,我們以之前專案中建立的dao、service和controller為例進行說明。dao、service和controller的初始程式碼分別如下所示。
- dao
package io.mykit.spring.plugins.register.dao;
import org.springframework.stereotype.Repository;
/**
* @author binghe
* @version 1.0.0
* @description 測試的dao
*/
@Repository
public class PersonDao {
}
- service
package io.mykit.spring.plugins.register.service;
import io.mykit.spring.plugins.register.dao.PersonDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author binghe
* @version 1.0.0
* @description 測試的Service
*/
@Service
public class PersonService {
@Autowired
private PersonDao personDao;
}
- controller
package io.mykit.spring.plugins.register.controller;
import org.springframework.stereotype.Controller;
/**
* @author binghe
* @version 1.0.0
* @description 測試的controller
*/
@Controller
public class PersonController {
@Autowired
private PersonService personService;
}
可以看到,我們在Service中使用@Autowired註解注入了Dao,在Controller中使用@Autowired註解注入了Service。為了方便測試,我們在PersonService類中生成一個toString()方法,如下所示。
package io.mykit.spring.plugins.register.service;
import io.mykit.spring.plugins.register.dao.PersonDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author binghe
* @version 1.0.0
* @description 測試的Service
*/
@Service
public class PersonService {
@Autowired
private PersonDao personDao;
@Override
public String toString() {
return personDao.toString();
}
}
這裡,我們在PersonService類的toString()方法中直接呼叫personDao的toString()方法並返回。為了更好的演示效果,我們在專案的 io.mykit.spring.plugins.register.config
包下建立AutowiredConfig類,如下所示。
package io.mykit.spring.plugins.register.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author binghe
* @version 1.0.0
* @description 測試自動裝配元件的Config配置類
*/
@Configuration
@ComponentScan(value = {
"io.mykit.spring.plugins.register.dao",
"io.mykit.spring.plugins.register.service",
"io.mykit.spring.plugins.register.controller"})
public class AutowiredConfig {
}
接下來,我們來測試一下上面的程式,我們在專案的src/test/java目錄下的 io.mykit.spring.test
包下建立AutowiredTest類,如下所示。
package io.mykit.spring.test;
import io.mykit.spring.plugins.register.config.AutowiredConfig;
import io.mykit.spring.plugins.register.service.PersonService;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author binghe
* @version 1.0.0
* @description 測試自動裝配
*/
public class AutowiredTest {
@Test
public void testAutowired01(){
//建立IOC容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
PersonService personService = context.getBean(PersonService.class);
System.out.println(personService);
context.close();
}
}
測試方法比較簡單,這裡,我就不做過多說明了。接下來,我們執行AutowiredTest類的testAutowired01()方法,得出的輸出結果資訊如下所示。
io.mykit.spring.plugins.register.dao.PersonDao@10e92f8f
可以看到,輸出了PersonDao資訊。
那麼問題來了:我們在PersonService類中輸出的PersonDao,和我們直接在Spring IOC容器中獲取的PersonDao是不是同一個物件呢?
我們可以在AutowiredTest類的testAutowired01()方法中新增獲取PersonDao物件的方法,並輸出獲取到的PersonDao物件,如下所示。
@Test
public void testAutowired01(){
//建立IOC容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
PersonService personService = context.getBean(PersonService.class);
System.out.println(personService);
PersonDao personDao = context.getBean(PersonDao.class);
System.out.println(personDao);
context.close();
}
我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。
io.mykit.spring.plugins.register.dao.PersonDao@10e92f8f
io.mykit.spring.plugins.register.dao.PersonDao@10e92f8f
可以看到,我們在PersonService類中輸出的PersonDao物件和直接從IOC容器中獲取的PersonDao物件是同一個物件。
如果在Spring容器中存在對多個PersonDao物件該如何處理呢?
首先,為了更加直觀的看到我們使用@Autowired註解裝配的是哪個PersonDao物件,我們對PersonDao類進行改造,為其加上一個remark欄位,為其賦一個預設值,如下所示。
package io.mykit.spring.plugins.register.dao;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Repository;
/**
* @author binghe
* @version 1.0.0
* @description 測試的dao
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Repository
public class PersonDao {
private String remark = "1";
}
接下來,我們就在AutowiredConfig類中注入一個PersonDao物件,並且顯示指定PersonDao物件在IOC容器中的bean的名稱為personDao2,併為PersonDao物件的remark欄位賦值為2,如下所示。
@Bean("personDao2")
public PersonDao personDao(){
return new PersonDao("2");
}
目前,在我們的IOC容器中就會注入兩個PersonDao物件。那此時,@Autowired註解裝配的是哪個PersonDao物件呢?
接下來,我們執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。
PersonDao{remark='1'}
可以看到,結果資訊輸出了1,說明:@Autowired註解預設優先按照型別去容器中找對應的元件,找到就賦值;如果找到多個相同型別的元件,再將屬性名稱作為元件的id,到 IOC 容器中進行查詢。
那我們如何讓@Autowired裝配personDao2呢? 這個問題問的好,其實很簡單,我們將PersonService類中的personDao全部修改為personDao2,如下所示。
package io.mykit.spring.plugins.register.service;
import io.mykit.spring.plugins.register.dao.PersonDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author binghe
* @version 1.0.0
* @description 測試的Service
*/
@Service
public class PersonService {
@Autowired
private PersonDao personDao2;
@Override
public String toString() {
return personDao2.toString();
}
}
此時,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。
PersonDao{remark='2'}
可以看到,此時命令列輸出了personDao2的資訊。
測試@Qualifier註解
從測試@Autowired註解的結果來看:@Autowired註解預設優先按照型別去容器中找對應的元件,找到就賦值;如果找到多個相同型別的元件,再將屬性名稱作為元件的id,到 IOC 容器中進行查詢。
如果IOC容器中存在多個相同型別的元件時,我們可不可以顯示指定@Autowired註解裝配哪個元件呢?有些小夥伴肯定會說:廢話!你都這麼問了,那肯定可以啊!沒錯,確實可以啊!此時,@Qualifier註解就派上用場了!
在之前的測試案例中,命令列輸出了 PersonDao{remark='2'}
說明@Autowired註解裝配了personDao2,那我們如何顯示的讓@Autowired註解裝配personDao呢?
比較簡單,我們只需要在PersonService類上personDao2欄位上新增@Qualifier註解,顯示指定@Autowired註解裝配personDao,如下所示。
@Qualifier("personDao")
@Autowired
private PersonDao personDao2;
此時,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。
PersonDao{remark='1'}
可以看到,此時儘管欄位的名稱為personDao2,但是我們使用了@Qualifier註解顯示指定@Autowired註解裝配personDao物件,所以,最終的結果輸出了personDao物件的資訊。
測試容器中無元件的情況
如果IOC容器中無相應的元件,會發生什麼情況呢?此時,我們刪除PersonDao類上的@Repository註解,並且刪除AutowiredConfig類中的personDao()方法上的@Bean註解,如下所示。
package io.mykit.spring.plugins.register.dao;
/**
* @author binghe
* @version 1.0.0
* @description 測試的dao
*/
public class PersonDao {
private String remark = "1";
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public String toString() {
return "PersonDao{" +
"remark='" + remark + '\'' +
'}';
}
}
package io.mykit.spring.plugins.register.config;
import io.mykit.spring.plugins.register.dao.PersonDao;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author binghe
* @version 1.0.0
* @description 測試自動裝配元件的Config配置類
*/
@Configuration
@ComponentScan(value = {
"io.mykit.spring.plugins.register.dao",
"io.mykit.spring.plugins.register.service",
"io.mykit.spring.plugins.register.controller"})
public class AutowiredConfig {
public PersonDao personDao(){
PersonDao personDao = new PersonDao();
personDao.setRemark("2");
return personDao;
}
}
此時IOC容器中不再有personDao,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.mykit.spring.plugins.register.dao.PersonDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier(value=personDao), @org.springframework.beans.factory.annotation.Autowired(required=true)}
可以看到,Spring丟擲了異常,未找到相應的bean物件,我們能不能讓Spring不報錯呢? 那肯定可以啊!Spring的異常資訊中都給出了相應的提示。
{@org.springframework.beans.factory.annotation.Qualifier(value=personDao), @org.springframework.beans.factory.annotation.Autowired(required=true)}
解決方案就是在PersonService類的@Autowired新增一個屬性required=false
,如下所示。
@Qualifier("personDao")
@Autowired(required = false)
private PersonDao personDao2;
並且我們修改下PersonService的toString()方法,如下所示。
@Override
public String toString() {
return "PersonService{" +
"personDao2=" + personDao2 +
'}';
}
此時,還需要將AutowiredTest類的testAutowired01()方法中直接從IOC容器中獲取personDao的程式碼刪除,如下所示。
@Test
public void testAutowired01(){
//建立IOC容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
PersonService personService = context.getBean(PersonService.class);
System.out.println(personService);
context.close();
}
此時,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。
PersonService{personDao2=null}
可以看到,當為@Autowired新增屬性required=false
後,即使IOC容器中沒有對應的物件,Spring也不會丟擲異常。此時,裝配的物件就為null。
測試完成後,我們再次為PersonDao類新增@Repository註解,並且為AutowiredConfig類中的personDao()方法新增@Bean註解。
測試@Primary註解
在Spring中,對同一個介面,可能會有幾種不同的實現類,而預設只會採取其中一種實現的情況下, 就可以使用@Primary註解來標註優先使用哪一個實現類。
首先,我們在AutowiredConfig類的personDao()方法上新增@Primary註解,此時,我們需要刪除PersonService類中personDao欄位上的@Qualifier註解,這是因為@Qualifier註解為顯示指定裝配哪個元件,如果使用了@Qualifier註解,無論是否使用了@Primary註解,都會裝配@Qualifier註解標註的物件。
設定完成後,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。
PersonService{personDao2=PersonDao{remark='2'}}
可以看到,此時remark的值為2,裝配了AutowiredConfig類中注入的personDao。
接下來,我們為PersonService類中personDao欄位再次新增@Qualifier註解,如下所示。
@Qualifier("personDao")
@Autowired(required = false)
private PersonDao personDao;
此時,我們再次執行AutowiredTest類的testAutowired01()方法,輸出的結果資訊如下所示。
PersonService{personDao=PersonDao{remark='1'}}
可以看到,此時,Spring裝配了使用@Qualifier標註的personDao。
重磅福利
關注「 冰河技術 」微信公眾號,後臺回覆 “設計模式” 關鍵字領取《深入淺出Java 23種設計模式》PDF文件。回覆“Java8”關鍵字領取《Java8新特性教程》PDF文件。回覆“限流”關鍵字獲取《億級流量下的分散式限流解決方案》PDF文件,三本PDF均是由冰河原創並整理的超硬核教程,面試必備!!
好了,今天就聊到這兒吧!別忘了點個贊,給個在看和轉發,讓更多的人看到,一起學習,一起進步!!
寫在最後
如果你覺得冰河寫的還不錯,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習高併發、分散式、微服務、大資料、網際網路和雲原生技術,「 冰河技術 」微信公眾號更新了大量技術專題,每一篇技術文章乾貨滿滿!不少讀者已經通過閱讀「 冰河技術 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨幹!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公眾號吧,每天更新超硬核技術乾貨,讓你對如何提升技術能力不再迷茫!