Spring学习第5篇:自动注入(autowire)详解

       大家家好,我是一名网络怪咖,北漂五年。相信大家和我一样,都有一个大厂梦,作为一名资深Java选手,深知Spring重要性,现在普遍都使用SpringBoot来开发,面试的时候SpringBoot原理也是经常会问到,SpringBoot是为了简化Spring开发,但是底层仍然是Spring。如果不了解Spring源码,那就更别提SpringBoot源码了,接下来我准备用两个月时间,从基础到源码彻彻底底的学习一遍Spring。

       学习框架一定要踏踏实实一步一个脚印的学,很多人都比较喜欢急功近利,选择跳着学,包括我有时候也是,最终会发现你不但节约不了时间,反而会更耗时,而且学着学着很容易放弃。

目录

    • 一、手动注入的不足
    • 二、isAssignableFrom方法
    • 三、自动注入
      • 3.1.按照名称进行注入(byName)
      • 3.2.按照类型进行自动注入(byType)
      • 3.3.按照构造函数进行自动注入(constructor)
      • 3.4.autowire=default
      • 3.5.autowire=no
    • 四、总结

一、手动注入的不足

上篇文章中介绍了依赖注入中的手动注入,所谓手动注入是指在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为我们提供了更强大的功能:自动注入

二、isAssignableFrom方法

在介绍自动注入之前需要先介绍一些基础知识。

isAssignableFrom是Class类中的一个方法,看一下这个方法的定义:

public native boolean isAssignableFrom(Class<?> cls)

用法如下:

c1.isAssignableFrom(c2)

用来判断c2和c1是否相等,或者c2是否是c1的子类。

案例:

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

public class Client {
    public static void main(String[] args) {
        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:默认注入方式
  • no:不使用自动注入

下面我们详解每种注入方式的用法。

3.1.按照名称进行注入(byName)

autowire设置为byName

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

setter方法,以set开头,后跟首字母大写的属性名,如:setUserName,简单属性一般只有一个方法参数,方法返回值通常为void;

以setUserName为例,spring容器会按照UserName名称的首字母小写userName去容器中查找同名的bean对象,然后将查找到的对象通过set方法注入到对应的bean中,未找到对应名称的bean对象则set方法不进行注入。set方法的参数类型必须和找到的bean是一个类型,否则类型异常!

Spring学习第5篇:自动注入(autowire)详解_第1张图片

案例:

public class TestService {

    public TestService() {
        System.out.println("TestService初始化了");
    }
}
public class TestController {

    private TestService testService;

    public TestController() {
        System.out.println("testController初始化了");
    }
	
	// 这里注意他是按照set方法的参数名称去容器当中找这个对象的
    public void setTestService(TestService testService) {
        System.out.println("testService的set方法执行了");
        this.testService = testService;
    }

    @Override
    public String toString() {
        return "TestController{" +
                "testService=" + testService +
                '}';
    }
}

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       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.xsd">
    
    <bean id="testService" class="com.gzl.cn.service.TestService"/>
    <bean id="testController" class="com.gzl.cn.controller.TestController" autowire="byName"/>
beans>
import com.gzl.cn.controller.TestController;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Client {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("classpath:bean.xml");
        TestController testController = (TestController)classPathXmlApplicationContext.getBean("testController");
        System.out.println(testController);
    }
}

运行结果:

Spring学习第5篇:自动注入(autowire)详解_第2张图片

3.2.按照类型进行自动注入(byType)

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

Spring学习第5篇:自动注入(autowire)详解_第3张图片
案例:

<bean id="testService" class="com.gzl.cn.service.TestService"/>
<bean id="testController" class="com.gzl.cn.controller.TestController" autowire="byType">bean>

运行结果:

Spring学习第5篇:自动注入(autowire)详解_第4张图片

按照类型注入还有2中比较牛逼的用法:

  • 一个容器中满足某种类型的bean可以有很多个,将容器中某种类型中的所有bean,通过set方法注入给一个java.util.List<需要注入的Bean的类型或者其父类型或者其接口>对象
  • 将容器中某种类型中的所有bean,通过set方法注入给一个java.util.Map对象

案例:

被static修饰的内部类可以直接作为一个普通类来使用

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

public class DiAutowireByTypeExtend {
    // 定义了一个接口
    public interface IService1 {
    }

    public static class BaseServie {
        private String desc;

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

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

    // Service1实现了IService1接口,还继承了BaseServie
    public static class Service1 extends BaseServie implements IService1 {

    }

    // Service1实现了IService1接口,还继承了BaseServie
    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 void setServiceList(List<IService1> serviceList) {//@5
        this.serviceList = serviceList;
    }

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

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

    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 +
                '}';
    }
}

<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.xsd">

    <bean id="service1" class="com.gzl.cn.bean.DiAutowireByTypeExtend.Service1">
        <property name="desc" value="service1"/>
    bean>
    <bean id="service2" class="com.gzl.cn.bean.DiAutowireByTypeExtend.Service2">
        <property name="desc" value="service2"/>
    bean>
    <bean id="service2-1" class="com.gzl.cn.bean.DiAutowireByTypeExtend.Service2">
        <property name="desc" value="service2-1"/>
    bean>

    <bean id="diAutowireByTypeExtend" class="com.gzl.cn.bean.DiAutowireByTypeExtend" autowire="byType"/>
beans>
public class Client {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("classpath:bean.xml");
        DiAutowireByTypeExtend diAutowireByTypeExtend = (DiAutowireByTypeExtend)classPathXmlApplicationContext.getBean("diAutowireByTypeExtend");
        System.out.println(diAutowireByTypeExtend);
    }
}

