前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring依赖注入之手动注入

Spring依赖注入之手动注入

作者头像
可为编程
发布2023-11-15 13:17:59
2940
发布2023-11-15 13:17:59
举报

继续咱的Spring专题,从手动注入到自动注入有着不一样的操作方式,先从手动注入出发,逐步理解Spring依赖注入的方式和原理。

概述

本次主要是演示一下Spring依赖注入的原理,从最开始的Xml配置文件形式开始,也就是手动注入,彻底搞懂依赖注入的运行机理。我将分为以下几个模块并结合案例进行说明。

  1. 主要介绍xml中依赖注入的配置
  2. 构造器注入的3种方式详解
  3. set方法注入详解
  4. 注入容器中的其他bean的2种方式
  5. 其他常见类型注入详解

依赖回顾

通常情况下,系统中类和类之间是有依赖关系的,如果一个类对外提供的功能需要通过调用其他类的方法来实现的时候,说明这两个类之间存在依赖关系,如:

代码语言:javascript
复制
public class UserService{
    public void insert(UserModel model){
        //插入用户信息
    }
}
public class UserController{
    private UserService userService;
    public void insert(UserModel model){
        this.userService.insert(model);
    }
}

这是我们日常业务中最常见的调用方式了,UserController中的insert方法中需要调用userService的insert方法,说明UserController依赖于UserService,如果userService不存在,此时UserControler无法对外提供insert操作。

那么我们创建UserController对象的时候userService的值是如何设置的呢?通常有两种方式:一种是采用构造器函数,一种是采用set()方法

依赖对象的初始化方式

通过构造器设置依赖对象

UserController中添加一个有参构造方法,如下:

代码语言:javascript
复制
public class UserController {

    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }


    public void insert(Person person) {
        this.userService.insert(person);
    }


    public static void main(String[] args) {
        UserController userController = new UserController(new UserService());
        userController.userService.insert(new Person());

    }
}

UserService

代码语言:javascript
复制
public class UserService {
    public void insert(Person person) {
        //插入用户信息
        System.out.println("生成的person类对象为-----------"+person);
    }
}

执行结果

代码语言:javascript
复制
Connected to the target VM, address: '127.0.0.1:51055', transport: 'socket'
生成的person类对象为-----------org.kewei.pojo.Person@77468bd9
Disconnected from the target VM, address: '127.0.0.1:51055', transport: 'socket'
通过set方法设置依赖对象

可以在UserController中给userService添加一个set方法,然后通过调用set方法设置userService对象到userController中,如下所示:

代码语言:javascript
复制
public class UserController {

    private UserService userService;


    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void insert(Person person) {
        this.userService.insert(person);
    }


    public static void main(String[] args) {
        UserController userController = new UserController();
        userController.setUserService(new UserService());
        userController.userService.insert(new Person());

    }
}

上面这些操作,将被依赖的对象设置到依赖的对象中,spring容器内部都提供了支持,整个依赖对象的注入过程在spirng中就叫做依赖注入。

关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

Spring依赖注入

Spring中依赖注入主要分为手动注入自动注入两种,本文主要说一下手动注入,手动注入需要我们明确配置需要注入的对象有哪些。刚才我们将被依赖方注入到依赖方,通常有2种方式:构造函数方式和调用set()方法的方式,Spring中也是通过这两种方式实现注入的,下面详解2种方式。

通过构造器注入

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

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

之前采用Xml形式进行注入的一些例子就是构造器参数索引注入,但是它对注入对象的参数位置有很强的依赖性,若构造函数参数位置被人调整过,会导致注入出错。不过通常情况下,不建议去在代码中修改构造函数,如果需要新增参数的,可以新增一个构造函数来实现,这算是一种扩展,不会影响目前已有的功能。但是这种还是会修改代码,整体耦合性比较大。

根据构造器参数类型注入

用法

代码语言:javascript
复制
<beanid="diByConstructorParamType"class="com.javacode2018.lesson001.demo5.UserModel">
 
<constructor-argtype="参数类型"value="参数值"/>
 
<constructor-argtype="参数类型"value="参数值"/>
 
</bean>

constructor-arg用户指定构造器的参数 type:构造函数参数的完整类型,如:java.lang.String,int,double value:构造器参数的值,value只能用来给简单的类型设置值

test.xml

代码语言:javascript
复制
<bean id="personByConstructor" class="org.kewei.pojo.Person">
     <constructor-arg type="java.lang.String" value="测试通过根据构造器参数类型注入"/>
     <constructor-arg type="java.lang.String" value="kewei"/>
     <constructor-arg type="java.lang.Integer" value="18"/>
</bean>
代码语言:javascript
复制
    public Person(String str, String name, Integer age) {
        this.str = str;
        this.name = name;
        this.age = age;
    }
