spring成神之路第八篇:自動注入(autowire)詳解,高手在於堅持!


本文內容

  • 手動注入的不足

  • Class.isAssignableFrom方法介紹

  • 3種自動注入方式詳解及案例

  • 按名稱自動注入

  • 按類型自動注入

  • 按構造器進行自動注入

  • 按類型自動注入某種類型的所有bean給List和Map(重點)

  • autowire=default介紹

  • 總結

  • 案例源碼

手動注入的不足

上篇文章中介紹了依賴注入中的手動注入,所謂手動注入是指在xml中采用硬編碼的方式來配置注入的對象,比如通過構造器注入或者set方法注入,這些注入的方式都存在不足,比如下面代碼:

public class A{
    private B b;
    private C c;
    private D d;
    private E e;
    ....
    private N n;

    //上面每個private屬性的get和set方法
}

使用spring容器來管理,xml配置如下:

<bean class="b" class="B"/>
<bean class="c" class="C"/>
<bean class="d" class="D"/>
<bean class="e" class="E"/>
<bean class="a" class="A">
    <property name="b" ref="b"/>
    <property name="c" ref="c"/>
    <property name="d" ref="d"/>
    <property name="e" ref="e"/>
    ...
</bean>

上面的注入存在的問題:

  • 如果需要注入的對象比較多,比如A類中有幾十個屬性,那么上面的property屬性是不是需要寫幾十個,此時配置文件代碼量暴增

  • 如果A類中新增或者刪除了一些依賴,還需要手動去調整bean xml中的依賴配置信息,否則會報錯

  • 總的來說就是不利於維護和擴展

為了解決上面這些問題,spring為我們提供了更強大的功能:自動注入

在介紹自動注入之前需要先介紹一些基礎知識。

Class.isAssignableFrom方法

用法

isAssignableFrom是Class類中的一個方法,看一下這個方法的定義:

public native boolean isAssignableFrom(Class<?> cls)

用法如下:

c1.isAssignableFrom(c2)

用來判斷c2和c1是否相等,或者c2是否是c1的子類

案例

@Test
public void isAssignableFrom(){
    System.out.println(Object.class.isAssignableFrom(Integer.class)); //true
    System.out.println(Object.class.isAssignableFrom(int.class)); //false
    System.out.println(Object.class.isAssignableFrom(List.class)); //true
    System.out.println(Collection.class.isAssignableFrom(List.class)); //true
    System.out.println(List.class.isAssignableFrom(Collection.class)); //false
}

自動注入

自動注入是采用約定大約配置的方式來實現的,程序和spring容器之間約定好,遵守某一種都認同的規則,來實現自動注入。

xml中可以在bean元素中通過autowire屬性來設置自動注入的方式:

<bean id="" class="" autowire="byType|byName|constructor|default" />
  • byteName:按照名稱進行注入

  • byType:按類型進行注入

  • constructor:按照構造方法進行注入

  • default:默認注入方式

下面我們詳解每種注入方式的用法。

按照名稱進行注入(byName)

用法

autowire設置為byName

<bean id="" class="X類" autowire="byName"/>

spring容器會按照set屬性的名稱去容器中查找同名的bean對象,然后將查找到的對象通過set方法注入到對應的bean中,未找到對應名稱的bean對象則set方法不進行注入

需要注入的set屬性的名稱和被注入的bean的名稱必須一致。

案例

DiByName.java
package com.javacode2018.lesson001.demo6;

/**
 * 按照名稱自動注入
 */
public class DiAutowireByName {
    public static class Service1 { //@1
        private String desc;

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        @Override
        public String toString() {
            return "Service1{" +
                    "desc='" + desc + '\'' +
                    '}';
        }
    }

    public static class Service2 { //@1
        private String desc;

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        @Override
        public String toString() {
            return "Service2{" +
                    "desc='" + desc + '\'' +
                    '}';
        }
    }

    private Service1 service1;//@3
    private Service2 service2;//@4

    public Service1 getService1() {
        return service1;
    }

    public void setService1(Service1 service1) {
        System.out.println("setService1->" + service1);
        this.service1 = service1;
    }

    public Service2 getService2() {
        return service2;
    }

