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


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

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

    一、手动注入的不足

    上篇文章中介绍了依赖注入中的手动注入,所谓手动注入是指在xml中采用硬编码的方式来配置注入的对象,比如通过构造器注入或者set方法注入,这些注入的方式都存在不足,比如下面代码:

    public class A{
        private B b;
        private C c;
        private D d;
        private E e;
        ....
        private N n;
    
        //上面每个private属性的get和set方法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上面的注入存在的问题:

    • 如果需要注入的对象比较多,比如A类中有几十个属性,那么上面的property属性是不是需要写几十个,此时配置文件代码量暴增
    • 如果A类中新增或者删除了一些依赖,还需要手动去调整bean xml中的依赖配置信息,否则会报错
    • 总的来说就是不利于维护和扩展

    为了解决上面这些问题,spring为我们提供了更强大的功能:自动注入

    二、isAssignableFrom方法

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

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

    public native boolean isAssignableFrom(Class<?> cls)
    
    • 1

    用法如下:

    c1.isAssignableFrom(c2)
    
    • 1

    用来判断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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    三、自动注入

    自动注入是采用约定大约配置的方式来实现的,程序和spring容器之间约定好,遵守某一种都认同的规则,来实现自动注入。

    xml中可以在bean元素中通过autowire属性来设置自动注入的方式:

    <bean id="" class="" autowire="byType|byName|constructor|default" />
    
    • 1
    • byName:按照名称进行注入
    • byType:按类型进行注入
    • constructor:按照构造方法进行注入
    • default:默认注入方式
    • no:不使用自动注入

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

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

    autowire设置为byName

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

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

    以setTestService为例,spring容器会按照setTestService名称的首字母小写testService去容器中查找同名的bean对象,找到后会通过该set方法进行属性注入。

    案例:

    public class TestService {
    
        public TestService() {
            System.out.println("TestService初始化了");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    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 +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果:

    现在存在几个疑问

    (1)Spring到底是根据属性去找set的,还是直接扫描的所有set方法?

    基于这个问题,我们可以直接把属性给干掉,看看到底set方法还会不会执行

    答案是会的,从而也说明了Spring就是循环遍历对象当中的所有set方法。跟属性压根没有一点关系,他甚至都不管你有没有属性。

    (2)假如setTestService,根据testService找到了bean,但是参数并不是TestService或者是没有参数,会访问成功吗?

    答案是不会的

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

    autowire设置为byType

    <bean id="" class="X类" autowire="byType"/>
    
    • 1

    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

    案例:

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

    运行结果:

    按照类型注入还有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 +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    在这里插入图片描述

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

    autowire设置为constructor

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

    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 +
                    '}';
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    当我们把service2给去掉后运行:

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

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

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    案例:

    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 +
                    '}';
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行结果:

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

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    运行结果:

    四、总结

    • xml中手动注入存在的不足之处,可以通过自动注入的方式来解决,本文介绍了3中自动注入:通过名称自动注入、通过类型自动注入、通过构造器自动注入
    • 按类型注入中有个比较重要的是注入匹配类型所有的bean,可以将某种类型所有的bean注入给一个List对象,可以将某种类型的所有bean按照bean名称->bean对象的映射方式注入给一个Map对象,这种用法比较重要,用途比较大,要掌握
    • spring中还有其他自动注入的方式,用起来会更爽,后面的文章中我们会详细介绍。
  • 相关阅读:
    传奇攻城期间禁止玩家下地图打怪的脚本写法
    文件操作IO-Part1
    laravel 多条件结合scope查询作用域优化
    【2024秋招】2023-8-5-小红书-数据引擎团队后端开发提前批面经
    KKSwarm功能升级,低成本实现多车集群与避障
    C# 类class、继承、多态性、运算符重载,相关练习题
    《Python编程基础》第1章、简介及示例
    网络安全——自学(黑客)方法
    C# 基于腾讯云人脸核身和百度云证件识别技术相结合的 API 实现
    Himall验证帮助类判断当前时间是否在指定的时间段内、是否是数值(包括整数和小数)
  • 原文地址:https://blog.csdn.net/weixin_43888891/article/details/127825589