首页 > 技术文章 > 基于XML的DI

mengd 2020-08-09 22:15 原文


DI 是ioc(控制反转)的技术实现
ioc技术实现使用的DI(Dependency Injection) :依赖注入, 只需要在程序中提供要使用的对象名称就可以, 至于对象如何在容器中创建,赋值,查找都由容器内部实现。

spring是使用的di实现了ioc的功能, spring底层创建对象,使用的是反射机制。

spring是一个容器,管理对象,给属性赋值, 底层是反射创建对象

一、注入分类

bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。

初始化是由容器自动完成的,称为注入
根据注入方式的不同,常用的有两类:set 注入、构造注入

二、set注入

set 注入也叫设值注入,是指通过 setter 方法传入被调用者的实例,这种注入方式简单、直观,因而在 Spring 的依赖注入中大量使用

1. 简单类型

项目的具体创建看上一篇就可以了,这里直接写重点

首先声明一个Studnet的类

package com.md.b1;

/**
 * @author MD
 * @create 2020-08-07 19:55
 */
public class Student {
    private String name;
    private int age;

    public Student() {
        System.out.println("我是Student类的无参构造方法");
    }

    public void setName(String name) {
        System.out.println("setName:"+name);
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

然后写对应的配置文件

<?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">

    <!--
        声明Student的对象

        简单类型:spring中java的基本数据类型和String都是简单数据类型

        di:给属性赋值也就是注入
        1. set注入 :spring来调用类的set方法,在set方法中完成属性的赋值
            1. 简单类型的注入
            <bean id="xx" class="yyy">
               <property name="属性名字" value="此属性的值"/>
               一个property只能给一个属性赋值
               <property....>
            </bean>

            必须要有属性对应的set方法,没有的话就报错
            但是set方法里面的内容是你能控制,除了赋值,你还可以在set里多写几条java语句

    -->

    <bean id="student" class="com.md.b1.Student">
        <property name="name" value="张三" /> <!-- setName("张三")-->
        <property name="age" value="20"/>
    </bean>

</beans>

结构图

测试类

注意:此时由于这个文件不是直接在resources下面,而是在下面的b1包的下面,所以指定的路径得加上

 @Test
    public void test01(){
        String config = "b1/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        // 从容器中获取Student的对象
        Student student = (Student) ac.getBean("student");

        System.out.println(student);

//        我是Student类的无参构造方法
//        setName:张三
//        Student{name='张三', age=20}

    }

注意一:没有属性但有set方法

还可以在Student的类中加入这个方法,

  public void setEmail(String eamil) {
        System.out.println("setEmail:"+eamil);
    }

对应的配置文件

 <bean id="student" class="com.md.b1.Student">

        <property name="name" value="张三" /> <!-- setName("张三")-->
        <property name="age" value="20"/>
        <property name="email" value="zs@qq.com"/>

    </bean>

此时在Student类中没有email属性,但是有setEmail方法,能顺利执行不?
能,只要有对应的set方法都是正确的,无论属性名是否存在

测试:

@Test
    public void test01(){
        // 注意:此时由于这个文件不是直接在resources下面,而是在下面的b1包的下面,所以指定的路径得加上
        String config = "b1/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        // 从容器中获取Student的对象
        Student student = (Student) ac.getBean("student");
        System.out.println(student);

//        我是Student类的无参构造方法
//        setName:张三
//        setEmail:zs@qq.com
//        Student{name='张三', age=20}
    }

注意二:对于非自定义的类

在配置文件中

    <!--
        非自定义类设置属性
        只有这个类中有setXXX(),就可以
    -->

    <bean id="mydate" class="java.util.Date">

        <!--setTime(993462034956)-->
        <property name="time" value="9348362034" />

    </bean>

测试:

 @Test
    public void test02(){
        String config = "b1/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        Date mydate = (Date) ac.getBean("mydate");
        System.out.println(mydate);
    }

2. 引用类型

当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系

ref的值必须为某 bean 的 id 值

如下:

先创建一个School类

package com.md.b2;

/**
 * @author MD
 * @create 2020-08-07 20:40
 */
public class School {

    private String name;
    private String address;

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(String address) {
        this.address = address;
    }

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

再创建一个Student类,在里面引用School的类的对象

package com.md.b2;

/**
 * @author MD
 * @create 2020-08-07 19:55
 */
public class Student {


    private String name;
    private int age;


//    声明一个引用数据类型
    private School school;

    public void setName(String name) {

        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }

    public void setSchool(School school) {
        System.out.println("setSchool:"+school);
        this.school = school;
    }

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

写对应的配置文件

<?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">

    <!--
           2. 引用数据类型的注入:spring来调用类的set方法,
           <property name="属性名称" ref="bean的id也就是对象的名称"/>
    -->


    <!--
        声明school对象
    -->
    <bean id="school" class="com.md.b2.School">
        <property name="name" value="清华"/>
        <property name="address" value="北京"/>
    </bean>


    <bean id="student" class="com.md.b2.Student">
        <property name="name" value="张三"/>
        <property name="age" value="40"/>
        <!--
            引用数据类型,调用的是setSchool(school),就是上面的
        -->
        <property name="school" ref="school"/>
    </bean>

</beans>

测试:

   @Test
    public void test02(){
        // 注意:此时由于这个文件不是直接在resources下面,而是在下面的b2包的下面,所以指定的路径得加上
        String config = "b2/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        Student student = (Student) ac.getBean("student");
        System.out.println(student);

//        setSchool:School{name='清华', address='北京'}
//        Student{name='张三', age=40, school=School{name='清华', address='北京'}}

    }

三、构造注入

构造注入是指,spring在调用类的有参构造方法,在创建对象的同时,在构造方法中进行属性的赋值

语法:使用<constructor-arg />标签,具体看下面的使用

首先还在Student类中写有参构造器

public Student(String name, int age, School school) {
        System.out.println("我是Student类的有参构造方法");
        this.name = name;
        this.age = age;
        this.school = school;
    }

然后在配置文件中

<?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">

    <!--
       构造注入
            spring在调用类的有参构造方法,在创建对象的同时,在构造方法中进行属性的赋值
               构造注入使用 <constructor-arg> 标签
          <constructor-arg> 标签:一个<constructor-arg>表示构造方法一个参数。
          <constructor-arg> 标签属性:
             name:表示构造方法的形参名
             index:表示构造方法的参数的位置,参数从左往右位置是 0 , 1 ,2的顺序
             value:构造方法的形参类型是简单类型的,使用value
             ref:构造方法的形参类型是引用类型的,使用ref
    -->


    <!--
        声明school对象
    -->
    <bean id="school" class="com.md.b3.School">
        <property name="name" value="清华"/>
        <property name="address" value="北京"/>
    </bean>


  <!--推荐用name-->
    
    <!--调用类的有参构造方法-->
    <!--<bean id="student" class="com.md.b3.Student">-->
        <!--<constructor-arg name="name"  value="张三"/>-->
        <!--<constructor-arg name="age" value="30"/>-->
        <!--<constructor-arg name="school" ref="school"/>-->
    <!--</bean>-->

    <!-- 或者这样也是可以的,根据参数的位置-->
    <!--<bean id="student" class="com.md.b3.Student">-->
        <!--<constructor-arg index="0" value="张三"/>-->
        <!--<constructor-arg index="1" value="30"/>-->
        <!--<constructor-arg index="2" ref="school"/>-->
    <!--</bean>-->
    <!---->

    <!--或者直接省略-->
    <bean id="student" class="com.md.b3.Student">
        <constructor-arg value="张三"/>
        <constructor-arg value="30"/>
        <constructor-arg ref="school"/>
    </bean>


</beans>

测试:

    @Test
    public void test01(){
        // 注意:此时由于这个文件不是直接在resources下面,而是在下面的b3包的下面,所以指定的路径得加上
        String config = "b3/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        Student student = (Student) ac.getBean("student");

        System.out.println(student);

//        我是Student类的有参构造方法
//        Student{name='张三', age=30, school=School{name='清华', address='北京'}}
    }

四、引用类型属性自动注入

对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为<bean/>标签设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)

根据自动注入判断标准的不同,可以分为两种:

  • byName:根据名称自动注入
  • byType: 根据类型自动注入

1. byName 方式自动注入

当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的

语法:

byName(按名称注入) : java类中引用类型的属性名和spring容器中(配置文件)<bean>的id名称一样,
                     且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。
       语法:
       <bean id="xx" class="yyy" autowire="byName">
          简单类型属性赋值
       </bean>

例子:

public class School {

    private String name;
    private String address;
    // 省略set
}

//---------------------------------
public class Student {
    private String name;
    private int age;

//    声明一个引用数据类型
    private School school;
    
    // 省略set
}  

在配置文件中

<bean id="school" class="com.md.b4.School">
        <property name="name" value="北大"/>
        <property name="address" value="北京"/>
 </bean>

 <!--/////////////////////////////-->

 <bean id="student" class="com.md.b4.Student" autowire="byName">
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
     <!--自动赋值引用数据类型-->
    </bean>

如上:

java类中引用类型的属性名school 和 spring容器中(配置文件)<bean>的id名称一样,且数据类型一致,这样就能自动注入

2. byType 方式自动注入

使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)

但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了

语法:

byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性
                            是同源关系的,这样的bean能够赋值给引用类型
       同源就是一类的意思:
        1.java类中引用类型的数据类型和bean的class的值是一样的。
        2.java类中引用类型的数据类型和bean的class的值父子类关系的。
        3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的
       语法:
       <bean id="xx" class="yyy" autowire="byType">
          简单类型属性赋值
       </bean>

       注意:在byType中, 在xml配置文件中声明bean只能有一个符合条件的,
              多余一个是错误的

还是上面的例子:

五、指定多个 Spring 配置文件

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring 配置文件分解成多个配置文件,其中一个为主配置文件

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

    <!--
         包含关系的配置文件:
         total表示主配置文件 : 包含其他的配置文件的,主配置文件一般是不定义对象的。
         语法:<import resource="其他配置文件的路径" />
         关键字:"classpath:" 表示类路径(class文件所在的目录),
               在spring的配置文件中要指定其他文件的位置, 需要使用classpath,告诉spring到哪去加载读取文件。
    -->

    <!--加载的是文件列表-->
    <!--
    <import resource="classpath:b4/spring-school.xml" />
    <import resource="classpath:b4/spring-student.xml" />
    -->
<!--/////////////////////////////-->
    <!--
       在包含关系的配置文件中,可以通配符(*:表示任意字符)
       注意: 主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)
    -->
    <import resource="classpath:b4/spring-*.xml" />

</beans>

六、总结

1. set注入

spring调用类的set方法实现属性赋值

  1. 简单类型的set注入: <property name="属性名" value="属性的值"/>
  2. 引用类型的set注入:<property name="属性名" ref="bean的id"/>

2. 构造注入

spring调用有参数的构造方法

  1. <constructor-arg>的name属性,name表示构造方法的形参名
  2. <constructor-arg>的index属性,表示构造方法形参的位置,从0开始

3. 自动注入

由spring根据某些规则,给引用类型完成赋值,有byName、byType

  1. byName:按名称注入,java类中引用类型的属性名和spring容器中bean的id一样,数据类型一样
  2. byType:按类型注入,java类中引用类型的数据类型和spring容器中bean的class是同源关系

推荐阅读