    public void setService2(Service2 service2) {
        System.out.println("setService2->" + service2);
        this.service2 = service2;
    }

    @Override
    public String toString() {
        return "DiAutowireByName{" +
                "service1=" + service1 +
                ", service2=" + service2 +
                '}';
    }
}

這個類中有2個屬性,名稱為:

  • service1

  • service2

這兩個屬性都有對應的set方法。

下面我們在bean xml中定義2個和這2個屬性同名的bean,然后使用按照名稱進行自動注入。

diAutowireByName.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

    <bean id="service1" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service1">
        <property name="desc" value="service1"/>
    </bean>
    <bean id="service2" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service2">
        <property name="desc" value="service2"/>
    </bean>
    <bean id="service2-1" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service2">
        <property name="desc" value="service2-1"/>
    </bean>

    <!-- autowire:byName 配置按照name進行自動注入 -->
    <bean id="diAutowireByName1" class="com.javacode2018.lesson001.demo6.DiAutowireByName" autowire="byName"/>

    <!-- 當配置了自動注入,還可以使用手動的方式自動注入進行覆蓋,手動的優先級更高一些 -->
    <bean id="diAutowireByName2" class="com.javacode2018.lesson001.demo6.DiAutowireByName" autowire="byName">
        <property name="service2" ref="service2-1"/>
    </bean>

</beans>

上面注釋認真看一下。

@1:定義了一個名稱為service1的bean

@2:定義了一個名稱為service2的bean

@3:定義diAutowireByName需要將autowire的值置為byName,表示按名稱進行自動注入。

spring容器創建diAutowireByName對應的bean時,會遍歷DiAutowireByName類中的所有set方法,然后得到set對應的屬性名稱列表:{"service1","service2"},然后遍歷這屬性列表,在容器中查找和屬性同名的bean對象,然后調用屬性對應的set方法,將bean對象注入進去

測試用例
package com.javacode2018.lesson001.demo6;

import com.javacode2018.lesson001.demo5.IocUtils;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 公眾號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術干貨!堅信用技術改變命運,讓家人過上更體面的生活!
 * xml中自動注入配置案例
 */
public class DiAutowireTest {

    /**
     * 按照名稱進行注入
     */
    @Test
    public void diAutowireByName() {
        String beanXml = "classpath:/com/javacode2018/lesson001/demo6/diAutowireByName.xml";
        ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
        System.out.println(context.getBean("diAutowireByName"));
    }
}
效果

運行diAutowireByName輸出:

setService1->Service1{desc='service1'}
setService2->Service2{desc='service2'}
setService2->Service2{desc='service2-1'}
setService1->Service1{desc='service1'}
DiAutowireByName{service1=Service1{desc='service1'}, service2=Service2{desc='service2'}}
DiAutowireByName{service1=Service1{desc='service1'}, service2=Service2{desc='service2-1'}}

優缺點

按名稱進行注入的時候,要求名稱和set屬性的名稱必須同名,相對於硬編碼的方式注入,確實節省了不少代碼。

按照類型進行自動注入

用法

autowire設置為byType

<bean id="" class="X類" autowire="byType"/>

spring容器會遍歷x類中所有的set方法,會在容器中查找和set參數類型相同的bean對象,將其通過set方法進行注入,未找到對應類型的bean對象則set方法不進行注入。

需要注入的set屬性的類型和被注入的bean的類型需要滿足isAssignableFrom關系。

按照類型自動裝配的時候,如果按照類型找到了多個符合條件的bean,系統會報錯

set方法的參數如果是下面的類型或者下面類型的數組的時候,這個set方法會被跳過注入:

Object,Boolean,boolean,Byte,byte,Character,char,Double,double,Float,float,Integer,int,Long,Short,shot,Enum,CharSequence,Number,Date,java.time.temporal.Temporal,java.net.URI,java.net.URI,java.util.Locale,java.lang.Class

來看看案例吧。

案例

public class People {   //類的關系
    private Teacher teacher;
}
 xml配置
<bean id="teacher123" class="com.test.Teacher"></bean>
<bean id="people" class="com.test.People" autowire="byType"></bean>  

spring 容器中不可以出現兩個相同類型的<bean>,否則報錯