这个是格式化后的运行结果:

Spring学习第5篇:自动注入(autowire)详解_第5张图片

3.3.按照构造函数进行自动注入(constructor)

autowire设置为constructor

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

spring会找到x类中所有的构造方法(一个类可能有多个构造方法),然后将这些构造方法进行排序(先按修饰符进行排序,public的在前面,其他的在后面,如果修饰符一样的,会按照构造函数参数数量倒叙,也就是采用贪婪的模式进行匹配,spring容器会尽量多注入一些需要的对象)得到一个构造函数列表,会轮询这个构造器列表,判断当前构造器所有参数是否在容器中都可以找到匹配的bean对象,如果可以找到就使用这个构造器进行注入,如果不能找到,那么就会跳过这个构造器,继续采用同样的方式匹配下一个构造器,直到找到一个合适的为止。

案例:

public class DiAutowireByConstructor {

    public static class BaseServie {
        private String desc;

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

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

    // Service1实现了IService1接口,还继承了BaseServie
    public static class Service1 extends BaseServie {

    }

    // Service1实现了IService1接口,还继承了BaseServie
    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 void setService1(Service1 service1) {
        this.service1 = service1;
    }

    public void setService2(Service2 service2) {
        this.service2 = service2;
    }

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

}

<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.xsd">

    <bean id="service1" class="com.gzl.cn.bean.DiAutowireByConstructor.Service1">
        <property name="desc" value="service1"/>
    bean>
    <bean id="service2" class="com.gzl.cn.bean.DiAutowireByConstructor.Service2">
        <property name="desc" value="service2"/>
    bean>

    <bean id="diAutowireByConstructor" class="com.gzl.cn.bean.DiAutowireByConstructor" autowire="constructor"/>
beans>

运行结果: 很明显他是使用了 public DiAutowireByConstructor(Service1 service1, Service2 service2) 构造器

Spring学习第5篇:自动注入(autowire)详解_第6张图片
当我们把service2给去掉后运行:

<bean id="service2" class="com.gzl.cn.bean.DiAutowireByConstructor.Service2">
    <property name="desc" value="service2"/>
bean>

他会使用public DiAutowireByConstructor(Service1 service1)构造器,也就是他会使用最大限度的注入所有依赖的对象。

Spring学习第5篇:自动注入(autowire)详解_第7张图片

3.4.autowire=default

bean xml的根元素为beans,注意根元素有个default-autowire属性,这个属性可选值有(no|byName|byType|constructor|default),这个属性可以批量设置当前文件中所有bean的自动注入的方式,bean元素中如果省略了autowire属性,就相当于是autowire=default,那么会取default-autowire的值作为其autowire的值,而每个bean元素还可以单独设置自己的autowire覆盖default-autowire的配置,如下:


<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.xsd" default-autowire="byName">

beans>

案例:

public class DiAutowireByConstructor {

    public static class BaseServie {
        private String desc;

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

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

    // Service1实现了IService1接口,还继承了BaseServie
    public static class Service1 extends BaseServie {

    }

    // Service1实现了IService1接口,还继承了BaseServie
    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 void setService1(Service1 service1) {
        System.out.println("setService1执行了");
        this.service1 = service1;
    }

    public void setService2(Service2 service2) {
        System.out.println("setService2执行了");
        this.service2 = service2;
    }

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

}

<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.xsd" default-autowire="byName">

    <bean id="service1" class="com.gzl.cn.bean.DiAutowireByConstructor.Service1">
        <property name="desc" value="service1"/>
    bean>
    <bean id="service2" class="com.gzl.cn.bean.DiAutowireByConstructor.Service2">
        <property name="desc" value="service2"/>
    bean>

    <bean id="diAutowireByConstructor" class="com.gzl.cn.bean.DiAutowireByConstructor" autowire="default"/>
beans>

运行结果:

default-autowire设置的是byName,然后bean标签设置的是autowire=“default”,运行结果如下

Spring学习第5篇:自动注入(autowire)详解_第8张图片

3.5.autowire=no

设置为no的时候他就不会随着default-autowire来自动配置了

<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.xsd" default-autowire="byName">

    <bean id="service1" class="com.gzl.cn.bean.DiAutowireByConstructor.Service1">
        <property name="desc" value="service1"/>
    bean>
    <bean id="service2" class="com.gzl.cn.bean.DiAutowireByConstructor.Service2">
        <property name="desc" value="service2"/>
    bean>

    <bean id="diAutowireByConstructor" class="com.gzl.cn.bean.DiAutowireByConstructor" autowire="no"/>
beans>

运行结果:

Spring学习第5篇:自动注入(autowire)详解_第9张图片

四、总结

  • xml中手动注入存在的不足之处,可以通过自动注入的方式来解决,本文介绍了3中自动注入:通过名称自动注入、通过类型自动注入、通过构造器自动注入
  • 按类型注入中有个比较重要的是注入匹配类型所有的bean,可以将某种类型所有的bean注入给一个List对象,可以将某种类型的所有bean按照bean名称->bean对象的映射方式注入给一个Map对象,这种用法比较重要,用途比较大,要掌握
  • spring中还有其他自动注入的方式,用起来会更爽,后面的文章中我们会详细介绍。

你可能感兴趣的:(Spring,spring,学习,java)