Spring系列第十篇 primary可以解決什麼問題?

qwer1030274531發表於2020-10-31

存在的問題以及解決方案

直接上案例,透過案例來看技術是如何使用的:

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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章