<bean id="teacher123" class="com.test.Teacher"></bean>
<bean id="teacher124" class="com.test.Teacher"></bean>
<bean id="people" class="com.test.People" autowire="byType"></bean>

輸出報錯信息:expected single matching bean but found 2: teacher123,teacher124

注入類型匹配的所有bean(重點)

按照類型注入還有2中比較牛逼的用法

  • 一個容器中滿足某種類型的bean可以有很多個,將容器中某種類型中的所有bean,通過set方法注入給一個java.util.List<需要注入的Bean的類型或者其父類型或者其接口>對象

  • 將容器中某種類型中的所有bean,通過set方法注入給一個java.util.Map<String,需要注入的Bean的類型或者其父類型或者其接口>對象

來看個案例就懂了。

DiAutowireByTypeExtend.java
package com.javacode2018.lesson001.demo6;

import java.util.List;
import java.util.Map;

/**
 * 滿足條件的bean注入到集合中(重點)
 */
public class DiAutowireByTypeExtend {

    //定義了一個接口
    public interface IService1 {
    }

    public static class BaseServie {
        private String desc;

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        @Override
        public String toString() {
            return "BaseServie{" +
                    "desc='" + desc + '\'' +
                    '}';
        }
    }

    //Service1實現了IService1接口
    public static class Service1 extends BaseServie implements IService1 {

    }

    //Service1實現了IService1接口
    public static class Service2 extends BaseServie implements IService1 {
    }

    private List<IService1> serviceList;//@1
    private List<BaseServie> baseServieList;//@2
    private Map<String, IService1> service1Map;//@3
    private Map<String, BaseServie> baseServieMap;//@4

    public List<IService1> getServiceList() {
        return serviceList;
    }

    public void setServiceList(List<IService1> serviceList) {//@5
        this.serviceList = serviceList;
    }

    public List<BaseServie> getBaseServieList() {
        return baseServieList;
    }

    public void setBaseServieList(List<BaseServie> baseServieList) {//@6
        this.baseServieList = baseServieList;
    }

    public Map<String, IService1> getService1Map() {
        return service1Map;
    }

    public void setService1Map(Map<String, IService1> service1Map) {//@7
        this.service1Map = service1Map;
    }

    public Map<String, BaseServie> getBaseServieMap() {
        return baseServieMap;
    }

    public void setBaseServieMap(Map<String, BaseServie> baseServieMap) {//@8
        this.baseServieMap = baseServieMap;
    }

    @Override
    public String toString() { //9
        return "DiAutowireByTypeExtend{" +
                "serviceList=" + serviceList +
                ", baseServieList=" + baseServieList +
                ", service1Map=" + service1Map +
                ", baseServieMap=" + baseServieMap +
                '}';
    }
}

@1,@2,@3,@4:定義了4個屬性,都是泛型類型的,都有對應的set方法。

@5:參數類型是List<BaseServie>,這個集合集合中元素的類型是BaseServie,spring會找到容器中所有滿足BaseServie.isAssignableFrom(bean的類型)的bean列表,將其通過@5的set方法進行注入。

@6:同@5的代碼

@7:這個參數類型是一個map了,map的key是string類型,value是IService1類型,spring容器會將所有滿足IService1類型的bean找到,按照name->bean對象這種方式丟到一個map中,然后調用@7的set方法進行注入,最后注入的這個map就是bean的名稱和bean對象進行映射的一個map對象。

@8:同@7的代碼

@9:重寫了toString方法,輸出的時候好看一些

測試用例

DiAutowireTest新增一個方法:

/**
 * 按照類型注入集合
 */
@Test
public void diAutowireByTypeExtend() {
    String beanXml = "classpath:/com/javacode2018/lesson001/demo6/diAutowireByTypeExtend.xml";
    ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
    //從容器中獲取DiAutowireByTypeExtend
    DiAutowireByTypeExtend diAutowireByTypeExtend = context.getBean(DiAutowireByTypeExtend.class);
    //輸出diAutowireByTypeExtend中的屬性看一下
    System.out.println("serviceList:" + diAutowireByTypeExtend.getServiceList());
    System.out.println("baseServieList:" + diAutowireByTypeExtend.getBaseServieList());
    System.out.println("service1Map:" + diAutowireByTypeExtend.getService1Map());
    System.out.println("baseServieMap:" + diAutowireByTypeExtend.getBaseServieMap());
}
效果

