Spring IoC 的依赖注入 - 基于 xml 方式

IoC 操作 Bean 管理 - 依赖注入(DI)


Bean 管理指的是两个操作:

  • Spring 创建对象
  • Spring 注入属性(依赖注入,DI)
  • Bean 管理操作有 2 种实现方式:基于 xml 配置文件方式实现 和 基于注解方式实现。
  • 这里介绍基于 xml 方式实现的依赖注入。

关于 Spring 的学习 Demo,GitHub地址:https://github.com/Jacks5320/Spring-Study


1 创建对象

使用的标签:<bean></bean>

  • 作用:使用默认的构造函数创建对象。
  • 属性:
    • id 属性:对象唯一标识,类似 Map 的 key-value 结构,key 是 id,value 是 class属性。
    • class 属性:类的全限定类名,用于告诉 Spring 创建的是哪个对象。
    • name 属性:作用与 id 属性一样用于定位类,但是 id 属性只能定义一个,而 name 属性中可以定义多个,多个命名之前用逗号隔开。
    • factory-bean 指的是工厂类的 id
    • factory-method 指的是创建对应对象的工厂方法名。
    • scope 指定作用范围,取值:singleton,单例方式。prototype,多例方式。
    • init-method 指定对象初始化时执行的方法,值为方法名。
    • destroy-method 指定对象消亡时执行的方法,值为方法名。

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

    <!-- 用 id 命名 -->
    <bean id="stu" class="com.jk.demo1.Student" />
    <!-- 用 name 指定命名 -->
    <bean name="stu,student" class="com.jk.demo1.Student">
    <!-- 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入 Spring) -->
    <bean id="userFactory" class="com.jk.factory.UserFactory">
    <bean id="user" factory-bean="userFactory" factory-method="getUser">
    <!-- 使用工厂中的静态方法创建对象 -->
    <bean id="user" class="com.jk.factory.UserFactory" factory-method="getUser">
</beans>
  • 如上所示,创建了com.jk.demo1.Student对象,默认采用的是无参构造函数,如果只创建了有参构造,没有无参构造,程序会报错。

获取对象并使用:

// 加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 通过反射获取创建的对象
Student stu = context.getBean("stu", Student.class);
// 使用对象
System.out.println(stu);
  • 关于 ClassPathXmlApplicationContext 前篇博客已经解释过了,是用于加载 xml 文件的。
  • getBean() 方法用于获取经过 xml 解析、反射和工程模式后创建对象。
    • 第一个参数为要获取的 Bean 对象的 id
    • 第二个参数为要实例化对象的字节码文件,如果不加,则需要强制转换返回值类型。

2 基本注入

使用到的标签:

  • <bean>标签内部的 <constructor-arg> 标签,这是基于有参构造注入的方式,所以要求要有有参构造,对有参构造提供的参数进行注入。
  • <bean>标签内部的 <property> 标签,这是基于 setter 注入的方式,所以要求有 set 方法的属性才能使用此标签注入。

2.1 基于构造函数注入

标签及属性:<constructor-arg>

  • 作用:对有参构造提供的参数进行依赖注入。
  • 属性:
    • type:用于指定注入参数的类型,该类型是构造函数参数列表中的某个或某些个参数类型。
    • index:用于指定构造函数中索引位置的参数赋值,参数索引的位置从 0 开始。
    • name:用于指定给构造函数中指定名称的参数赋值。(常用)
    • 以上三个属性用于指定给构造函数中的哪个参数赋值,以下两个属性用于指定注入的值
    • value:用于指定注入的值,只能是基本类型和 String 类型
    • ref:用于指定其他的 bean 类型属性。指的是在Spring的IoC核心容器中出现过的bean对象(Spring 中配置创建的对象)。
  • 特点:
    • 在获取 bean 对象时,注入数据是必须的操作,否则对象无法创建成功。

Java 类:

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

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

xml 配置

<bean id="user" class="com.jc.spring5.User">
    <constructor-arg name="name" value="小明"></constructor-arg>
    <constructor-arg name="age" value="12"></constructor-arg>
</bean>

2.2 基于 setter 方法注入

标签:<property>

  • 作用:对有 set 方法的属性进行注入。
  • 属性:
    • name 属性 : 用于指定调用的 setter 方法的名称(这里的名称是指setXXX中的XXX,且首字母小写)。
    • value 属性 : 用于指定注入的值,只能是基本类型和 String 类型。
    • ref:用于指定其他的 bean 类型属性。指的是在 Spring 的 IoC 核心容器中出现过的 bean 对象(Spring 中配置创建的对象),值为注入 Bean 的 id 值。
  • 特点:
    • 创建对象时,没有明确的限制,可以直接使用默认无参构造函数。如果某个成员必须有值,在获取对象时,如果没有配置 setter 方法会导致属性为空。

