存在的問題以及解決方案
直接上案例,透過案例來看技術是如何使用的:
package com.javacode2018.lesson001.demo8;public class NormalBean {
public interface IService{} //@1
public static class ServiceA implements IService{} //@2
public static class ServiceB implements IService{} //@3}1234567
上面程式碼很簡單,@1:定義了一個介面IService,@2和@3建立了兩個類都實現了IService介面。
下面我們透過spring來定義ServiceA和ServiceB兩個bean,配置檔案(normalBean.xml)如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
xmlns:xsi="
xsi:schemaLocation="
/spring-beans-4.3.xsd">
<bean id="serviceA" class="com.javacode2018.lesson001.demo8.NormalBean$ServiceA"/>
<bean id="serviceB" class="com.javacode2018.lesson001.demo8.NormalBean$ServiceB"/></beans>1234567891011
來個測試用例來從spring容器中獲取上面定義的bean物件,如下:
package com.javacode2018.lesson001.demo8;import com.javacode2018.lesson001.demo5.IocUtils;import org.junit.Test;import org.springframework.context.support.ClassPathXmlApplicationContext;/**
* bean元素的primary屬性可以解決什麼問題?
*/public class PrimaryTest {
@Test
public void normalBean() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo8/normalBean.xml";
ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
//下面我們透過spring容器的T getBean(Class<T> requiredType)方法獲取容器中對應的bean
NormalBean.IService service = context.getBean(NormalBean.IService.class); //@1
System.out.println(service);
}}12345678910111213141516171819
注意@1的程式碼,從spring容器中在容器中查詢NormalBean.IService.class型別的bean物件,我們來執行一下看看效果,部分輸出如下:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.javacode2018.lesson001.demo8.NormalBean$IService' available: expected single matching bean but found 2: serviceA,serviceB
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1180)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:416)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1126)1234567
發生異常了,錯誤中有一段提示比較重要,如下:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.javacode2018.lesson001.demo8.NormalBean$IService' available: expected single matching bean but found 2: serviceA,serviceB1
這個詳細說出了錯誤原因:spring容器中定義了2個bean,分別是serviceA和serviceB,這兩個bean物件都實現了IService介面,而用例中我們想從容器中獲取IService介面對應的bean,此時容器中有2個候選者(serviceA和serviceB)滿足我們的需求,此時spring容器不知道如何選擇,到底是返回serviceA呢還是返回serviceB呢?spring容器也懵逼了,所以報錯了。
再來看一個透過setter方法注入的案例:
package com.javacode2018.lesson001.demo8;public class SetterBean {
public interface IService{} //@1
public static class ServiceA implements IService{} //@2
public static class ServiceB implements IService{} //@3
private IService service;
public void setService(IService service) {
this.service = service;
}}12345678910111213
下面我們透過xml來定義SetterBean,並且使用setter方式將IService注入到SetterBean中,配置如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
xmlns:xsi="
xsi:schemaLocation="
/spring-beans-4.3.xsd">
<bean id="serviceA" class="com.javacode2018.lesson001.demo8.SetterBean$ServiceA"/>
<bean id="serviceB" class="com.javacode2018.lesson001.demo8.SetterBean$ServiceA"/>
<bean id="setterBean" class="com.javacode2018.lesson001.demo8.SetterBean" autowire="byType" /></beans>1234567891011
注意上面setterBean的定義,autowire="byType"採用了按照型別自動注入的方式,容器啟動的時候,會自動取呼叫SetterBean的setService方法,在容器中查詢和這個方法引數型別匹配的bean,將查詢的bean透過setService方法注入進去。
來個測試用例,PrimaryTest中加個方法:
@Testpublic void setterBean() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo8/setterBean.xml";
ClassPathXmlApplicationContext context = IocUtils.context(beanXml);}12345
執行輸出:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'setterBean' defined in class path resource [com/javacode2018/lesson001/demo8/setterBean.xml]: Unsatisfied dependency expressed through bean property 'service'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.javacode2018.lesson001.demo8.SetterBean$IService' available: expected single matching bean but found 2: serviceA,serviceB
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1526)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1406)1234
錯誤中重點資訊:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.javacode2018.lesson001.demo8.SetterBean$IService' available: expected single matching bean but found 2: serviceA,serviceB1
容器中去找IService介面對應的bean,期望有一個匹配的,實際上卻找到了2個匹配的,不知道如何選擇,報錯了。
上面2個案例報的異常都是下面這個異常:
org.springframework.beans.factory.NoUniqueBeanDefinitionException1
當希望從容器中獲取到一個bean物件的時候,容器中卻找到了多個匹配的bean,此時spring不知道如何選擇了,處於懵逼狀態,就會報這個異常。
spring中可以透過bean元素的primary屬性來解決這個問題,可以透過這個屬性來指定當前bean為主要候選者,當容器查詢一個bean的時候,如果容器中有多個候選者匹配的時候,此時spring會返回主要的候選者。
下面我們使用primary來解決上面案例的問題:
package com.javacode2018.lesson001.demo8;public class PrimaryBean {
public interface IService{} //@1
public static class ServiceA implements IService{} //@2
public static class ServiceB implements IService{} //@3
private IService service;
public void setService(IService service) {
this.service = service;
}
@Override
public String toString() {
return "PrimaryBean{" +
"service=" + service +
'}';
}}1234567891011121314151617181920
spring配置檔案如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
xmlns:xsi="
xsi:schemaLocation="
/spring-beans-4.3.xsd">
<bean id="serviceA" class="com.javacode2018.lesson001.demo8.PrimaryBean$ServiceA" primary="true"/>
<bean id="serviceB" class="com.javacode2018.lesson001.demo8.PrimaryBean$ServiceA"/>
<bean id="setterBean" class="com.javacode2018.lesson001.demo8.PrimaryBean" autowire="byType" /></beans>1234567891011
上面配置中我們將serviceA的primary屬性置為true了,將其置為主要候選者,容器中如果查詢bean的時候,如果有多個匹配的,就以他為主。
我們來個測試用例:
@Testpublic void primaryBean() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo8/primaryBean.xml";
ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
PrimaryBean.IService service = context.getBean(PrimaryBean.IService.class); //@1
System.out.println(service);
PrimaryBean primaryBean = context.getBean(PrimaryBean.class); //@2
System.out.println(primaryBean);}123456789
@1:從容器中查詢IService型別匹配的bean,這個介面有2個實現類(ServiceA和Service2),這類在容器中都定義了,但是serviceA為主要的bean,所以這行程式碼會返回serviceA
@2:從容器中查詢PrimaryBean型別的bean,容器中有一個,這個bean按照byType預設注入IService介面匹配的bean,注入的時候如果候選者有多個,以primary="true"的bean為主來注入,所以此處會注入service2
我們來執行一下,看看和分析的是否一致,執行輸出:
com.javacode2018.lesson001.demo8.PrimaryBean$ServiceA@7b227d8dPrimaryBean{service=com.javacode2018.lesson001.demo8.PrimaryBean$ServiceA@7b227d8d}12
上面輸出確實都是ServiceA,和我們分析的一致。
chengdou/
還有當候選者中如果有多個bean都將primary置為true,此時spring還是會懵逼的,也會報錯,不知道如何選擇了。
總結
sichuan/
當從容器中查詢一個bean的時候,如果容器中出現多個Bean候選者時,可以透過primary="true"將當前bean置為首選者,那麼查詢的時候就會返回主要的候選者,否則將丟擲異常。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2731317/,如需轉載,請註明出處,否則將追究法律責任。