運行diAutowireByTypeExtend輸出:

serviceList:[BaseServie{desc='service1-1'}, BaseServie{desc='service1-2'}, BaseServie{desc='service2-1'}]
baseServieList:[BaseServie{desc='service1-1'}, BaseServie{desc='service1-2'}, BaseServie{desc='service2-1'}]
service1Map:{service1-1=BaseServie{desc='service1-1'}, service1-2=BaseServie{desc='service1-2'}, service2-1=BaseServie{desc='service2-1'}}
baseServieMap:{service1-1=BaseServie{desc='service1-1'}, service1-2=BaseServie{desc='service1-2'}, service2-1=BaseServie{desc='service2-1'}}

下面我們來介紹另外一種自動注入方式。

按照構造函數進行自動注入

用法

autowire設置為constructor

<bean id="" class="X類" autowire="constructor"/>

spring會找到x類中所有的構造方法(一個類可能有多個構造方法),然后將這些構造方法進行排序(先按修飾符進行排序,public的在前面,其他的在后面,如果修飾符一樣的,會按照構造函數參數數量倒敘,也就是采用貪婪的模式進行匹配,spring容器會盡量多注入一些需要的對象)得到一個構造函數列表,會輪詢這個構造器列表,判斷當前構造器所有參數是否在容器中都可以找到匹配的bean對象,如果可以找到就使用這個構造器進行注入,如果不能找到,那么就會跳過這個構造器,繼續采用同樣的方式匹配下一個構造器,直到找到一個合適的為止。

來看看案例吧。

案例

DiAutowireByConstructor.java
package com.javacode2018.lesson001.demo6;

/**
 * 構造函數的方式進行自動注入
 */
public class DiAutowireByConstructor {

    public static class BaseServie {
        private String desc;

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        @Override
        public String toString() {
            return "BaseServie{" +
                    "desc='" + desc + '\'' +
                    '}';
        }
    }

    //Service1實現了IService1接口
    public static class Service1 extends BaseServie {
    }

    //Service1實現了IService1接口
    public static class Service2 extends BaseServie {
    }

    private Service1 service1;
    private Service2 service2;

    public DiAutowireByConstructor() { //@0
    }

    public DiAutowireByConstructor(Service1 service1) { //@1
        System.out.println("DiAutowireByConstructor(Service1 service1)");
        this.service1 = service1;
    }

    public DiAutowireByConstructor(Service1 service1, Service2 service2) { //@2
        System.out.println("DiAutowireByConstructor(Service1 service1, Service2 service2)");
        this.service1 = service1;
        this.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 "DiAutowireByConstructor{" +
                "service1=" + service1 +
                ", service2=" + service2 +
                '}';
    }
}

@1:1個參數的構造函數

@2:2個參數的構造函數

2個有參構造函數第一行都打印了一段文字,一會在輸出中可以看到代碼是調用了那個構造函數創建對象。

diAutowireByConstructor.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

    <bean class="com.javacode2018.lesson001.demo6.DiAutowireByConstructor$Service1">
        <property name="desc" value="service1"/>
    </bean>

    <bean id="diAutowireByConstructor" class="com.javacode2018.lesson001.demo6.DiAutowireByConstructor"
          autowire="constructor"/>

</beans>
測試用例

DiAutowireTest新增一個方法

/**
 * 構造函數的方式進行自動注入
 */
@Test
public void diAutowireByConstructor() {
    String beanXml = "classpath:/com/javacode2018/lesson001/demo6/diAutowireByConstructor.xml";
    ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
    System.out.println(context.getBean("diAutowireByConstructor"));
}
效果

運行diAutowireByConstructor輸出:

DiAutowireByConstructor(Service1 service1)
DiAutowireByConstructor{service1=BaseServie{desc='service1'}, service2=null}

從輸出中可以看到調用的是DiAutowireByConstructor類中的第一個構造函數注入service1 bean。

構造函數匹配采用貪婪匹配,多個構造函數結合容器找到一個合適的構造函數,最匹配的就是第一個有參構造函數,而第二個有參構造函數的第二個參數在spring容器中找不到匹配的bean對象,所以被跳過了。

