寫在前面
【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均是由冰河原創並整理的超硬核教程,面試必備!!
好了,今天就聊到這兒吧!別忘了點個贊,給個在看和轉發,讓更多的人看到,一起學習,一起進步!!
寫在最后
如果你覺得冰河寫的還不錯,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習高並發、分布式、微服務、大數據、互聯網和雲原生技術,「 冰河技術 」微信公眾號更新了大量技術專題,每一篇技術文章干貨滿滿!不少讀者已經通過閱讀「 冰河技術 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨干!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公眾號吧,每天更新超硬核技術干貨,讓你對如何提升技術能力不再迷茫!