代码语言:javascript
复制
  System.out.println("---------------------------------------------");
  System.out.println(personByConstructor.name+personByConstructor.age+"-----"+personByConstructor.str);
代码语言:javascript
复制
---------------------------------------------
kewei18-----测试通过根据构造器参数类型注入
---------------------------------------------

优缺点

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

根据构造器参数名称注入

用法

代码语言:javascript
复制
<beanid="diByConstructorParamName"class="com.javacode2018.lesson001.demo5.UserModel">

<constructor-argname="参数类型"value="参数值"/>

<constructor-argname="参数类型"value="参数值"/>

</bean>

constructor-arg用户指定构造器的参数 name:构造参数名称 value:构造器参数的值,value只能用来给简单的类型设置值

关于方法参数名称的问题

java通过反射的方式可以获取到方法的参数名称,不过源码中的参数通过编译之后会变成class对象,通常情况下源码变成class文件之后,参数的真实名称会丢失,参数的名称会变成arg0,arg1,arg2这样的,和实际参数名称不一样了,如果需要将源码中的参数名称保留在编译之后的class文件中,编译的时候需要用下面的命令

代码语言:javascript
复制
javac -parameters java源码

但是我们难以保证编译代码的时候,操作人员一定会带上-parameters参数,所以方法的参数可能在class文件中会丢失,导致反射获取到的参数名称和实际参数名称不符,这个我们需要先了解一下。

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

代码语言:javascript
复制
@ConstructorProperties({"第一个参数名称","第二个参数的名称",..."第n个参数的名称"})
 
public类名(String p1,String p2...,参数n){
 
}

案例

Car.java
代码语言:javascript
复制
public class Car {
    public String name;
    //描述信息
    public String desc;
    public Car() {
    }
    @ConstructorProperties({"name", "desc"})
    public Car(String p1, String p2) {
        this.name = p1;
        this.desc = p2;
    }
    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

 System.out.println("---------------------------------------------");
        System.out.println(car.getDesc()+"-----"+car.getName());
        System.out.println("---------------------------------------------");
代码语言:javascript
复制
<bean id="car" class="org.kewei.pojo.Car">
        <constructor-arg name="desc" value="测试通过根据构造器参数类型注入"/>
        <constructor-arg name="name" value="宝马"/>
    </bean>
代码语言:javascript
复制
---------------------------------------------
测试通过根据构造器参数类型注入-----宝马
---------------------------------------------

通过setter方法注入

通常情况下,我们的类都是标准的javabean,那当然肯定要符合一些javabean类的特点:

  • 属性都是private访问级别的
  • 属性通常情况下通过一组setter(修改器)和getter(访问器)方法来访问,这个需要我们自己生成,当然有些已经帮我们生成好了,或者使用Lombok的的注释。
  • setter方法,以set 开头,后跟首字母大写的属性名,如:setUserName,简单属性一般只有一个方法参数,方法返回值通常为void;
  • getter方法,一般属性以get开头,对于boolean类型一般以is开头,后跟首字母大写的属性名,如:getUserName,isOk;

Spring对符合javabean特点类,提供了setter方式的注入,会调用对应属性的setter方法将被依赖的对象注入进去。下面我们演示一下Setter方法注入。注意哈,我这里说的set方法和get方法也被称为setter和getter方法,它们是一种封装的实现。通过set方法,我们可以将值设置到对象的私有属性中,而通过get方法,可以从对象的私有属性中获取值。

代码语言:javascript
复制
<bean id="" class="">
    <property name="属性名称" value="属性值" />
    ...
    <property name="属性名称" value="属性值" />
</bean>

property用于对属性的值进行配置,可以有多个 name:属性的名称 value:属性的值

之前我们在前面的例子中已经有过演示,这里就不重复演示了,setter注入相对于构造函数注入要灵活一些,可以指定哪些属性赋予哪些值,如果某些属性没有设置值会默认为null, 构造函数需要指定对应构造函数中所有参数的值,而setter注入的方式没有这种限制,不需要对所有属性都进行注入,可以按需进行注入,不注入的为null。

上面介绍的都是注入普通类型的对象,都是通过value属性来设置需要注入的对象的值的,value属性的值是String类型的,spring容器内部自动会将value的值转换为对象的实际类型。

若我们依赖的对象是容器中的其他bean对象的时候,需要用下面的方式进行注入。

注入容器中的Bean

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

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

ref属性方式

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

关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

构造器方式,将value替换为ref:

代码语言:javascript
复制
<constructor-arg ref="需要注入的bean的名称"/>

setter方式,将value替换为ref:

代码语言:javascript
复制
<property name="属性名称" ref="需要注入的bean的名称"/>

内置bean的方式

构造器的方式:

代码语言:javascript
复制
<constructor-arg>
 
<bean class=""/>
 
</constructor-arg>

setter方式:

代码语言:javascript
复制
<property name="属性名称">
 
<bean class=""/>
 
</property>

例子

代码语言:javascript
复制
 <!--构造方法中增加妻子对象-->
    <bean class="org.kewei.pojo.Person" id="person">
        <constructor-arg type="org.kewei.pojo.Wife" ref="wife"/>
        <constructor-arg index="0" value="可为编程"/>
        <constructor-arg index="1" value="18"/>
    </bean>
    <bean class="org.kewei.pojo.Wife" id="wife" autowire-candidate="true">
        <property name="age" value="18"/>
        <property name="name" value="可为"/>
    </bean>

其他类型注入

对于其他类型数据注入,我这里做一个简单说明吧,因为现在很多时候很少用这种方式,咱能明白Spring主要以哪种形式进行注入就可以了。

关注公众号【可为编程】回复【面试】领取年度最新面试题大全!!!

注入java.util.List(list元素)

代码语言:javascript
复制
<list>

<value>Spring</value>

