Spring学习第4篇:Spring 的依赖注入

news2024/11/22 15:18:16

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

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

目录

    • 一、依赖注入的概念
    • 二、三种注入方式
      • 2.1.构造器注入的4种方式
        • 2.1.1.根据构造器参数`索引`注入
        • 2.1.2.根据构造器参数`类型`注入
        • 2.1.3.根据构造器参数`名称`注入
        • 2.1.4.根据bean的名称注入
      • 2.2.setter注入
      • 2.3.使用 p 名称空间注入数据
    • 三、注入容器中的bean
      • 3.1.ref属性方式
      • 3.2.内置bean的方式
    • 四、注入集合属性

一、依赖注入的概念

依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。例如:我们的controller层仍会调用service层的方法。那这种controllerservice层的依赖关系,在使用 spring 之后,就让 spring 来维护了。简单的说,就是坐等框架把service层对象传入controller层,而不用我们自己去获取。

public class TestController {

    private TestService testService;

    public void method1(){
        testService.method1();
    }

	public void method2(){
        testService.method2();
    }
}

TestController当中需要通过TestService来完成某些操作,这就叫TestController依赖于TestService

上面的TestController其实是存在一个问题的,testService并没有赋值,我们要给成员变量赋值只有两个途径,构造器赋值set方法赋值。那也就意味着创建TestController对象之前,需要先将被依赖对象通过new的方式创建好,然后通过两种方式当中的任意一种将其传递给TestController,这些工作都是TestController的使用者自己去做的,所有对象的创建都是由使用者自己去控制的。

上一篇我们刚刚接触了IOC容器,也就是TestController我们肯定是要通过容器来创建的,那么他依赖的对象当然也要交给spring来给我们注入。所谓的注入其实就是给依赖的成员变量赋值

二、三种注入方式

spring中依赖注入主要分为手动注入自动注入,本文我们主要说一下手动注入,手动注入需要我们明确配置需要注入的对象。

刚才上面我们回顾了,将被依赖方注入到依赖方,通常有2种方式:构造函数的方式和set属性的方式,spring中也是通过这两种方式实现注入的,下面详解2种方式。

2.1.构造器注入的4种方式

顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。

构造器的参数就是被依赖的对象,构造器注入又分为4种注入方式:

  • 根据构造器参数索引注入
  • 根据构造器参数类型注入
  • 根据构造器参数名称注入
  • 根据bean的名称注入

如下类是我们要测试的类:

public class TestController {

    private String name;
    private Integer age;

    public TestController(String name,Integer age) {
        this.name = name;
        this.age = age;
        System.out.println("TestController初始化了");
    }