我們在diAutowireByConstructor.xml加入Service2的配置:

<bean class="com.javacode2018.lesson001.demo6.DiAutowireByConstructor$Service2">
    <property name="desc" value="service2"/>
</bean>

再來運行一下diAutowireByConstructor輸出:

DiAutowireByConstructor(Service1 service1, Service2 service2)
DiAutowireByConstructor{service1=BaseServie{desc='service1'}, service2=BaseServie{desc='service2'}}

此時可以看到第二個有參構造函數被調用了,滿足了貪婪方式的注入原則,最大限度的注入所有依賴的對象。

autowire=default

用法

bean xml的根元素為beans,注意根元素有個default-autowire屬性,這個屬性可選值有(no|byName|byType|constructor|default),這個屬性可以批量設置當前文件中所有bean的自動注入的方式,bean元素中如果省略了autowire屬性,那么會取default-autowire的值作為其autowire的值,而每個bean元素還可以單獨設置自己的autowire覆蓋default-autowire的配置,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"
       default-autowire="byName">

</beans>

案例

diAutowireByDefault.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"
       default-autowire="byName"> //@1

    <bean id="service1" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service1">
        <property name="desc" value="service1"/>
    </bean>
    <bean id="service2" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service2">
        <property name="desc" value="service2"/>
    </bean>
    <bean id="service2-1" class="com.javacode2018.lesson001.demo6.DiAutowireByName$Service2">
        <property name="desc" value="service2-1"/>
    </bean>

    <!-- autowire:default,會采用beans中的default-autowire指定的配置 -->
    <bean id="diAutowireByDefault1" class="com.javacode2018.lesson001.demo6.DiAutowireByName" autowire="default"/> //@2

    <!-- autowire:default,會采用beans中的default-autowire指定的配置,還可以使用手動的方式自動注入進行覆蓋,手動的優先級更高一些 -->
    <bean id="diAutowireByDefault2" class="com.javacode2018.lesson001.demo6.DiAutowireByName" autowire="default"> //@3
        <property name="service2" ref="service2-1"/>
    </bean>

</beans>

注意上面的@1配置的default-autowire="byName",表示全局默認的自動注入方式是:按名稱注入

@2和@3的autowire=default,那么注入方式會取default-autowire的值。

測試用例

DiAutowireTest中新增一個方法

/**
 * autowire=default
 */
@Test
public void diAutowireByDefault() {
    String beanXml = "classpath:/com/javacode2018/lesson001/demo6/diAutowireByDefault.xml";
    ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
    System.out.println(context.getBean("diAutowireByDefault1"));
    System.out.println(context.getBean("diAutowireByDefault2"));
}
效果

運行diAutowireByDefault輸出

setService1->Service1{desc='service1'}
setService2->Service2{desc='service2'}
setService2->Service2{desc='service2-1'}
setService1->Service1{desc='service1'}
DiAutowireByName{service1=Service1{desc='service1'}, service2=Service2{desc='service2'}}
DiAutowireByName{service1=Service1{desc='service1'}, service2=Service2{desc='service2-1'}}

總結

  • xml中手動注入存在的不足之處,可以通過自動注入的方式來解決,本文介紹了3中自動注入:通過名稱自動注入、通過類型自動注入、通過構造器自動注入

  • 類型注入中有個比較重要的是注入匹配類型所有的bean,可以將某種類型所有的bean注入給一個List對象,可以將某種類型的所有bean按照`bean名稱->bean對象`的映射方式注入給一個Map對象,這種用法比較重要,用途比較大,要掌握

  • spring中還有其他自動注入的方式,用起來會更爽,后面的文章中我們會詳細介紹。

案例源碼

鏈接:https://pan.baidu.com/s/1p6rcfKOeWQIVkuhVybzZmQ 
提取碼:zr99

來源:https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933974&idx=2&sn=7c9cc4e1f2c0f4cb83e985b593f2b6fb&chksm=88621e68bf15977e9451262d440c21e0abf622e54162beef838ba8a9512c7eac0bb8b8852527&token=2030963208&lang=zh_CN&scene=21#wechat_redirect

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM