SSM之Spring系列(二)---- 使用 spring 的 IoC 解决程序耦合、Spring 基于 XML 的 IoC 详解和Spring的 依赖注入

我们在了解完什么是IoC之后,现在我们就来了解Spring中的IoC的用法

使用 spring 的 IoC 解决程序耦合

入门案例

接下来我们通过账户的业务层和持久层间的依赖关系的案例来演示 Spring 的 IoC。

  • 导入依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>

(Spring 目录结构:)

  • docs :API 和开发规范.
  • libs :jar 包和源码.
  • schema :约束.

注意,因为 Spring 5 版本是用 JDK8 编写的,所以我们的 JDK 版本需要是 8 及以上,同时如果使用 Tomcat 的话, Tomcat 版本要求 8.5 及以上。

  • 创建业务层接口和实现类
package com.cz.service;

/**
 * 账户业务层接口
 */
public interface AccountService {
    
    

    /**
     * 模拟保存账户
     */
    void saveAccount();
}

package com.cz.service.impl;

import com.cz.dao.AccountDao;
import com.cz.dao.impl.AccountDaoImpl;
import com.cz.service.AccountService;

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements AccountService {
    
    

    private AccountDao accountDao = new AccountDaoImpl();

    public void saveAccount(){
    
    
        accountDao.saveAccount();
    }
}

  • 创建持久层接口和实现类
package com.cz.dao;

/**
 * 账户的持久层接口
 */
public interface AccountDao {
    
    

    /**
     * 模拟保存账户
     */
    void saveAccount();
}

package com.cz.dao.impl;

import com.cz.dao.AccountDao;

/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl implements AccountDao {
    
    

    public void saveAccount(){
    
    
        System.out.println("保存了账户");
    }
}

  • 编写配置文件bean.xml(该文件需要放在resources路径下,名称是任意的,但是不能是中文)
<?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来管理  -->
    <!-- 配置serive -->
    <bean id="accountService" class="com.cz.service.impl.AccountServiceImpl"></bean>
    <!-- 配置dao -->
    <bean id="accountDao" class="com.cz.dao.impl.AccountDaoImpl"></bean>
</beans>
  • 测试代码及运行结果如下
package com.cz.ui;

import com.cz.dao.AccountDao;
import com.cz.service.AccountService;
import com.cz.service.impl.AccountServiceImpl;
import javafx.application.Application;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 模拟表现层,用于调用业务层
 */
public class Client {
    
    

    /**
     * 获取Spring容器IoC核心容器,并根据id获取对象
     * @param args
     */
    public static void main(String[] args) {
    
    
        //1.获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根据id获取bean对象
        AccountService as = (AccountService)ac.getBean("accountService");
        AccountDao adao = ac.getBean("accountDao",AccountDao.class);
        System.out.println(as);
        System.out.println(adao);
        as.saveAccount();
        }
}

在这里插入图片描述

Spring 基于 XML 的 IOC 详解细节

spring 中工厂的类结构图

在这里插入图片描述
在这里插入图片描述

ApplicationContext 接口的常用实现类

在开发中我们一般使用BeanFactory的子接口ApplicationContext,它的常用实现类如下表:

类名称 作用
ClassPathXmlApplicationContext 从类的根路径加载配置文件,要求配置文件必须在类路径下,不在的话,加载不了。推荐
FileSystemXmlApplicationContext 从磁盘任意路径加载配置文件,必须要有访问权限,不推荐
AnnotationConfigApplicationContext 使用注解配置容器对象时,需要使用此类来创建 Spring 容器。它用来读取注解

BeanFactory 和 ApplicationContext 的区别

  • BeanFactory 才是 Spring核心容器的顶级接口(多例对象适用)。
    • 在构建核心容器时,创建对象的默认方式为延迟加载。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
  • ApplicationContextBeanFactory子接口(单例子对象适用)。
    • 在构建核心容器时,创建对象的默认方式为立即加载。也就是说,只要一读取完配置文件就会马上创建配置文件中配置的所有对象(singleton对象)。

Bean 标签

  • 作用:
    • 用于配置对象让 spring 来创建的。默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
  • 属性:
  • id:对象的唯一标识,用于获取对象。
  • class: 对象的全限定类名,用于反射创建对象。
  • scope : 指定对象的作用范围,有以下取值:
    • singleton默认值,单例的
    • prototype多例的
    • request : WEB 项目中的请求范围,Spring 创建一个 Bean 对象,并将对象存入 request域中。
    • session : WEB 项目中的会话范围,Spring 创建一个 Bean 对象,并将对象存入 session 域中。
    • global-session : WEB 项目中,应用在 Portlet (集群)环境的会话范围(全局会话范围),如果没有 Portlet 环境则相当于 session。
  • init-method : 指定类中的初始化方法名称,创建对象前调用。
  • destroy-method : 指定类中的销毁方法名称,销毁对象前调用。
  • factory-bean : 指定工厂 Bean 对象的唯一标识 (Id),将通过工厂创建对象。
  • factory-method : 指定工厂中创建对象的方法,Spring 会通过该方法来创建对象。

global-session解析:

在这里插入图片描述

bean 的作用范围和生命周期

  • 单例对象:scope="singleton"
    一个应用只有一个对象的实例。它的作用范围就是整个引用。

    • 生命周期:
      对象出生:当应用加载,创建容器时,对象就被创建了。
      对象活着:只要容器在,对象一直活着。
      对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
      总结:单例对象的生命周期和容器相同。
  • 多例对象:scope="prototype"
    每次访问对象时,都会重新创建对象实例。

    • 生命周期:
      对象出生:当使用对象时,创建新的对象实例。
      对象活着:只要对象在使用中,就一直活着。
      对象死亡:当对象长时间不用时且没有别的对象引用时,被 java 的垃圾回收器回收了。

实例化 Bean 的三种方式

  1. 使用默认无参构造方法
<bean id="accountService" class="com.cz.service.impl.AccountServiceImpl"></bean>

  在Spring的配置文件中使用bean标签,配以idclass属性之后,且没有其他属性和标签时,使用的就是默认构造函数创建bean对象。
  在默认情况下,它会根据无参构造方法来创建类对象。如果类中没有无参构造方法,将会创建失败

  1. 使用实例工厂的方法,使用实例工厂的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
package com.cz.factory;

import com.cz.service.AccountService;
import com.cz.service.impl.AccountServiceImpl;

/**
 * 模拟工厂类(该类可能是存在jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
 */
public class InstanceFactory {
    
    
    public AccountService getAccountService(){
    
    
        return new AccountServiceImpl();
    }
}
    <bean id="instanceFactory" class="com.cz.factory.InstanceFactory"></bean>
    <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>

先把工厂的创建交给 Spring 来管理,然后再使用工厂中的方法来创建对象。
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。

  1. 使用静态工厂的静态方法,使用静态工厂的方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
package com.cz.factory;

import com.cz.service.AccountService;
import com.cz.service.impl.AccountServiceImpl;

public class StaticFactory {
    
    
    public static AccountService getAccountService(){
    
    
        return new AccountServiceImpl();
    }
}
<bean id="accountService" class="com.cz.factory.StaticFactory" factory-method="getAccountService"></bean>

因为是静态工厂,所以无需先创建工厂,直接通过工厂的静态方法来创建对象。
使用 StaticFactory 类中的静态方法 getAccountService 创建对象,并存入 spring 容器
id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法

Spring 的依赖注入

依赖注入的概念

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

  • 能注入的数据:

    • 基本类型和String
    • 其它bean类型(在配置文件中或者注解配置过的bean)
    • 复杂类型/集合类型
  • 注入的方式:

    • 使用构造函数提供
    • 使用set方法提供
    • 使用注解提供(下篇文章说明)

构造函数注入

  • 顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。具体代码如下:
package com.cz.service.impl;

import com.cz.service.AccountService;
import javafx.scene.chart.PieChart;