Java 类

public class Person {
    private String name;
    private String gender;

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

    public void setGender(String gender) {
        this.gender = gender;
    }

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

xml 配置文件

<bean id="user" class="com.jc.spring5.User">
    <property name="name" value="小明"></property>
    <property name="age" value="12"></property>
</bean>

2.3 p 名称空间注入(了解)

标签:<bean> 标签的 p:property(value) 属性,property 是拥有 set方法的属性名,value 是注入值。
作用:也是基于 setter 注入的方式进行依赖注入。
注意:需要引入 p 名称空间 xmlns:p="http://www.springframework.org/schema/p"


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
           <!--p 名称空间注入-->
    <bean id="stu" class="com.jk.demo1.Person" p:name="小明" p:gender=""/>

</beans>
  • 当需要注入属性很多时,p 名称空间注入的可读性不如 <property> 标签,所以作为了解即可。
  • 大多数情况下还是使用的 setter 注入。

3 null 值和 xml 中的特殊值

1 注入 null 值

<!--注入 null 值-->
<!--基于构造函数-->
<bean id="student" class="com.jk.demo1.Student">
    <constructor-arg name="name">
        <null></null>
    </constructor-arg>
    <constructor-arg name="age" value="12"/>
</bean>
<!--基于 setter 方法-->
<bean id="person" class="com.jk.demo1.Person">
    <property name="gender" value=""/>
</bean>
  • 对于构造注入来说,构造函数的每个参数都必须注入值,如果不想注入值,可以使用 null 标签来替代注入值。
  • 注意:注入 null 的前提是对应的属性允许存在 null 值,如果不允许 null 值,则注入会报错,如 int 这些基本类型,如果需要 null 值,则可以定义成它们的包装类型,如 Integer 或赋值成 0。
  • 对于 setter 注入来说,不调用某个属性的 set 方法,就会对应注入 null 值,或者注入默认值。

2 注入 xml 中的特殊符号

<!--注入特殊值-->
<!--特殊值注入:实体方式-->
<bean id="student2" class="com.jk.demo1.Student">
    <constructor-arg name="name" value="&lt;刘备&gt;"/>
    <constructor-arg name="age" value="12"/>
</bean>

<!--特殊值注入:<![CDATA[含特殊符号的字符串]]>-->
<bean id="person2" class="com.jk.demo1.Person">
    <property name="name">
        <value><![CDATA[<刘备>]]></value>
    </property>
</bean>
  • xml 中,<>&等字符是不能直接存入的,否则 xml 语法检查时会报错。
  • 可以使用 ![CDATA[ ]] 这种格式注入含特殊符号的值。
  • <![CDATA[ ]]>xml语法。在 CDATA 内部的所有内容会被标记为纯文本。
  • 如果不想用 <![CDATA[ ]>,必须将特殊符号转义为实体,如< 对应的实体 &lt;> 对应的实体 &gt;; 对应的实体 &amp;

4 Bean 类型注入

标签及属性:<property> 标签中的 ref 属性。
作用:注入 Bean 类型的属性。
取值:要注入 Beanid


Java 类:

public class Department {
    private String name;

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

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

public class Employee {
    private String name;
    //员工属于某一个部门,使用对象形式表示
    private Department dept;
    //set
    public void setName(String name) {this.name = name;}
    public void setDept(Department dept) {this.dept = dept;}
    // get
    public Department getDept() {return dept;}

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

4.1 外部注入

<bean id="d1" class="com.jk.demo2.Department">
    <property name="name" value="管理部"/>
</bean>
<bean id="e1" class="com.jk.demo2.Employee">
    <property name="name" value="刘备"/>
    <property name="dept" ref="d1"/>
</bean>
  • 外部注入就是将要注入的 Bean(Department类) 在配置文件中进行注入,然后通过 ref 属性注入到指定的 Bean(Employee类) 中。

4.2 级联注入

<bean id="d2" class="com.jk.demo2.Department"/>
<bean id="e2" class="com.jk.demo2.Employee">
    <property name="name" value="关羽"/>
    <!--级联注入-->
    <property name="dept" ref="d2"/>
    <property name="dept.name" value="财务部"/>
</bean>
  • 级联注入就是将要 Department 对象在配置文件中创建出来,然后通过 ref 属性注入 Employee 对象中,最后使用 . 表达式给 Department 对象注入属性。
  • 需要注意的是,. 表达式需要借助于 getter 方法。

4.3 内部注入

<bean id="e3" class="com.jk.demo2.Employee">
    <property name="name" value="张飞"/>
    <!--内部注入-->
    <property name="dept">
        <bean class="com.jk.demo2.Department">
            <property name="name" value="安保部"/>
        </bean>
    </property>
</bean>
  • 内部注入就是,直接将 Department 对象在 Employee 对象的 <Property> 标签中进行创建并注入相应的属性。

5 集合类型注入

5.1 基本类型的集合注入

使用的标签及属性:<property> 中的各集合标签。

  • 用于给 List结构集合注入的标签:list、array、set
  • 用于给 Map 结构集合注入的标签:map、props

结构相同,标签可以互换。


Java 类:

public class Collection {
    private String[] array;
    private List<String> list;
    private Set<String> set;
    private Map<String,String> map;
    private Properties properties;

    public void setArray(String[] array) {
        this.array = array;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public String toString() {
        return "Collection{" +
                "array=" + Arrays.toString(array) +
                ", list=" + list +
                ", set=" + set +
                ", map=" + map +
                ", properties=" + properties +
                '}';
    }

xml 配置文件:

<bean id="c" class="com.jk.demo3.Collection">
    <!--数组-->
    <property name="array">
        <array>
            <value>刘备</value>
            <value>关羽</value>
            <value>张飞</value>
        </array>
    </property>
    <!--list-->
    <property name="list">
        <list>
            <value>吕布</value>
            <value>貂蝉</value>
        </list>
    </property>
    <!--set-->
    <property name="set">
        <set>
            <value>周瑜</value>
            <value>小乔</value>
        </set>
    </property>
    <!--map-->
    <property name="map">
        <map>
            <entry key="刘备" value="玄德"/>
            <entry key="关羽" value="云长"/>
            <entry key="张飞" value="益德"/>
        </map>
    </property>
    <!--property-->
    <property name="properties">
        <props>
            <prop key="初级">小学</prop>
            <prop key="中级">中学</prop>
            <prop key="高级">大学</prop>
        </props>
    </property>
</bean>

5.2 对象类型的集合注入

使用的标签:

  • Map 结构:<property> 标签内部的 <map> 标签内部的 <entry> 标签中的 value-ref 属性,值为 xml 中创建对象的 id
  • List 结构:<property> 标签内部的 <list> 标签内部的 <ref> 标签,值为 xml 中创建对象的 id

Java类:

public class Course {
    private String name;

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

public class Student2 {
    private Map<String, Course> map;
    private List<Course> list;
    //getter setter toString 这里节约篇幅不写,实际需要加上。
}

xml 配置文件:

<!-- 创建多个多选 -->
<bean id="c1" class="com.jk.demo4.Course">
    <property name="name" value="Java程序开发"/>
</bean>
<bean id="c2" class="com.jk.demo4.Course">
    <property name="name" value="Java程序开发"/>
</bean>
<!-- 注入属性 -->
<bean id="student" class="com.jk.demo4.Student2">
    <property name="map">
        <map>
            <entry key="第一门课" value-ref="c1"/>
            <entry key="第二门课" value-ref="c2"/>
        </map>
    </property>
    <property name="list">
        <list>
            <ref bean="c1"/>
            <ref bean="c2"/>
        </list>
    </property>
</bean>
  • List 等集合可以使用 <ref> 标签中的 bean 属性注入对象类型的属性。
  • Map 集合可以使用 value-ref 注入对象类型的属性

5.3 提取公共集合

标签及属性:util: , 需要引入 util 名称空间
作用:提供公共集合


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    <!--提取公共集合-->
    <util:list id="courseList">
        <bean class="com.jk.demo4.Course">
            <property name="name" value="Java程序设计"/>
        </bean>
        <bean class="com.jk.demo4.Course">
            <property name="name" value="数据库设计"/>
        </bean>
    </util:list>

    <!--注入公共集合-->
    <bean id="s1" class="com.jk.demo4.Student2">
        <property name="list" ref="courseList"/>
    </bean>
    <!-- 注入公共集合 -->
    <bean id="s2" class="com.jk.demo4.Student2">
        <property name="list" ref="courseList"/>
    </bean>
</beans>

6 引入外部配置文件

用到的标签及属性:

  • <context:property-placeholder> 标签的 location 属性,用于指定外部属性配置文件的位置。
  • <property> 标签,借助 el 表达式提取出外部属性文件的值。

Java 类

public class Course {
    private String name;

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

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

外部属性配置文件 course.properties

course.name="Java程序设计"

配置注入:需要引入 context 名称空间。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--引入外部配置文件-->
    <context:property-placeholder location="classpath:com/jk/b_DI_xml/course.properties"/>
    <!--将外部配置文件的内容注入到对象中-->
    <bean id="course" class="com.jk.b_DI_xml.demo4.Course">
        <property name="name" value="${course.name}"/>
    </bean>
</beans>

7 Bean 作用范围

标签及属性:bean 标签中的 scope 属性。
作用:用于设置 Bean 对象的作用范围。
可选值:

  • singleton:默认值,表示单实例对象。特点是:加载 Spring 配置文件的时候,就会创建一个单实例对象。
  • prototype:表示多实例对象。特点是:在调用 getBean() 方法时创建多实例对象。
  • request:作用于 web 应用的请求范围
  • session:作用于 web 应用的会话范围
  • global-session:作用域集群环境的会话范围,或者是全局会话范围,当不是集群环境时,就是 session
  • 在 Spring 5 版本中,后面的可选值逐渐被淘汰,只剩下 singletonprototype 两个可选值了。

作用范围验证

<!--默认情况下,单例对象:scope="singleton"-->
<bean id="s1" class="com.jk.demo5.ScopeDemo"/>
<!--使用多例对象:scope="prototype"-->
<bean id="s2" class="com.jk.demo5.ScopeDemo" scope="prototype"/>
public class TestDemo2 {

    ApplicationContext context;

    @Before
    public void init() {
        context = new ClassPathXmlApplicationContext("bean.xml");
    }
    @Test
    public void testSingleton() {
        ScopeDemo s1 = context.getBean("s1", ScopeDemo.class);
        ScopeDemo s2 = context.getBean("s1", ScopeDemo.class);
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);   // true
    }

    @Test
    public void testPrototype() {
        ScopeDemo s1 = context.getBean("s2", ScopeDemo.class);
        ScopeDemo s2 = context.getBean("s2", ScopeDemo.class);
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

8 Bean 的生命周期

  • 单例 Bean:bean的生命与容器保持一致,只要容器存在,就一直活着,关闭容器需要手动调用 ClassPathXmlApplicationContext 实现类中的的 close 方法。
  • 多例 Bean:当我们使用时 Spring 框架才创建,对象只要还在用,就活着,当对象长时间不用且没有别的对象引用时,由 Java 垃圾回收机制回收。

8.1 单例 Bean 生命周期

  • 1 通过构造函数创建 Bean 实例(无参构造),生命开始。
  • 2 为 Bean 注入属性(设置属性值或对其他 Bean 的引用,调用 setter 方法)
  • 3 调用 Bean 的初始化方法(需要自行配置)
  • 4 Bean 可以使用了(获取到对象)。
  • 5 当容器关闭时,调用 Bean 的销毁方法(多例对象不会消亡)

Java类

public class BeanLife {
    private String name;

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

    public void init(){
        System.out.println("初始化方法执行了。。。");
    }

    public void destroy(){
        System.out.println("对象消亡了。。。");
    }
}

xml 配置

<bean id="l1" class="com.jk.demo6.BeanLife" init-method="init" destroy-method="destroy" />
  • init-method 用于初始化之前执行的方法
  • destroy-method 用于 Bean 消亡时执行的方法

测试方法

public class TestDemo2 {

    //测试生命周期
    @Test
    public void testLife(){
        ClassPathXmlApplicationContext context2 = new ClassPathXmlApplicationContext("bean8.xml");
        BeanLife l1 = context2.getBean("l1", BeanLife.class);
        System.out.println(l1);
        //手动关闭容器
        context2.close();
    }
}

8.2 配置后置处理器后 Bean 的生命周期

  • 1 通过构造函数创建 Bean 实例(无参构造),生命开始。
  • 2 为 Bean 注入属性(设置属性值或对其他 Bean 的引用,调用 setter 方法)
  • 3 把 Bean 实例传递给 Bean 后置处理器的方法postProcessBeforeInitialization
  • 4 调用 Bean 的初始化方法(需要进行配置)
  • 5 把 Bean 实例传递给 Bean 后置处理器的方法postProcessAfterInitialization
  • 6 Bean 可以使用(对象获取到了)
  • 7 当容器关闭时,调用 Bean 的销毁方法(需要进行配置销毁的方法),生命结束。(多例对象不会消亡)

后置处理器:

public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后置处理器在初始化之前执行了。。。");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后置处理器在初始化之后执行了。。。");
        return bean;
    }
}
  • 后置处理器需要实现 BeanPostProcessor 接口。
  • postProcessBeforeInitialization 方法会在初始化方法执行之前执行。
  • postProcessAfterInitialization 方法会在初始化方法执行之后执行。

xml 配置:

<!--Bean 生命周期验证-->
<bean id="l1" class="com.jk.demo6.BeanLife" init-method="init" destroy-method="destroy"/>
<!--后置处理器-->
<bean class="com.jk.demo6.MyBeanPost"/>
  • 需要注意的是:配置后置处理器过后,会为当前容器中的所有 Bean 实例添加后置处理器。

以上就是关于基于 xml 方式的依赖注入,多结合实例方便理解~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值