你期望月薪4萬,出門右拐,不送,這幾個點,你也就是個初級的水平
先來看幾個問題
通過註解的方式注入依賴物件,介紹一下你知道的幾種方式
@Autowired和@Resource有何區別
說一下@Autowired查詢候選者的過程
說一下@Resource查詢候選者的過程
@Qulifier有哪些用法?
@Qulifier加在類上面是幹什麼用的?
@Primary是做什麼的?
泛型注入用過麼?
這些問題如果你都ok,那麼恭喜你,很厲害。
本文內容
介紹spring中通過註解實現依賴注入的所有方式
@Autowired註解
@Qualifier註解
@Resource註解
@Primary註解
@Bean中注入的幾種方式
將指定型別的所有bean,注入到集合中
將指定型別的所有bean,注入到map中
注入泛型
依賴注入原始碼方面的一些介紹
本文內容比較多,所有知識點均有詳細案例,大家一定要敲一遍,加深理解。
@Autowired:注入依賴物件
作用
實現依賴注入,spring容器會對bean中所有欄位、方法進行遍歷,標註有@Autowired註解的,都會進行注入。
看一下其定義:
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* <p>Defaults to {@code true}.
*/
boolean required() default true;
}
可以用在構造器、方法、方法引數、欄位、註解上。
引數:
required:標註的物件是否必須注入,可能這個物件在容器中不存在,如果為true的時候,找不到匹配的候選者就會報錯,為false的時候,找不到也沒關係 。
@Autowire查詢候選者的過程
查詢過程有點複雜,看不懂的可以先跳過,先看後面案例,本文看完之後,可以回頭再來看這個過程。
@Autowired標註在欄位上面:假定欄位型別為一個自定義的普通的型別,候選者查詢過程如下
@Autowired標註在方法上或者方法引數上面:假定引數型別為為一個自定義的普通的型別,候選者查詢過程如下:
上圖中深色的表示方法注入和欄位注入查詢過程的不同點。
上圖中展示的是方法中只有一個引數的情況,如果有多個引數,就重複上面的過程,直到找到所有需要注入的引數。
將指定型別的所有bean注入到Collection中
如果被注入的物件是Collection型別的,可以指定泛型的型別,然後會按照上面的方式查詢所有滿足泛型型別所有的bean
將指定型別的所有bean注入到Map中
如果被注入的物件是Map型別的,可以指定泛型的型別,key通常為String型別,value為需要查詢的bean的型別,然後會按照上面方式查詢所有注入value型別的bean,將bean的name作為key,bean物件作為value,放在HashMap中,然後注入。
@Autowired查詢候選者可以簡化為下面這樣
按型別找->通過限定符@Qualifier過濾->@Primary->@Priority->根據名稱找(欄位名稱或者方法名稱)
概括為:先按型別找,然後按名稱找
案例1:@Autowired標註在構造器上,通過構造器注入依賴物件
Service1
package com.javacode2018.lesson001.demo26.test0;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test0;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
private Service1 service1;
public Service2() { //@1
System.out.println(this.getClass() + "無參構造器");
}
public Service2(Service1 service1) { //@2
System.out.println(this.getClass() + "有參構造器");
this.service1 = service1;
}
@Override
public String toString() { //@2
return "Service2{" +
"service1=" + service1 +
'}';
}
}
Service2中依賴於Service1,有2個構造方法
@1:無參構造器
@2:有參構造器,可以通過這個傳入依賴的Service1
@3:重寫了toString方法,一會列印測試的時候方便檢視
來個總的配置檔案
package com.javacode2018.lesson001.demo26.test0;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan //@1
public class MainConfig0 {
}
@1:會自動掃描當前類所在的包,會將Service1和Service2註冊到容器。
來個測試用例
package com.javacode2018.lesson001.demo26;
import com.javacode2018.lesson001.demo26.test0.MainConfig0;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class InjectTest {
@Test
public void test0() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig0.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
}
main方法中啟動容器,載入MainConfig0配置類,然後輸出容器中所有的bean
執行部分輸出
class com.javacode2018.lesson001.demo26.test0.Service2無參構造器
service1->com.javacode2018.lesson001.demo26.test0.Service1@4a94ee4
service2->Service2{service1=null}
輸出中可以看出呼叫了Service2的無參構造器,service2中的service1為null
通過@Autowired指定注入的構造器
在Service2有參有參構造器上面加上@Autowired註解,如下:
@Autowired
public Service2(Service1 service1) {
System.out.println(this.getClass() + "有參構造器");
this.service1 = service1;
}
再次執行test0()
class com.javacode2018.lesson001.demo26.test0.Service2有參構造器
service1->com.javacode2018.lesson001.demo26.test0.Service1@4ec4f3a0
service2->Service2{service1=com.javacode2018.lesson001.demo26.test0.Service1@4ec4f3a0}
Service2有參構造器被呼叫了,service2中的service1有值了。
案例2:@Autowired標註在方法上,通過方法注入依賴的物件
Service1
package com.javacode2018.lesson001.demo26.test1;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
private Service1 service1;
@Autowired
public void injectService1(Service1 service1) { //@1
System.out.println(this.getClass().getName() + ".injectService1()");
this.service1 = service1;
}
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
@1:方法上標註了@Autowired,spring容器會呼叫這個方法,從容器中查詢Service1型別的bean,然後注入。
來個總的配置檔案
package com.javacode2018.lesson001.demo26.test1;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig1 {
}
來個測試用例
InjectTest中加個方法
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig1.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
com.javacode2018.lesson001.demo26.test1.Service2.injectService1()
service1->com.javacode2018.lesson001.demo26.test1.Service1@9597028
service2->Service2{service1=com.javacode2018.lesson001.demo26.test1.Service1@9597028}
通過injectService1方法成功注入service1
案例3:@Autowired標註在setter方法上,通過setter方法注入
上面2種通過構造器,和通過普通的一個方法注入,不是很常見,可以將@Autowired標註在set方法上面,來注入指定的物件
Service1
package com.javacode2018.lesson001.demo26.test2;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
private Service1 service1;
@Autowired
public void setService1(Service1 service1) { //@1
System.out.println(this.getClass().getName() + ".setService1方法");
this.service1 = service1;
}
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
@1:標準的set方法,方法上使用了 @Autowired,會通過這個方法注入Service1型別的bean物件。
來個總的配置檔案
package com.javacode2018.lesson001.demo26.test2;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig2 {
}
來個測試用例
@Test
public void test2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
com.javacode2018.lesson001.demo26.test2.Service2.setService1方法
service1->com.javacode2018.lesson001.demo26.test2.Service1@6069db50
service2->Service2{service1=com.javacode2018.lesson001.demo26.test2.Service1@6069db50}
案例4:@Autowired標註在方法引數上
Service1
package com.javacode2018.lesson001.demo26.test3;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test3;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
private Service1 service1;
@Autowired
public void injectService1(Service1 service1, String name) { //@1
System.out.println(String.format("%s.injectService1(),{service1=%s,name=%s}", this.getClass().getName(), service1, name));
this.service1 = service1;
}
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
@1:方法上標註了@Autowired,表示會將這個方法作為注入方法,這個方法有2個引數,spring查詢這2個引數對應的bean,然後注入。
第一個引數對應的bean是存在的,第二個是一個String型別的,我們並沒有定義String型別bean,一會看看效果
來個總的配置檔案
package com.javacode2018.lesson001.demo26.test3;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig3 {
}
來個測試用例
@Test
public void test3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'service2': Unsatisfied dependency expressed through method 'injectService1' parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
報錯了,從錯誤資訊中可以看出,通過injectService1方法注入的時候,第二個引數為String型別,spring從容器中沒有找到String型別的候選bean,所以報錯了。
我們可以這麼做
多個引數的時候,方法上面的@Autowire預設對方法中所有引數起效,如果我們想對某個引數進行特定的配置,可以在引數上加上@Autowired,這個配置會覆蓋方法上面的@Autowired配置。
在第二個引數上面加上@Autowired,設定required為false:表示這個bean不是強制注入的,能找到就注入,找不到就注入一個null物件,調整一下程式碼,如下:
@Autowired
public void injectService1(Service1 service1, @Autowired(required = false) String name) { //@1
System.out.println(String.format("%s.injectService1(),{service1=%s,name=%s}", this.getClass().getName(), service1, name));
this.service1 = service1;
}
此時方法的第一個引數被方法上面的@Autowired約束
第二個引數受@Autowired(required = false)約束
再次執行輸出
com.javacode2018.lesson001.demo26.test3.Service2.injectService1(),{service1=com.javacode2018.lesson001.demo26.test3.Service1@59309333,name=null}
service1->com.javacode2018.lesson001.demo26.test3.Service1@59309333
service2->Service2{service1=com.javacode2018.lesson001.demo26.test3.Service1@59309333}
注入成功了,service1有值,name為null
案例5:@Autowired用在欄位上
Service1
package com.javacode2018.lesson001.demo26.test4;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test4;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
}
Service3
package com.javacode2018.lesson001.demo26.test4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service3 {
@Autowired
private Service1 service1;//@1
@Autowired
private Service2 service2;//@2
@Override
public String toString() {
return "Service3{" +
"service1=" + service1 +
", service2=" + service2 +
'}';
}
}
@1和@2:定義了2個欄位,上面都標註了@Autowired,spring會去容器中按照型別查詢這2種型別的bean,然後設定給這2個屬性。
來個總的配置檔案
package com.javacode2018.lesson001.demo26.test4;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig4 {
}
來個測試用例
@Test
public void test4() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
service1->com.javacode2018.lesson001.demo26.test4.Service1@7e07db1f
service2->com.javacode2018.lesson001.demo26.test4.Service2@1189dd52
service3->Service3{service1=com.javacode2018.lesson001.demo26.test4.Service1@7e07db1f, service2=com.javacode2018.lesson001.demo26.test4.Service2@1189dd52}
service3中標註@Autowired的2個屬性都有值了,都被注入成功了。
案例6:@Autowire標註欄位,多個候選者的時候,按欄位名稱注入
IService介面
package com.javacode2018.lesson001.demo26.test5;
public interface IService {
}
介面來2個實現
2個實現類上都標註了@Component註解,都會被註冊到容器中。
Service0
package com.javacode2018.lesson001.demo26.test5;
import org.springframework.stereotype.Component;
@Component
public class Service0 implements IService {
}
Service1
package com.javacode2018.lesson001.demo26.test5;
import org.springframework.stereotype.Component;
@Component
public class Service1 implements IService {
}
來個Service2
package com.javacode2018.lesson001.demo26.test5;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
@Autowired
private IService service1; //@1
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
@1:標註了@Autowired註解,需要注入型別為IService型別的bean,滿足這種型別的有2個:service0和service1
按照上面介紹的候選者查詢過程,最後會注入和欄位名稱一樣的bean,即:service1
來個總的配置類,負責掃描當前包中的元件
package com.javacode2018.lesson001.demo26.test5;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig5 {
}
來個測試用例
@Test
public void test5() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
service0->com.javacode2018.lesson001.demo26.test5.Service0@36902638
service1->com.javacode2018.lesson001.demo26.test5.Service1@223d2c72
service2->Service2{service1=com.javacode2018.lesson001.demo26.test5.Service1@223d2c72}
注意最後一行,service2中的service1被注入了bean:service1
案例7:將指定型別的所有bean,注入到Collection、Map中
注入到Collection中
被注入的型別為Collection型別或者Collection子介面型別,注意必須是介面型別,如:
Collection<IService>
List<IService>
Set<IService>
會在容器中找到所有IService型別的bean,放到這個集合中。
注入到Map中
被注入的型別為Map型別或者Map子介面型別,注意必須是介面型別,如:
Map<String,IService>
會在容器中找到所有IService型別的bean,放到這個Map中,key為bean的名稱,value為bean物件。
來看案例程式碼。
來個介面
package com.javacode2018.lesson001.demo26.test6;
public interface IService {
}
來2個實現類,標註@Component註解
Service0
package com.javacode2018.lesson001.demo26.test6;
import org.springframework.stereotype.Component;
@Component
public class Service0 implements IService {
}
Service1
package com.javacode2018.lesson001.demo26.test6;
import org.springframework.stereotype.Component;
@Component
public class Service1 implements IService {
}
再來個類Service2
package com.javacode2018.lesson001.demo26.test6;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
public class Service2 {
@Autowired
private List<IService> services;
@Autowired
private Map<String, IService> serviceMap;
@Override
public String toString() {
return "Service2{\n" +
"services=" + services +
", \n serviceMap=" + serviceMap +
'}';
}
}
@1:注入IService型別的所有bean
@2:注入一個map
來個總的配置類
package com.javacode2018.lesson001.demo26.test6;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig6 {
}
來個測試用例
@Test
public void test6() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
service0->com.javacode2018.lesson001.demo26.test6.Service0@1189dd52
service1->com.javacode2018.lesson001.demo26.test6.Service1@36bc55de
service2->Service2{
services=[com.javacode2018.lesson001.demo26.test6.Service0@1189dd52, com.javacode2018.lesson001.demo26.test6.Service1@36bc55de],
serviceMap={service0=com.javacode2018.lesson001.demo26.test6.Service0@1189dd52, service1=com.javacode2018.lesson001.demo26.test6.Service1@36bc55de}}
注意看一下上面services和serviceMap的值。
@Autowired原始碼
spring使用下面這個類處理@Autowired註解
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
@Resource:注意依賴物件
作用
和@Autowired註解類似,也是用來注入依賴的物件的,spring容器會對bean中所有欄位、方法進行遍歷,標註有@Resource註解的,都會進行注入。
看一下這個註解定義:
javax.annotation.Resource
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
..其他不常用的引數省略
}
這個註解是javax中定義的,並不是spring中定義的註解。
從定義上可以見,這個註解可以用在任何型別上面、欄位、方法上面。
注意點:
用在方法上的時候,方法引數只能有一個。
@Resource查詢候選者的過程
查詢過程有點複雜,看不懂的可以先跳過,先看後面案例,本文看完之後,可以回頭再來看這個過程。
@Resource標註在欄位上面:假定欄位型別為一個自定義的普通的型別,候選者查詢過程如下
@Autowired標註在方法上或者方法引數上面:假定引數型別為為一個自定義的普通的型別,候選者查詢過程如下:
將指定型別的所有bean注入到Collection中
如果被注入的物件是Collection型別的,可以指定泛型的型別,然後會按照上面的方式查詢所有滿足泛型型別所有的bean
將指定型別的所有bean注入到Map中
如果被注入的物件是Map型別的,可以指定泛型的型別,key通常為String型別,value為需要查詢的bean的型別,然後會按照上面方式查詢所有注入value型別的bean,將bean的name作為key,bean物件作為value,放在HashMap中,然後注入。
@Resource查詢候選者可以簡化為
先按Resource的name值作為bean名稱找->按名稱(欄位名稱、方法名稱、set屬性名稱)找->按型別找->通過限定符@Qualifier過濾->@Primary->@Priority->根據名稱找(欄位名稱或者方法引數名稱)
概括為:先按名稱找,然後按型別找
案例1:將@Resource標註在欄位上
IService介面
package com.javacode2018.lesson001.demo26.test7;
public interface IService {
}
2個實現類
Service0
package com.javacode2018.lesson001.demo26.test7;
import org.springframework.stereotype.Component;
@Component
public class Service0 implements IService {
}
@Component標註的bean名稱預設為service0
Service1
package com.javacode2018.lesson001.demo26.test7;
import org.springframework.stereotype.Component;
@Component
public class Service1 implements IService {
}
@Component標註的bean名稱預設為service1
再來一個類
package com.javacode2018.lesson001.demo26.test7;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@Component
public class Service2 {
@Resource
private IService service1;//@1
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
@1:欄位名稱為service1,按照欄位名稱查詢bean,會找到Service1
來個配置類
package com.javacode2018.lesson001.demo26.test7;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig7 {
}
測試用例
@Test
public void test7() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig7.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
service0->com.javacode2018.lesson001.demo26.test7.Service0@222545dc
service1->com.javacode2018.lesson001.demo26.test7.Service1@5c5eefef
service2->Service2{service1=com.javacode2018.lesson001.demo26.test7.Service1@5c5eefef}
最後一行可以看出注入了service1
如果將Service2中的程式碼調整一下
@Resource
private IService service0;
此時會注入service0這個bean
同樣@Resource可以用在方法上,也可以將所有型別的bean注入到Collection、Map中,這裡就不演示了,重點了解一下候選者查詢的過程,使用上就比較簡單了,@Resource的其他案例,大家可以自己寫寫練練。
下面來說另外幾個註解,也是比較重要的。
@Resource原始碼
spring使用下面這個類處理@Resource註解
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
@Qualifier:限定符
作用
這個單詞的意思是:限定符。
可以在依賴注入查詢候選者的過程中對候選者進行過濾。
看一下其定義:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
可以用在欄位、方法、引數、任意型別、註解上面
有一個引數value
還是來看案例,通過案例理解更容易。
案例1:用在類上
用在類上,你可以理解為給通過@Qulifier給這個bean打了一個標籤。
先來一個介面
package com.javacode2018.lesson001.demo26.test8;
public interface IService {
}
來3個實現類
前2個@Qulifier的value為tag1,第3個實現類為tag2
Service1
package com.javacode2018.lesson001.demo26.test8;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("tag1") //@1
public class Service1 implements IService {
}
@1:tag1
Service2
package com.javacode2018.lesson001.demo26.test8;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("tag1")
public class Service2 implements IService {
}
@1:tag1
Service3
package com.javacode2018.lesson001.demo26.test8;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("tag2")//@1
public class Service3 implements IService {
}
@1:tag2
來一個類,來注入上面幾個bean
package com.javacode2018.lesson001.demo26.test8;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class InjectService {
@Autowired
@Qualifier("tag1") //@1
private Map<String, IService> serviceMap1;
@Autowired
@Qualifier("tag2") //@2
private Map<String, IService> serviceMap2;
@Override
public String toString() {
return "InjectService{" +
"serviceMap1=" + serviceMap1 +
", serviceMap2=" + serviceMap2 +
'}';
}
}
@1:限定符的值為tag1,此時會將類上限定符為tag1的所有bean注入進來
@2:限定符的值為tag2,此時會將類上限定符為tag2的所有bean注入進來
來個配置類
package com.javacode2018.lesson001.demo26.test8;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig8 {
}
測試用例
@Test
public void test8() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig8.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
injectService->InjectService{serviceMap1={service1=com.javacode2018.lesson001.demo26.test8.Service1@9597028, service2=com.javacode2018.lesson001.demo26.test8.Service2@6069db50}, serviceMap2={service3=com.javacode2018.lesson001.demo26.test8.Service3@4efbca5a}}
service1->com.javacode2018.lesson001.demo26.test8.Service1@9597028
service2->com.javacode2018.lesson001.demo26.test8.Service2@6069db50
service3->com.javacode2018.lesson001.demo26.test8.Service3@4efbca5a
注意第一行的輸出,看一下serviceMap1和serviceMap2的值。
serviceMap1注入了@Qulifier的value為tag1的所有IService型別的bean
serviceMap1注入了@Qulifier的value為tag2的所有IService型別的bean
實現了bean分組的效果。
案例2:@Autowired結合@Qulifier指定注入的bean
被注入的型別有多個的時候,可以使用@Qulifier來指定需要注入那個bean,將@Qulifier的value設定為需要注入bean的名稱
看案例程式碼
來個介面
package com.javacode2018.lesson001.demo26.test9;
public interface IService {
}
有2個實現類
2個實現類上面沒有使用@Qulifier註解了
Service1
package com.javacode2018.lesson001.demo26.test9;
import org.springframework.stereotype.Component;
@Component
public class Service1 implements IService {
}
Service2
package com.javacode2018.lesson001.demo26.test9;
import org.springframework.stereotype.Component;
@Component
public class Service2 implements IService {
}
我們可以知道上面2個bean的名稱分別為:service1、service2
來個類,注入IService型別的bean
package com.javacode2018.lesson001.demo26.test9;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class InjectService {
@Autowired
@Qualifier("service2") //@1
private IService service;
@Override
public String toString() {
return "InjectService{" +
"service=" + service +
'}';
}
}
@1:這裡限定符的值為service2,容器中IService型別的bean有2個[service1和service2],當類上沒有標註@Qualifier註解的時候,可以理解為:bean的名稱就是限定符的值,所以@1這裡會匹配到service2
來個配置類
package com.javacode2018.lesson001.demo26.test9;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
@ComponentScan
public class MainConfig9 {
}
來個測試用例
@Test
public void test9() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig9.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
injectService->InjectService{service=com.javacode2018.lesson001.demo26.test9.Service2@223d2c72}
service1->com.javacode2018.lesson001.demo26.test9.Service1@8f4ea7c
service2->com.javacode2018.lesson001.demo26.test9.Service2@223d2c72
從第一行可以看出注入了service1
案例3:用在方法引數上
程式碼
package com.javacode2018.lesson001.demo26.test10;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class InjectService {
private IService s1;
private IService s2;
@Autowired
public void injectBean(@Qualifier("service2") IService s1, @Qualifier("service1") IService s2) { //@1
this.s1 = s1;
this.s2 = s2;
}
@Override
public String toString() {
return "InjectService{" +
"s1=" + s1 +
", s2=" + s2 +
'}';
}
}
@1:方法上標註了@Autowired註解,說明會被注入依賴,2個引數上分別使用了限定符來指定具體需要注入哪個bean
測試用例
@Test
public void test10() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig10.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
injectService->InjectService{s1=com.javacode2018.lesson001.demo26.test10.Service2@55183b20, s2=com.javacode2018.lesson001.demo26.test10.Service1@4f83df68}
service1->com.javacode2018.lesson001.demo26.test10.Service1@4f83df68
service2->com.javacode2018.lesson001.demo26.test10.Service2@55183b20
第一行中的
s1:service2
s2:service1
案例4:用在setter方法上
不管是用在setter方法還是普通方法上面,都是一樣的效果
程式碼
package com.javacode2018.lesson001.demo26.test11;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class InjectService {
private IService s1;
private IService s2;
@Autowired
@Qualifier("service2")
public void setS1(IService s1) {
this.s1 = s1;
}
@Autowired
@Qualifier("service2")
public void setS2(IService s2) {
this.s2 = s2;
}
@Override
public String toString() {
return "InjectService{" +
"s1=" + s1 +
", s2=" + s2 +
'}';
}
}
上面2個setter方法上都有@Autowired註解,並且結合了@Qulifier註解來限定需要注入哪個bean
測試用例
@Test
public void test11() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig11.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
injectService->InjectService{s1=com.javacode2018.lesson001.demo26.test11.Service2@35e2d654, s2=com.javacode2018.lesson001.demo26.test11.Service2@35e2d654}
service1->com.javacode2018.lesson001.demo26.test11.Service1@1bd4fdd
service2->com.javacode2018.lesson001.demo26.test11.Service2@35e2d654
輸出中可以看出:s1為service2,s2為service1
@Primary:設定為主要候選者
注入依賴的過程中,當有多個候選者的時候,可以指定哪個候選者為主要的候選者。
看一下其定義
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {
}
可以用在類上或者方法上面。
通常定義bean常見的有2種方式:
方式1:在類上標註@Component註解,此時可以配合@Primary,標註這個bean為主要候選者
方式2:在配置檔案中使用@Bean註解標註方法,來註冊bean,可以在@Bean標註的方法上加上@Primary,標註這個bean為主要候選bean。
看案例。
案例1:用在類上
來個介面
package com.javacode2018.lesson001.demo26.test12;
public interface IService {
}
2個實現類
Service1
package com.javacode2018.lesson001.demo26.test12;
import org.springframework.stereotype.Component;
@Component
public class Service1 implements IService {
}
Service2
package com.javacode2018.lesson001.demo26.test12;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
@Primary
public class Service2 implements IService {
}
Service2上面使用了@Primary,表示這是個主要的候選者
再來個類,注入IService型別的bean
package com.javacode2018.lesson001.demo26.test12;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class InjectService {
@Autowired
private IService service1; //@1
@Override
public String toString() {
return "InjectService{" +
"service1=" + service1 +
'}';
}
}
@1:容器中IService型別的bean有2個,但是service2為主要的候選者,所以此處會注入service2
總的配置類
package com.javacode2018.lesson001.demo26.test12;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig12 {
}
測試用例
@Test
public void test12() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig12.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
injectService->InjectService{service1=com.javacode2018.lesson001.demo26.test12.Service2@49ec71f8}
service1->com.javacode2018.lesson001.demo26.test12.Service1@1d2adfbe
service2->com.javacode2018.lesson001.demo26.test12.Service2@49ec71f8
案例2:用在方法上,結合@Bean使用
來個介面
package com.javacode2018.lesson001.demo26.test13;
public interface IService {
}
2個實現類
Service1
package com.javacode2018.lesson001.demo26.test13;
public class Service1 implements IService {
}
Service2
package com.javacode2018.lesson001.demo26.test13;
public class Service2 implements IService {
}
InjectService
package com.javacode2018.lesson001.demo26.test13;
import org.springframework.beans.factory.annotation.Autowired;
public class InjectService {
@Autowired
private IService service1;//@1
@Override
public String toString() {
return "InjectService{" +
"service1=" + service1 +
'}';
}
}
使用了@Autowired,需要注入
來個配置類,通過@Bean定義上面3個型別的bean
package com.javacode2018.lesson001.demo26.test13;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class MainConfig13 {
@Bean
public IService service1() {
return new Service1();
}
@Bean
@Primary //@1
public IService service2() {
return new Service2();
}
@Bean
public InjectService injectService() {
return new InjectService();
}
}
上面是一個配置類,定義了3個bean
@1:這個bean被標註為主要的候選者
來個測試用例
@Test
public void test13() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig13.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
service1->com.javacode2018.lesson001.demo26.test13.Service1@6913c1fb
service2->com.javacode2018.lesson001.demo26.test13.Service2@66d18979
injectService->InjectService{service1=com.javacode2018.lesson001.demo26.test13.Service2@66d18979}
注意最後一行,service1注入的是service2這個bean
@Bean定義bean時注入依賴的幾種方式
常見3種方式
硬編碼方式
@Autowired、@Resource的方式
@Bean標註的方法引數的方式
方式1:硬編碼方式
來3個類
Service1
package com.javacode2018.lesson001.demo26.test14;
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test14;
public class Service2 {
}
Service3
package com.javacode2018.lesson001.demo26.test14;
public class Service3 {
private Service1 service1;
private Service2 service2;
public Service1 getService1() {
return service1;
}
public void setService1(Service1 service1) {
this.service1 = service1;
}
public Service2 getService2() {
return service2;
}
public void setService2(Service2 service2) {
this.service2 = service2;
}
@Override
public String toString() {
return "Service3{" +
"service1=" + service1 +
", service2=" + service2 +
'}';
}
}
上面類中會用到service1和service2,提供了對應的setter方法,一會我們通過setter方法注入依賴物件
來個配置類,通過@Bean的方式建立上面物件
package com.javacode2018.lesson001.demo26.test14;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MainConfig14 {
@Bean
public Service1 service1() {
return new Service1();
}
@Bean
public Service2 service2() {
return new Service2();
}
@Bean
public Service3 service3() {
Service3 service3 = new Service3(); //@0
service3.setService1(this.service1()); //@1
service3.setService2(this.service2()); //@2
return service3;
}
}
上面程式碼中通過@Bean定義了3個bean
Service3中需要用到Service1和Service2,注意@1和@2直接呼叫當前方法獲取另外2個bean,注入到service3中
測試用例
@Test
public void test14() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig14.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
service1->com.javacode2018.lesson001.demo26.test14.Service1@41a2befb
service2->com.javacode2018.lesson001.demo26.test14.Service2@6c40365c
service3->Service3{service1=com.javacode2018.lesson001.demo26.test14.Service1@41a2befb, service2=com.javacode2018.lesson001.demo26.test14.Service2@6c40365c}
方式2:@Autowired、@Resource的方式
這種方式就不講了直接在需要注入的物件上面加上這2個註解的任意一個就行了,可以參考文章前面的部分。
方式3:@Bean標註的方法使用引數來進行注入
package com.javacode2018.lesson001.demo26.test15;
import com.javacode2018.lesson001.demo26.test14.Service1;
import com.javacode2018.lesson001.demo26.test14.Service2;
import com.javacode2018.lesson001.demo26.test14.Service3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MainConfig15 {
@Bean
public Service1 service1() {
return new Service1();
}
@Bean
public Service2 service2() {
return new Service2();
}
@Bean
public Service3 service3(Service1 s1, Service2 s2) { //@0
Service3 service3 = new Service3();
service3.setService1(s1); //@1
service3.setService2(s2); //@2
return service3;
}
}
@0:這個地方是關鍵,方法上標註了@Bean,並且方法中是有引數的,spring呼叫這個方法建立bean的時候,會將引數中的兩個引數注入進來。
注入物件的查詢邏輯可以參考上面@Autowired標註方法時查詢候選者的邏輯。
來個測試用例
@Test
public void test15() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig15.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
service1->com.javacode2018.lesson001.demo26.test14.Service1@4009e306
service2->com.javacode2018.lesson001.demo26.test14.Service2@43c1b556
service3->Service3{service1=com.javacode2018.lesson001.demo26.test14.Service1@4009e306, service2=com.javacode2018.lesson001.demo26.test14.Service2@43c1b556}
同樣注入成功了。
其他
@Bean標註的方法引數上使用@Autowired註解
@Bean
public Service3 service3_0(Service1 s1, @Autowired(required = false) Service2 s2) { //@0
Service3 service3 = new Service3();
service3.setService1(s1); //@1
service3.setService2(s2); //@2
return service3;
}
@0:方法由2個引數,第二個引數上標註了@Autowired(required = false),說明第二個引數候選者不是必須的,找不到會注入一個null物件;第一個引數候選者是必須的,找不到會丟擲異常
@Bean結合@Qualifier
package com.javacode2018.lesson001.demo26.test17;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
@Configuration
public class MainConfig17 {
@Bean
@Qualifier("tag1") //@1
public Service1 service1() {
return new Service1();
}
@Bean
@Qualifier("tag1") //@2
public Service2 service2() {
return new Service2();
}
@Bean
@Qualifier("tag2") //@3
public Service3 service3() {
return new Service3();
}
@Bean
public InjectService injectService(@Qualifier("tag1") Map<String, IService> map1) { //@4
InjectService injectService = new InjectService();
injectService.setServiceMap1(map1);
return injectService;
}
}
Service1,Service2,Service3都實現了IService介面
@1,@2,@3這3個方法上面使用了@Bean註解,用來定義3個bean,這3個方法上還是用了@Qualifier註解,用來給這些bean定義標籤,service1()方法類似於下面的寫法:
@Compontent
@Qualifier("tag1")
public class Service1 implements IService{
}
再回到MainConfig17中的@4:引數中需要注入Map<String, IService>,會查詢IService型別的bean,容器中有3個,但是這個引數前面加上了@Qualifier限定符,值為tag1,所以會通過這個過濾,最後滿足的候選者為:[service1,service]
對應測試用例
@Test
public void test17() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig17.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
執行輸出
service1->com.javacode2018.lesson001.demo26.test17.Service1@1190200a
service2->com.javacode2018.lesson001.demo26.test17.Service2@6a2f6f80
service3->com.javacode2018.lesson001.demo26.test17.Service3@45b4c3a9
injectService->InjectService{serviceMap1={service1=com.javacode2018.lesson001.demo26.test17.Service1@1190200a, service2=com.javacode2018.lesson001.demo26.test17.Service2@6a2f6f80}, serviceMap2=null}
注意最後一行serviceMap1,注入了service1和service2
泛型注入
先來2個普通的類
UserModel
package com.javacode2018.lesson001.demo26.test18;
public class UserModel {
}
OrderModel
package com.javacode2018.lesson001.demo26.test18;
public class OrderModel {
}
記住上面2個普通的類UserModel和OrderModel,一會下面會用到。
來個泛型介面
package com.javacode2018.lesson001.demo26.test18;
public interface IDao<T> {
}
上面是個泛型類,類名後面後尖括號
來2個實現類
兩個實現類都會標註@Compontent,交給spring容器管理
UserDao
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.stereotype.Component;
@Component
public class UserDao implements IDao<UserModel> { //@1
}
@1:指定了IDao後面泛型的型別為UserModel
OrderDao
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.stereotype.Component;
@Component
public class OrderDao implements IDao<OrderModel> {//@1
}
@1:指定了IDao後面泛型的型別為OrderModel
在來個泛型型別
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.beans.factory.annotation.Autowired;
public class BaseService<T> {
@Autowired
private IDao<T> dao; //@1
public IDao<T> getDao() {
return dao;
}
public void setDao(IDao<T> dao) {
this.dao = dao;
}
}
BaseService同樣是個泛型類
@1:這個地方要注意了,上面使用了@Autowired,來注入IDao物件
BaseService來2個子類
兩個子類都會標註@Compontent,交給spring容器管理
UserService
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.stereotype.Component;
@Component
public class UserService extends BaseService<UserModel> {//@1
}
@1:指定了BaseService後面泛型的型別為UserModel
OrderService
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.stereotype.Component;
@Component
public class OrderService extends BaseService<OrderModel> {//@1
}
@1:指定了BaseService後面泛型的型別為OrderModel
UserService和OrderService繼承了BaseService,所以一會BaseService中的dao屬性會被注入,一會我們關注一下dao這個屬性的值,會是什麼樣的
來個總的配置類
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig18 {
}
上面有@CompontentScan註解,會自動掃描當前包中的所有類,並進行自動注入
來個測試用例
@Test
public void test18() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig18.class);
System.out.println(context.getBean(UserService.class).getDao());
System.out.println(context.getBean(OrderService.class).getDao());
}
上面程式碼中會將兩個service中的dao輸出,我們來看一下效果
執行輸出
com.javacode2018.lesson001.demo26.test18.UserDao@6adbc9d
com.javacode2018.lesson001.demo26.test18.OrderDao@4550bb58
結果就是重點了,dao屬性並沒有指定具體需要注入那個bean,此時是根據尖括號中的泛型型別來匹配的,這個功能也是相當厲害的。
總結
這篇文中內容比較多,每個案例大家都要去敲一遍,不清楚的,可以留言,或者直接微信中@我
需要掌握@Autowired註解和@Resource註解中候選者查詢的過程
@Autowired:先通過型別找,然後通過名稱找
@Resource:先通過名稱找,然後通過型別找
@Autowired和@Resource,建議開發中使用@Autowired來實現依賴注入,spring的註解用起來更名正言順一些
@Qulifier:限定符,可以用在類上;也可以用在依賴注入的地方,可以對候選者的查詢進行過濾
@Primary:多個候選者的時候,可以標註某個候選者為主要的候選者
@Bean中注入依賴的3種方式需要掌握
掌握泛型注入的使用
主要還是掌握候選者的查詢過程,過程熟悉了,其他的都是小意思,回頭再去看看上面的幾個查詢的流程圖。
案例原始碼
https://gitee.com/javacode2018/spring-series
路人甲java所有案例程式碼以後都會放到這個上面,大家watch一下,可以持續關注動態。
Spring系列
更多好文章
感謝大家的閱讀,也歡迎您把這篇文章分享給更多的朋友一起閱讀!謝謝!
路人甲java
▲長按圖片識別二維碼關注
路人甲Java:工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
相關文章
- 初學Java你有這些疑惑嗎?本文給你幾個建議Java
- python的五個特點,你知道幾個?Python
- 入門Java你需要了解的幾個知識要點!Java
- 這20個Docker Command,有幾個是你會的?Docker
- 從月薪5000到月薪5萬,Get到這些技能你也可以!
- 十個python熱門專案,你知道幾個Python
- 學會這幾點,你也能成為面試殺手!面試
- 理解這幾張圖,你就是js小牛了JS
- 也許,這就是一個新的開始吧……
- 這幾個python常用的庫你必須知道!Python
- 你知道黑客的入侵方式都有哪些嗎?這些你知道幾個?黑客
- 你可能也罵過這兩個面試題!面試題
- 速看!這10個開源安全工具你知道幾個?
- 這9個鮮為人知的Python庫,你用過幾個?Python
- 面試中的這些坑,你踩過幾個?面試
- Spring中11個最常用的擴充套件點,你知道幾個?Spring套件
- 你敢信?就是這個Netty的網路框架差點把我整瘋了,哭jjNetty框架
- 新手入門Web前端,你需要克服這幾點困難Web前端
- 用了這個方法你的公眾號也能1個月快速起號
- Linux伺服器有哪些防護要點?這幾個你知道嗎?Linux伺服器
- 初識python你應該知道的6個知識點!Python
- Docker容器系列文章|這20個Docker Command,有幾個是你會的?Docker
- 面試時這麼問你Spring Boot,你能答對幾個?面試Spring Boot
- 這樣做,你的APP也能成為下一個爆款APP
- 這幾個好用的Python開源框架,你都知道嗎?Python框架
- 這幾個高階前端常用的API,你用到了嗎?前端API
- 瞭解這12個概念,讓你的JavaScript水平更上一層樓JavaScript
- Linux系統的六大特點,你知道幾個?Linux
- React 效能最佳化,你需要知道的幾個點React
- 發現一個開源專案優化點,點進來就是你的了優化
- 一道面試題牽出12個前端硬核知識點,你能答出幾個?面試題前端
- ERP實施的關鍵點,你的企業做到幾個?
- 不花錢幾分鐘讓你的站點也支援httpsHTTP
- 這些Git事故災難, 你經歷過幾個?Git
- 這些強大的辦公軟體你用過幾個
- 初學Java,這三個階段你經歷過嗎?Java
- 自媒體實時熱點怎麼追?這幾個工具你必須知道!
- 新手怎麼做自媒體影片?這幾個要點幫你提高播放量