import java.util.Date;

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements AccountService {
    
    
    //如果是经常变化的数据,并不适用于注入的方式
    private String name;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl(String name,Integer age,Date birthday){
    
    
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public void saveAccount(){
    
    
        System.out.println("service中的saveAccount方法执行了");
        System.out.println(name+","+age+","+birthday);
    }
}
    <!--  构造函数注入  -->
    <bean id="accountService" class="com.cz.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="一个Java小白"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="birthday" ref="date"></constructor-arg>
    </bean>
    <!-- 配置一个日期对象用于注入到 service 中 -->
    <bean id="date" class="java.util.Date"></bean>
  • <constructor-arg>标签用于构造函数注入,属性如下:
    • index: 指定要注入的参数在构造方法参数列表中索引位置,索引从0开始。
    • type :指定要注入的参数的数据类型,该数据类型也是构造方法中某个或某些参数的类型。
    • name:指定要注入的参数的名称,这个是最方便也是最常用的。
    • value : 用于提供基本类型和 String 类型的数据。
    • ref :用于指定其他 Bean 类型数据,该 Bean 必须是 IoC 容器中的。

优势:
  在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:
  改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供

set 方法注入(更常用的方式)

  • 顾名思义,就是在类中提供需要注入成员的 set 方法,通过类中的 setXxx() 方法给成员变量赋值。具体代码如下:
package com.cz.service.impl;

import com.cz.service.AccountService;
import java.util.Date;

public class AccountServiceImpl2 implements AccountService {
    
    
    //如果是经常变化的数据,并不适用于注入的方式
    private String name;
    private Integer age;
    private Date birthday;

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

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

    public void setBirthday(Date birthday) {
    
    
        this.birthday = birthday;
    }

    public void saveAccount(){
    
    
        System.out.println("service中的saveAccount方法执行了");
        System.out.println(name+","+age+","+birthday);
    }
}

    <!--  set 方法注入  -->
    <bean id="accountService2" class="com.cz.service.impl.AccountServiceImpl2">
        <property name="name" value="一个Java小白"></property>
        <property name="age" value="18"></property>
        <property name="birthday" ref="date"></property>
    </bean>
  • <property>标签用于 set 方法注入,属性如下:
    • name: 指定注定时所调用的 set 方法名称。
    • value: 用于提供基本类型和 String 类型的数据。
      -ref:用于指定其他 Bean 类型数据,该 Bean 必须是 IoC 容器中的。

优势:
  创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
  如果有某个成员必须有值,则获取对象时有可能set方法没有执行。

注入集合属性

  • 顾名思义,就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组,List,Set,Map,Properties。具体代码如下:
package com.cz.service.impl;

import com.cz.service.AccountService;
import java.util.*;

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl3 implements AccountService {
    
    

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

    public void setMyStrs(String[] myStrs) {
    
    
        this.myStrs = myStrs;
    }

    public void setMyList(List<String> myList) {
    
    
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
    
    
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
    
    
        this.myMap = myMap;
    }

    public void setMyProps(Properties myProps) {
    
    
        this.myProps = myProps;
    }

    public void saveAccount(){
    
    
        System.out.println(Arrays.toString(myStrs));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(myProps);
    }
}
<!-- 注入集合属性-->
    <bean id="accountService3" class="com.cz.service.impl.AccountServiceImpl3">
        <!-- 给数组注入数据 -->
        <property name="myStrs">
            <array>
                <value>123</value>
                <value>456</value>
                <value>789</value>
            </array>
        </property>
        <!-- 给 List 注入数据 -->
        <property name="myList">
            <list>
                <value>aaa</value>
                <value>bbb</value>
                <value>ccc</value>
            </list>
        </property>
        <!-- 给 Set 注入数据 -->
        <property name="mySet">
            <set>
                <value>qqq</value>
                <value>www</value>
                <value>eee</value>
            </set>
        </property>
        <!-- 给 Map 注入数据 -->
        <property name="myMap">
            <map>
                <entry key="1">
                    <value>1111</value>
                </entry>
                <entry key="2" value="2222"></entry>
            </map>
        </property>
        <!-- 给 Properties 注入数据 -->
        <property name="myProps">
            <props>
                <prop key="aaa">mmmmmm</prop>
                <prop key="bbb">nnnnnn</prop>
            </props>
        </property>
    </bean>
  • 对于集合类型的注入,可以将所涉及的标签分为两组:
    • 用于给 List 结构(单列)注入的: <list><array><set>
    • 用于给 Map 结构(键值对)注入的 :<map><props>
  • 当结构相同时,使用的标签可以互换,也就是说,给 List 集合注入数据时,使用 <array>或者 <set>也是可以的。

猜你喜欢

转载自blog.csdn.net/weixin_43844418/article/details/113726161