    @Override
    public String toString() {
        return "TestController{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

之前无参构造的话是:
<bean id="testController" class="com.gzl.cn.controller.TestController"/>
bean标签是单标签,现在因为要使用<bean>标签当中的子标签<constructor-arg>来注入构造器参数,所以使用了<bean>双标签。

使用构造函数的方式,给 service 中的属性传值,要求:类中需要提供一个对应参数列表的构造函数。 可以存在多个构造器,不影响的。

涉及的标签:constructor-arg
属性:
        index: 指定参数在构造函数参数列表的索引位置
        type: 指定参数在构造函数中的数据类型
        name: 指定参数在构造函数中的名称
        value: 它能赋的值是基本数据类型和 String 类型
        ref: 它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean

2.1.1.根据构造器参数索引注入

<?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.xsd">
	
	<!-- bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中
        id 属性:对象的唯一标识。spring内部将这些名称和具体的bean对象进行绑定,然后spring容器可以通过这个的名称找对我们需要的对象,这个名称叫做bean的名称
        class 属性:指定要创建对象的全限定类名
    -->
    <bean id="testController" class="com.gzl.cn.controller.TestController">
        <constructor-arg index="0" value="张三"></constructor-arg>
        <constructor-arg index="1" value="11"></constructor-arg>
    </bean>
</beans>
public class Client {
    public static void main(String[] args) {
    	// 默认就是从项目根路径寻找bean.xml, classpath:bean.xml 和 bean.xml 是一样的效果
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("classpath:bean.xml");
        // 根据bean的名称来获取bean
        TestController testController = (TestController)classPathXmlApplicationContext.getBean("testController");
        System.out.println(testController);
    }
}

运行结果:

在这里插入图片描述

优缺点:

  • 参数位置的注入对参数顺序有很强的依赖性,若构造函数参数位置被人调整过,会导致注入出错。
  • 不过通常情况下,不建议去在代码中修改构造函数,如果需要新增参数的,可以新增一个构造函数来实现,这算是一种扩展,不会影响目前已有的功能。

2.1.2.根据构造器参数类型注入

<bean id="testController" class="com.gzl.cn.controller.TestController">
    <constructor-arg type="java.lang.String" value="张三"></constructor-arg>
    <constructor-arg type="java.lang.Integer" value="11"></constructor-arg>
</bean>

假如有多个String类型,那么按照类型他还能注入吗?

其实是不影响的,只要我们写<constructor-arg>的时候,参数顺序和构造器当中参数的顺序对应上即可。

优缺点:

实际上按照参数位置或者按照参数的类型注入,都有一个问题,很难通过bean的配置文件,知道这个参数是对应UserModel中的那个属性的,代码的可读性不好,比如我想知道这每个参数对应UserModel中的那个属性,必须要去看UserModel的源码,下面要介绍按照参数名称注入的方式比上面这2种更优秀一些。

2.1.3.根据构造器参数名称注入

<bean id="testController" class="com.gzl.cn.controller.TestController">
     <constructor-arg name="name" value="张三"></constructor-arg>
     <constructor-arg name="age" value="11"></constructor-arg>
</bean>

关于方法参数名称的问题

java通过反射的方式可以获取到方法的参数名称,不过源码中的参数通过编译之后会变成class对象,通常情况下源码变成class文件之后,参数的真实名称会丢失,关于这一点我们可以通过javac 类名.class命令来讲java编译成class文件,参数的名称会变成paramString,paramInteger或者arg1 arg2再或者var1 var2等等,总之和实际参数名称不一样了,如下:

在这里插入图片描述

如果需要将源码中的参数名称保留在编译之后的class文件中,编译的时候需要用下面的命令: 主要是加一个parameters

javac -parameters java源码.class

在这里插入图片描述

如果使用ider的话,我们正常编译后的class会到target目录当中,可能是编译器处理了这个问题,并没有做任何配置,然后测试了一下方法名称是保留下来的。

参数名称可能不稳定的问题,spring提供了解决方案,通过ConstructorProperties注解来定义参数的名称,将这个注解加在构造方法上面,注意只能在构造器上使用,如下:

@ConstructorProperties({"第一个参数名称", "第二个参数的名称",..."第n个参数的名称"})
public 类名(String p1, String p2...,参数n) {
}
public class TestController {

    private String name;
    private Integer age;

    @ConstructorProperties({"name", "age"})
    public TestController(String p1, Integer p2) {
        this.name = p1;
        this.age = p2;
        System.out.println("TestController初始化了");
    }

    @Override
    public String toString() {
        return "TestController{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

xml当中可能会报红,因为他找不到对应的构造器参数,实际我们通过@ConstructorProperties注解来指定了,所以报红并不影响正常运行!

<bean id="testController" class="com.gzl.cn.controller.TestController">
     <constructor-arg name="name" value="张三"></constructor-arg>
     <constructor-arg name="age" value="11"></constructor-arg>
</bean>

运行结果:

在这里插入图片描述

说直白点其实就是类似于一个参数别名一样,他并不会改变参数名称的编译结果,但是spring会通过这个注解所指定的名称,找到当前参数。

2.1.4.根据bean的名称注入

public class TestController {

    private TestService testService;

    public TestController(TestService testService) {
        this.testService = testService;
    }

    @Override
    public String toString() {
        return "TestController{" +
                "testService=" + testService +
                '}';
    }
}
<bean id="testService" class="com.gzl.cn.service.TestService"></bean>

<bean id="testController" class="com.gzl.cn.controller.TestController">
    <constructor-arg name="testService" ref="testService"></constructor-arg>
</bean>

运行结果:

在这里插入图片描述

2.2.setter注入

通常情况下,我们的类都是标准的javabean,javabean类的特点:

  • 属性都是private访问级别的
  • 属性通常情况下通过一组setter(修改器)和getter(访问器)方法来访问
  • setter方法,以set开头,后跟首字母大写的属性名,如:setUserName,简单属性一般只有一个方法参数,方法返回值通常为void;
  • getter方法,一般属性以get开头,对于boolean类型一般以is开头,后跟首字母大写的属性名,如:getUserName,isOk;

所谓的setter注入顾名思义就是通过构造器创建好对象之后,通过指定属性的set方法,将值赋值给成员变量。

涉及的标签:property,set注入同构造器注入一样,都是可以注入普通类型和bean类型
属性:
        name:找的是类中 set 方法后面的部分
        ref:指定bean的名称
        value:给属性赋值是基本数据类型和 string 类型的

public class TestController {

    private String name;
    private Integer age;
    private TestService testService;

    public TestController() {
        System.out.println("无参构造执行了");
    }

    public void setName(String name) {
        System.out.println("name的set方法执行了");
        this.name = name;
    }

    public void setAge(Integer age) {
        System.out.println("age的set方法执行了");
        this.age = age;
    }

    public void setTestService(TestService testService) {
        System.out.println("testService的set方法执行了");
        this.testService = testService;
    }

    @Override
    public String toString() {
        return "TestController{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", testService=" + testService +
                '}';
    }
}
public class TestService {

    public TestService() {
        System.out.println("TestService初始化了");
    }
}
<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="testService" class="com.gzl.cn.service.TestService"></bean>

    <bean id="testController" class="com.gzl.cn.controller.TestController">
        <property name="age" value="11"/>
        <property name="name" value="张三"/>
        <property name="testService" ref="testService"></property>
    </bean>
</beans>

运行结果:

在这里插入图片描述

2.3.使用 p 名称空间注入数据

此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的set 方法实现注入功能。

使用p标签得需要引入:xmlns:p="http://www.springframework.org/schema/p" 否则他无法识别p标签!

<?xml version="1.0" encoding="UTF-8"?>
<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>

    <bean id="testController" class="com.gzl.cn.controller.TestController" p:age="11" p:name="李四"
          p:testService-ref="testService"/>

</beans>

运行结果:

在这里插入图片描述

三、注入容器中的bean

注入容器中的bean有两种写法:

  • ref属性方式
  • 内置bean的方式

3.1.ref属性方式

将上面介绍的constructor-arg或者property元素的value属性名称替换为ref,ref属性的值为容器中其他bean的名称,如:构造器方式,将value替换为ref:

<constructor-arg ref="需要注入的bean的名称"/>

setter方式,将value替换为ref:

<property name="属性名称" ref="需要注入的bean的名称" />

3.2.内置bean的方式

构造器的方式:

<constructor-arg>
    <bean class=""/>
</constructor-arg>

setter方式:

<property name="属性名称">
    <bean class=""/>
</property>

四、注入集合属性

注入集合属性同样是可以用set方式注入,也可以用构造器方式注入,只不过集合得需要使用集合的标签。

public class TestController {

    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProps;

    public TestController(String[] myStrs, List<String> myList, Set<String> mySet, Map<String, String> myMap, Properties myProps) {
        this.myStrs = myStrs;
        this.myList = myList;
        this.mySet = mySet;
        this.myMap = myMap;
        this.myProps = myProps;
    }

    @Override
    public String toString() {
        return "TestController{" +
                "myStrs=" + Arrays.toString(myStrs) +
                ", myList=" + myList +
                ", mySet=" + mySet +
                ", myMap=" + myMap +
                ", myProps=" + myProps +
                '}';
    }
}

其中有set、array、list、props、map等标签,可以用在constructor-arg标签下,或者property标签下!

<bean id="testController" class="com.gzl.cn.controller.TestController">
	<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
	<!-- 给数组注入数据 -->
    <constructor-arg name="myStrs">
        <set>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </set>
    </constructor-arg>
    <constructor-arg name="myList">
        <array>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </array>
    </constructor-arg>
    <constructor-arg name="mySet">
        <list>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </list>
    </constructor-arg>
    <constructor-arg name="myMap">
        <props>
            <prop key="testA">aaa</prop>
            <prop key="testB">bbb</prop>
        </props>
    </constructor-arg>
    <constructor-arg name="myProps">
        <map>
            <entry key="testA" value="aaa"></entry>
            <entry key="testB">
            <value>bbb</value>
            </entry>
        </map>
    </constructor-arg>
</bean>

运行结果:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/5200.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构 | 单链表

… &#x1f333;&#x1f332;&#x1f331;本文已收录至&#xff1a;数据结构 | C语言 更多知识尽在此专栏中!文章目录&#x1f333;前言&#x1f333;正文&#x1f332;链表打印与销毁&#x1fab4;打印&#x1fab4;销毁&#x1f332;尾部插入与删除&#x1fab4;节点申请&…

javaweb JAVA JSP销售系统购物系统jsp购物系统购物商城系统源码(jsp电子商务系统)网上在线销售

JSP销售系统购物系统jsp购物系统购物商城系统源码&#xff08;jsp电子商务系统&#xff09;网上在线销售

Linux基础 - 虚拟化介绍(KVM)

‍‍&#x1f3e1;博客主页&#xff1a; Passerby_Wang的博客_CSDN博客-系统运维,云计算,Linux基础领域博主 &#x1f310;所属专栏&#xff1a;『Linux基础』 &#x1f30c;上期文章&#xff1a; Linux基础 - 服务管理&#xff08;systemd&#xff09; &#x1f4f0;如觉得博…

爬取医药卫生知识服务系统的药品数据——超详细流程

爬取医药卫生知识服务系统的药品数据——超详细流程 文章目录爬取医药卫生知识服务系统的药品数据——超详细流程前言一、寻找药品数据二、爬取药品ID1.资源获取2.数据提取3.资源保存4.主函数5.总体代码三、爬取药品信息1.加载资源ID2.获取数据3.数据提取4.保存信息5.主函数6.总…

SpringBoot-属性绑定和bean属性校验

目录 属性绑定 自定义类属性绑定 第三方bean属性匹配 规则:松散绑定&#xff08;宽松绑定&#xff09; Bean属性校验 属性绑定 属性绑定&#xff1a;我们可以使用配置文件对类的属性进行赋值绑定。 自定义类属性绑定 我们自定义一个类&#xff0c;在此使用yml文件进行类…

【滤波器】基于matlab实现微波带低通高通带通滤波器设计

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

python科研做图系列之雷达图

文章目录参考资料matplotlib库画的复现一个 pyecharts的雷达图尝试在上面的基础上&#xff0c;把pyecharts 导出存为一般的png图尝试在上面的基础上&#xff0c;把pyecharts 导出存为一般的矢量图用pygal画雷达图参考资料 参考知乎 CSDN给出了一些参数 matplotbib库雷达图官网 …

Python实现九九乘法表的几种方式,入门必备案例~超级简单~

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 们在学习Python的过程中需要不断的积累和练习&#xff0c;这样才能够走的更远&#xff0c; 今天一起来学习怎么用Python写九九乘法表~ 第一种方法&#xff1a;for-for 代码&#xff1a; for i in range(1, 10):for j in…

数据挖掘面试经总结【私人版,仅供参考】

1特征归一化 1.1为什么需要对数值类型的特征做归一化&#xff1f; 线性函数归一化零均值归一化 1.2在对数据进行预处理时&#xff0c;应该怎样处理类别型特征&#xff1f; 序号编码独热编码二进制编码 1.3什么是组合特征&#xff1f;如何处理高维组合特征&#xff1f; 例…

【python】云打印实现

这两天为了实现云打印功能找了很多相关的文章 记录一下这一篇&#xff0c;python云打印实现-朝花夕拾&#xff0c;代码通过监听文件夹有无产生新文件来判断是否执行&#xff0c;我尝试运行了下没问题&#xff0c;于是打算转载一下 程序运行结果 由于对方的代码和我实现的有点出…

【Maven】你好,Maven >>> 与Maven的初次见面~

个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的道路上摸爬滚打&#xff0c;记录学习的过程~ 与Maven的初次见面~一、了解Maven二、Maven的…

Flink双流join导致数据重复

大家都知道flink sql 中 left join 数据不会互相等待&#xff0c;存在 retract 问题&#xff0c;会导致写入 kafka 的数据量变大&#xff0c;就会导致出现数据重复的问题。 举例&#xff1a;即常见的曝光日志流&#xff08;show_log&#xff09;通过 log_id 关联点击日志流&am…

SQL:数据去重的三种方法

1、使用distinct去重 distinct用来查询不重复记录的条数&#xff0c;用count(distinct id)来返回不重复字段的条数。用法注意&#xff1a; distinct【查询字段】&#xff0c;必须放在要查询字段的开头&#xff0c;即放在第一个参数&#xff1b;只能在SELECT 语句中使用&#…

spring整合fastdfs客户端

解决Dependency ‘com.github.tobato:fastdfs-client:1.27.2’ not found 错误问题。 一、 将fastdfs客户端git下来 git https://github.com/happyfish100/fastdfs-client-java.gitcd fastdfs-client-java然后将fastdfs-client-java构建到本地maven仓库 mvn clean install&…

Pandas的数据结构

Pandas的数据结构 处理CSV 文件 CSV&#xff08;Comma-Separated Values&#xff0c;逗号分隔值&#xff0c;有时也称为字符分隔值&#xff0c;因为分隔字符也可以不是逗号&#xff09;&#xff0c;其文件以纯文本形式存储表格数据&#xff08;数字和文本&#xff09;。 Pan…

【强化学习】深入浅出强化学习--机器人找金币

文章目录Grid_mdp.py定义和初始化从环境状态构建观测值ResetStepRenderingClose注册环境参考文章Grid_mdp.py 定义和初始化 首先自定义环境&#xff0c;自定义的环境将继承gym.env环境。在初始化的时候&#xff0c;可以指定环境支持的渲染模式&#xff08;例如human,rgb_arra…

项目实战 | YOLOv5 + Tesseract-OCR 实现车牌号文本识别

项目实战 | YOLOv5 Tesseract-OCR 实现车牌号文本识别 最近看到了各种各样的车牌识别&#xff0c;觉得挺有意思&#xff0c;自己也简单搞一个玩玩&#x1f63c;。 传统的图像处理算法我也不太会&#xff0c;就直接用深度学习的方法实现吧。 文章目录项目实战 | YOLOv5 Tesser…

docker基础篇——万字解读小鲸鱼

目录 前言 为什么会出现docker&#xff1f; 背景 docker理念 容器和虚拟机比较 容器发展简史 容器虚拟化技术 Why Docker docker的基本组成 镜像(image) 容器(container) 仓库(repository&#xff09; 总结 第一个docker镜像——hello-world run干了什么 …

Spring Boot与Shiro实现权限管理04

1.实现用户管理 1.1 用户列表 首先创建dto&#xff0c;用于请求与响应数据的传输。在common包下创建dto包&#xff0c;在该包下创建UserDto.java类。 Data AllArgsConstructor NoArgsConstructor public class UserDto implements Serializable {private Integer id;private…

云原生|kubernetes|本地存储hostpath-provisioner部署以及无token密码方式登陆dashboard的部署

前言&#xff1a; kubernetes的存储类大家应该都知道&#xff0c;常用的有nfs-client-provisioner这样插件形式&#xff0c;其实还有一种本地存储类的插件&#xff0c;只是这个估计很冷门&#xff0c;生产上网络存储持久卷还是主流的&#xff0c;本文将介绍一种本地存储类插件…