    或

<refbean="bean名称"/>

    或

<list></list>

    或

<bean></bean>

    或

<array></array>

    或

<map></map>

</list>

注入java.util.Set(set元素)

代码语言:javascript
复制
<set>

<value>Spring</value>

    或

<refbean="bean名称"/>

    或

<list></list>

    或

<bean></bean>

    或

<array></array>

    或

<map></map>

</set>

注入java.util.Map(map元素)

代码语言:javascript
复制
<map>

<entrykey="可为"value="18"key-ref="key引用的bean名称"value-ref="value引用的bean名称"/>

    或

<entry>

<key>

            value对应的值,可以为任意类型

</key>

<value>

            value对应的值,可以为任意类型

</value>

</entry>

</map>

注入数组(array元素)

代码语言:javascript
复制
<array>

    数组中的元素

</array>

注入java.util.Properties(props元素)

Properties类相当于键值都是String类型的Map对象,使用props进行注入,如下:

代码语言:javascript
复制
 <props>

<propkey="key1">java高并发系列</prop>

<propkey="key2">mybatis系列</prop>

<propkey="key3">mysql系列</prop>

</props>
代码语言:javascript
复制
    <bean id="otherType" class="org.kewei.pojo.Person">
        <!-- 注入java.util.List对象 -->
        <property name="list1">
            <list>
                <value>Spring</value>
                <value>SpringBoot</value>
            </list>
        </property>
        <!-- 注入java.util.Set对象 -->
        <property name="set1">
            <set>
                <ref bean="user1"/>
                <ref bean="user2"/>
                <ref bean="user1"/>
            </set>
        </property>
        <!-- 注入java.util.Map对象 -->
        <property name="map1">
            <map>
                <entry key="可为" value="18"/>
                <entry key="可为1" value="28"/>
            </map>
        </property>
        <!-- 注入数组对象 -->
        <property name="array1">
            <array>
                <value>10</value>
                <value>9</value>
                <value>8</value>
            </array>
        </property>
        <!-- 注入java.util.Properties对象 -->
        <property name="properties1">
            <props>
                <prop key="key1">java高并发系列</prop>
                <prop key="key2">mybatis系列</prop>
                <prop key="key3">mysql系列</prop>
            </props>
        </property>
    </bean>
</beans>
代码语言:javascript
复制
private List<String> list1;
private Set<UserModel> set1;
private Map<String, Integer> map1;
private int[] array1;
private Properties properties1;

上面这个类中包含了刚才我们介绍的各种类型,其上面就是具体的注入xml文件配置。

总结

本文主要讲解了xml中bean的依赖注入,都是采用硬编码手动注入的方式进行注入。注入普通类型通过value属性或者value元素设置注入的值;注入对象如果是容器的其他bean的时候,需要使用ref属性或者ref元素或者内置bean元素的方式。还介绍了其他几种类型List、Set、Map、数组、Properties类型的注入,多看几遍加深理解后面我们将介绍spring为我们的自动注入功能。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-11-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 可为编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 继续咱的Spring专题,从手动注入到自动注入有着不一样的操作方式,先从手动注入出发,逐步理解Spring依赖注入的方式和原理。
    • 通过构造器设置依赖对象
      • 通过set方法设置依赖对象
      • 根据构造器参数类型注入
        • 用法
          • 优缺点
          • 根据构造器参数名称注入
            • 用法
              • 关于方法参数名称的问题
                • 案例
                  • Car.java
                • ref属性方式
                  • 内置bean的方式
                    • 对于其他类型数据注入,我这里做一个简单说明吧,因为现在很多时候很少用这种方式,咱能明白Spring主要以哪种形式进行注入就可以了。
                      • 注入java.util.List(list元素)
                        • 注入java.util.Set(set元素)
                          • 注入java.util.Map(map元素)
                            • 注入数组(array元素)
                              • 注入java.util.Properties(props元素)
                              相关产品与服务
                              容器服务
                              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档