Spring(一):IOC 控制反转

目录

1. Spring 概述

1.1 Spring 是什么

1.2 Spring 发展历程

1.3 Spring 优势

1.4 Spring 体系结构

2. 初识 IOC

2.1 概述

2.2 自定义 IOC 容器

3. Spring 快速入门

3.1 介绍

3.2 实现

4. Spring 相关 API

4.1 API 继承体系介绍

4.1.1 BeanFactory

4.1.2 ApplicationContext

5. Spring 配置文件

5.1 Bean 标签基本配置

5.2 Bean 标签范围配置

5.3 Bean 生命周期配置

5.4 Bean 实例化三种方式

5.4.1 无参构造方法实例化

5.4.2 工厂静态方法实例化

5.4.3 工厂普通方法实例化

5.5 Bean 依赖注入概念

5.6 Bean 依赖注入方式

5.6.1 构造方法

5.6.2 set 方法

5.6.3 P 命名空间注入

5.7 Bean 依赖注入的数据类型

5.7.1 注入普通数据类型

5.7.2 注入集合数据类型

5.8 配置文件模块化

5.9 小结

6. IOC 实战 - DbUtils

6.1 DbUtils 是什么?

6.2 Spring 的 xml 整合 DbUtils

6.3 小结

7. Spring 注解开发

7.1 Spring 常用注解

7.2 实现

7.2.1 Bean 实例化(IOC)

7.2.2 属性依赖注入(DI)

7.2.3 @Value

7.2.4 @Scope

7.2.5 Bean 生命周期

7.3 Spring 常用注解整合 DbUtils

7.4 Spring 新注解

7.5 Spring 纯注解整合 DbUtils

7.6 Spring 整合 Junit

7.6.1 普通 Junit 测试问题

7.6.2 Spring 整合 Junit


1. Spring 概述

1.1 Spring 是什么

Spring 是分层的 Java SE/EE 应用 full-stack (全栈式) 轻量级开源框架。

全栈式:对各种主流技术和框架都进行了整合,同时对三层架构都提供解决方案。

轻量级和重量级的划分主要依据就是看它使用了多少服务,启动时需要加载的资源多少以及耦合度等等。

提供了表现层 Spring MVC 和持久层 Spring JDBC Template 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

Spring 两大核心:

  • IOC - Inverse Of Control 控制反转:把对象的创建权交给 Spring
  • AOP - Aspect Oriented Programming 面向切面编程:在不修改源码的情况下,对方法进行增强

1.2 Spring 发展历程

EJB - Enterprise Java Beans:

  • 1997 年,IBM 提出了 EJB 的思想
  • 1998 年,SUN 制定开发标准规范 EJB 1.0
  • 1999 年,EJB 1.1 发布
  • 2001 年,EJB 2.0 发布
  • 2003 年,EJB 2.1 发布
  • 2006 年,EJB 3.0 发布

Spring:

Rod Johnson( Spring 之父)改变 Java 世界的大师级人物
2002 年编著《Expert one on one J2EE design and development》
指出了 Java EE 和 EJB 组件框架中的存在的一些主要缺陷;提出普通 java 类依赖注入更为简单的解决方案
2004 年编著《Expert one-on-one J2EE Development without EJB》阐述了 Java EE 开发时不使用 EJB 的解决方式(Spring 雏形),同年 4 月 spring 1.0 诞生
2006 年 10 月,发布 Spring 2.0
2009 年 12 月,发布 Spring 3.0
2013 年 12 月,发布 Spring 4.0
2017 年 9 月, 发布最新 Spring 5.0 通用版(GA)

1.3 Spring 优势

耦合:程序间的依赖关系

解耦:降低程序间的依赖关系;体现在编译期不依赖,运行期才依赖

JDBC 例子:

public class JDBCTest {

    @Test
    public void test1() throws ClassNotFoundException, SQLException {
        // 1.注册驱动
        // 存在编译期依赖:耦合重的体现
        // DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        // 去掉 new 关键字:编译期不依赖,运行期才依赖。虽然解决了编译期依赖,但是仍存在硬编码问题
        Class.forName("com.mysql.jdbc.Driver");
        ...
    }
}

解耦思路:配置文件 + 反射

Spring:

1)方便解耦,简化开发

  • Spring 就是一个容器,可以将所有对象创建和关系维护交给 Spring 管理
  • 什么是耦合度?对象之间的关系,通常说当一个模块 (对象) 更改时也需要更改其他模块 (对象),这就是耦合,耦合度过高会使代码的维护成本增加。要尽量解耦

2)AOP 编程的支持

  • Spring 提供面向切面编程,方便实现程序进行权限拦截,运行监控等功能。

3)声明式事务的支持

  • 通过配置完成事务的管理,无需手动编程

4)方便测试,降低 Java EE API 的使用

  • Spring 对 Junit 4 支持,可以使用注解测试

5)方便集成各种优秀框架

  • 不排除各种优秀的开源框架,内部提供了对各种优秀框架的直接支持

1.4 Spring 体系结构

以下八大模块可根据需求引入项目使用:

Data Access/Integration

  • JDBC
  • ORM
  • OXM
  • JMS
  • Transactions

Web

  • WebSocket
  • Servlet
  • Web
  • Portlet

AOP

  • Aspects
  • Instrumentation
  • Messaging

Core Container - 相当于盖房子的地基

  • Beans
  • Core
  • Context
    -- SpEL

Test


2. 初识 IOC

2.1 概述

控制反转(Inverse Of Control)是一种设 计思想,它的目的是指导我们设计出更加松耦合的程序。

控制:在 java 中指的是对象的控制权限(创建、销毁)。

反转:指的是对象控制权从由“开发者在类中手动控制”反转到由“ Spring 容器控制”。

例子:

  • 传统方式 - 需要一个 userDao 实例,需要开发者自己手动创建 new UserDao();
  • IOC 方式 - 需要一个 userDao 实例,直接从 spring 的 IOC 容器获得,对象的创建权交给了spring 控制

2.2 自定义 IOC 容器

介绍

需求:实现 Service 层与 Dao 层代码解耦合

步骤分析:

  1. 创建 java 项目,导入自定义 IOC 相关坐标
  2. 编写 Dao 接口和实现类
  3. 编写 Service 接口和实现类
  4. 编写测试代码

实现

创建 java 项目,导入自定义 IOC 相关坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zm</groupId>
    <artifactId>jdbc_spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--指定编码及版本-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.11</java.version>
        <maven.compiler.source>1.11</maven.compiler.source>
        <maven.compiler.target>1.11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

</project>

编写 Dao 接口和实现类

/*
Dao 接口
*/
public interface IUserDao {
    void save();
}


/*
实现类
*/
public class UserDaoImpl implements IUserDao {
    @Override
    public void save() {
        System.out.println("UserDao: Successfully Saved.");
    }
}

编写 Service 接口和实现类

public interface IUserService {
    void save();
}



public class UserServiceImpl implements IUserService {
    private IUserDao userDao = new UserDaoImpl();

    @Override
    public void save() {
        userDao.save();
    }
}

编写测试代码

public class SpringTest {
    @Test
    public void test1() {
        // 获取业务层对象
        IUserService userService = new UserServiceImpl();
        // 调用 save 方法
        userService.save();
    }
}

问题:当前 service 对象和 Dao 对象耦合度太高,而且每次 new 的都是一个新的对象,导致服务器压力过大。

解耦合的原则是编译期不依赖,而运行期依赖就行了。

采用反射方式:

public class UserServiceImpl implements IUserService {
    private IUserDao userDao;

    public UserServiceImpl() throws IllegalAccessException, ClassNotFoundException, InstantiationException {
        // 传统方式使用 new 方式,导致编译期依赖,耦合度太高
        // userDao = new UserDaoImpl();

        // 反射方式,存在硬编码问题
        userDao = (IUserDao) Class.forName("com.zm.dao.impl.UserDaoImpl").newInstance();
    }

    @Override
    public void save() {
        userDao.save();
    }
}

测试代码:

@Test
public void test1() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
    IUserService userService = new UserServiceImpl();
    userService.save();
}

改造步骤分析:

  1. 准备一个配置文件
  2. 编写一个工厂工具类,工厂类使用 dom4j 来解析配置文件,获取到类的全路径
  3. 使用反射生成对应类的实例对象,存到 Map 中,这个 Map 就是 IOC 容器

为了解决反射方式的硬编码问题,先编写 beans.xml 配置文件

<beans>
    <!-- id: 标识,class: 存的就是要生成实例的类的全路径 -->
    <bean id="userDao" class="com.zm.dao.impl.UserDaoImpl"></bean>
</beans>

编写 BeanFactory 工具类

public class BeanFactory {

    private static Map<String,Object> iocMap = new HashMap<>();

    // 程序启动时,初始化对象实例
    static {
        // 读取配置文件
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");

        try {
            // 解析 xml(dom4j)
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(resourceAsStream);

            // 编写 xpath 表达式
            String xpath = "//bean";

            // 获取到所有的 bean 标签
            List<Element> list = document.selectNodes(xpath);

            // 遍历并使用反射创建对象实例,存到 map 集合(ioc 容器)中
            for (Element element : list) {
                String id = element.attributeValue("id");
                String className = element.attributeValue("class");
                // 使用反射生成实例对象
                Object o = Class.forName(className).newInstance();
                // 存到 map 中:key-id,value-o
                iocMap.put(id, o);
            }

        } catch (DocumentException | InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static Object getBean(String beanId) {
        return iocMap.get(beanId);
    }

}

修改 UserServiceImpl 实现类

public UserServiceImpl() throws IllegalAccessException, ClassNotFoundException, InstantiationException {
    // 传统方式使用 new 方式,导致编译期依赖,耦合度太高
    // userDao = new UserDaoImpl();

    // 反射方式,存在硬编码问题
    // userDao = (IUserDao) Class.forName("com.zm.dao.impl.UserDaoImpl").newInstance();

    // 反射方式 + 配置文件
    userDao = (IUserDao) BeanFactory.getBean("userDao");
}

小结

  • 其实升级后的 BeanFactory 就是一个简单的 Spring 的 IOC 容器所具备的功能。
  • 之前需要一个 userDao 实例,开发者自己手动创建 new UserDao();
  • 现在需要一个 userDao 实例,直接从 spring 的 IOC 容器获得,对象的创建权交给了 spring 控制
  • 最终目标:代码解耦合

3. Spring 快速入门

3.1 介绍

需求:借助 spring 的 IOC 实现 service 层与 DAO 层代码解耦合

步骤分析:

  1. 创建 java 项目,导入 spring 开发基本坐标
  2. 编写 DAO 接口和实现类
  3. 创建 spring 核心配置文件
  4. 在 spring 配置文件中配置 UserDaoImpl
  5. 使用 spring 相关 API 获得 Bean 实例

3.2 实现

创建 java 项目,导入 spring 开发基本坐标

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

编写 Dao 接口和实现类

public interface IUserDao {
    void save();
}


public class UserDaoImpl implements IUserDao {
    @Override
    public void save() {
        System.out.println("UserDao: save...");
    }
}

创建 spring 核心配置文件 applicationContext.xml,并在 spring 配置文件中配置 UserDaoImpl Bean

<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--
        在 spring 配置文件中配置 UserDaoImpl
        id: 唯一标识,不能重复
        class: 类全路径
        scope="singleton" : 创建出来的 bean 是单例的,是默认值
              "prototype" : 创建出来的 bean 是多例的,每次从容器中获取都会创建一个新的对象
    -->
    <bean id="userDao" class="com.zm.dao.impl.UserDaoImpl"/>

</beans>

使用 spring 相关 API 获得 Bean 实例

@Test
public void test1(){
    // 获取到了 spring 上下文对象,借助上下文对象可以获取到 IOC 容器中的 bean 对象 ,加载的同时就创建了 bean 对象存到容器中
    // ApplicationContext XmlApplicationContext = new FileSystemXmlApplicationContext("D:\\gitee_repository\\stage-6-module-2\\code\\spring_quickstart\\src\\main\\resources\\applicationContext.xml");
    ApplicationContext XmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

    // 使用上下文对象从 IOC 容器中获取到了 bean 对象
    // 方法一:根据 bean id 在容器中找对应的 bean 对象
    // IUserDao userDao = (IUserDao) XmlApplicationContext.getBean("userDao");
    // 方法二:根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。
    // IUserDao userDao = XmlApplicationContext.getBean(IUserDao.class);
    // 方法三:根据 Bean 的 id 和类型获得 Bean 实例,解决容器中相同类型 Bean 有多个情况。
    IUserDao userDao = XmlApplicationContext.getBean("userDao", IUserDao.class);

    // 调用方法
    userDao.save();
}

Spring 的开发步骤总结

  1. 导入坐标
  2. 创建 Bean
  3. 创建 applicationContext.xml
  4. 在配置文件中进行 Bean 配置
  5. 创建 ApplicationContext 对象,执行 getBean

4. Spring 相关 API

4.1 API 继承体系介绍

Spring 的 API 体系异常庞大,现在只关注两个 BeanFactory 和 ApplicationContext

BeanFactory 是 ApplicationContext 的父接口,ApplicationContext 接口下又有 FileSystemXmlApplicationContext 和 ClassPathXmlApplicationContext 子接口

4.1.1 BeanFactory

BeanFactory 是 IOC 容器的核心接口,它定义了 IOC 的基本功能

特点:在第一次调用 getBean() 方法时,创建指定对象的实例

@Test
public void test2(){
    // 核心接口,但此时还不会创建 bean 对象存到IOC容器中
    BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

    // 只有调用getBean 的时候才真正创建 bean 对象
    IUserDao userDao = (IUserDao) xmlBeanFactory.getBean("userDao");

    // 调用方法
    userDao.save();
}

4.1.2 ApplicationContext

代表应用上下文对象,可以获得 spring 中 IOC 容器的 Bean 对象

特点:在 spring 容器启动时,加载并创建所有对象的实例

常用实现类:

1. ClassPathXmlApplicationContext - 它是从类的根路径下加载配置文件,推荐使用这种。

2. FileSystemXmlApplicationContext - 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

3. AnnotationConfigApplicationContext - 当使用注解配置容器对象时,需要使用此类来创建 spring 容器,它用来读取注解。

常用方法:

方法声明功能介绍
Object getBean(String name);根据 Bean 的 id 从容器中获得 Bean 实例,返回是 Object,需要强转。
<T> T getBean(Class<T> requiredType)根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。
<T> T getBean(String name,Class<T> requiredType);根据 Bean 的 id 和类型获得 Bean 实例,解决容器中相同类型 Bean 有多个情况。

5. Spring 配置文件

5.1 Bean 标签基本配置

用于配置对象交由 Spring 来创建。也就是说你想让Spring帮你创建一个对象的话就配置一个bean标签。

默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。

<!--
    id: 唯一标识,不能重复
    class: 类全路径
-->
<bean id="" class=""/>

5.2 Bean 标签范围配置

<bean id="" class="" scope=""></bean>

scope 属性指对象的作用范围,取值如下:

  • singleton - 默认值,单例的
  • prototype - 多例的
  • request - WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
  • session - WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
  • global session - WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么 global Session 相当于 session

测试 scope 属性:

@Test
public void test3(){
    ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

    IUserDao userDao_1 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");
    IUserDao userDao_2 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");

    System.out.println(userDao_1);
    System.out.println(userDao_2);
}

当 scope 的取值为 singleton 时:

  • Bean 的实例化个数:1 个
  • Bean 的实例化时机:当 Spring 核心文件被加载时,实例化配置的 Bean 实例
  • Bean 的生命周期:
    + 对象创建:当应用加载,创建容器时,对象就被创建了
    + 对象运行:只要容器在,对象一直活着
    + 对象销毁:当应用卸载,销毁容器时,对象就被销毁了

当 scope 的取值为 prototype 时:

  • Bean 的实例化个数:多个
  • Bean 的实例化时机:当调用 getBean() 方法时实例化 Bean
  • Bean 的生命周期:
    + 对象创建:当使用对象时,创建新的对象实例
    + 对象运行:只要对象在使用中,就一直活着
    + 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了

5.3 Bean 生命周期配置

applicationContext.xml

<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>
  • init-method:指定类中的初始化方法名称
  • destroy-method:指定类中销毁方法名称
public class UserDaoImpl implements IUserDao {
    public void init() {
        System.out.println("UserDao: Initialize...");
    }

    public void destroy() {
        System.out.println("UserDao: Destroy");
    }

    ...
}

测试代码

@Test
public void test4(){
    ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

    IUserDao userDao_1 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");
    IUserDao userDao_2 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");

    System.out.println(userDao_1);
    System.out.println(userDao_2);

    // 触发 destroy 方法
    classPathXmlApplicationContext.close();
}

5.4 Bean 实例化三种方式

5.4.1 无参构造方法实例化

它会根据默认无参构造方法来创建类对象,如果 bean 中没有默认无参构造函数,将会创建失败

<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl"/>

5.4.2 工厂静态方法实例化

应用场景:依赖的 jar 包中有个 A 类,A 类中有个静态方法 m1,m1 方法的返回值是一个 B 对象。如果频繁使用 B 对象,此时可以将 B 对象的创建权交给 spring 的 IOC 容器,以后在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。

public class StaticFactoryBean {
    public static IUserDao createUserDao(){
        return  new UserDaoImpl();
    }
}

 applicationContext.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"
       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">

    <!-- 方式一:无参构造方法实例化 -->
    <!--
        在 spring 配置文件中配置 UserDaoImpl
        id: 唯一标识,不能重复
        class: 类全路径
        scope="singleton" : 创建出来的 bean 是单例的,是默认值
              "prototype" : 创建出来的 bean 是多例的,每次从容器中获取都会创建一个新的对象
    -->
<!--    <bean id="userDao" class="com.zm.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>-->
<!--    <bean id="userDao2" class="com.zm.dao.impl.UserDaoImpl"/>-->

    <!-- 方式二:工厂静态方法实例化 -->
    <bean id="userDao" class="com.zm.factory.StaticFactoryBean" factory-method="createUserDao"/>

</beans>

5.4.3 工厂普通方法实例化

应用场景:依赖的 jar 包中有个 A 类,A 类中有个普通方法 m1,m1 方法的返回值是一个 B 对象。如果我们频繁使用 B 对象,此时我们可以将 B 对象的创建权交给 spring 的 IOC 容器,以后我们在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。

public class DynamicFactoryBean {
    public IUserDao createUserDao(){
        return  new UserDaoImpl();
    }
}

  applicationContext.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"
       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">

    <!-- 方式一:无参构造方法实例化 -->
    <!--
        在 spring 配置文件中配置 UserDaoImpl
        id: 唯一标识,不能重复
        class: 类全路径
        scope="singleton" : 创建出来的 bean 是单例的,是默认值
              "prototype" : 创建出来的 bean 是多例的,每次从容器中获取都会创建一个新的对象
    -->
<!--    <bean id="userDao" class="com.zm.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>-->
<!--    <bean id="userDao2" class="com.zm.dao.impl.UserDaoImpl"/>-->

    <!-- 方式二:工厂静态方法实例化 -->
<!--    <bean id="userDao" class="com.zm.factory.StaticFactoryBean" factory-method="createUserDao"/>-->

    <!-- 方式三:工厂普通方法实例化 -->
    <bean id="dynamicFactoryBean" class="com.zm.factory.DynamicFactoryBean"/>
    <bean id="userDao" factory-bean="dynamicFactoryBean" factory-method="createUserDao"/>

</beans>

5.5 Bean 依赖注入概念

依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。

在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。

这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是通过框架把持久层对象传入业务层,而不用手动去获取。

5.6 Bean 依赖注入方式

5.6.1 构造方法

在 UserServiceImpl 中创建有参构造

public class UserServiceImpl implements IUserService {
    private IUserDao userDao;

    public UserServiceImpl(IUserDao userDao) {
        this.userDao = userDao;
    }

    public UserServiceImpl() {
    }

    @Override
    public void save() {
        // 调用 dao 层的 save 方法
        userDao.save();
    }
}

配置 Spring 容器调用有参构造时进行注入

<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.zm.service.impl.UserServiceImpl">
    <!-- <constructor-arg index="0" type="com.zm.dao.IUserDao" ref="userDao"/> -->
    <!-- 其中name="userDao" :表示要为有参构造中参数名为userDao的参数进行值得注入;
             ref="userDao" :表示注入的就是上面bean标签中id为userDao的对象
     -->
    <constructor-arg name="userDao" ref="userDao"/>
</bean>

5.6.2 set 方法

在 UserServiceImpl 中创建 set 方法

public class UserServiceImpl implements IUserService {
    private IUserDao userDao;

    public UserServiceImpl(IUserDao userDao) {
        this.userDao = userDao;
    }

    public UserServiceImpl() {
    }

    @Override
    public void save() {
        // 调用 dao 层的 save 方法
        userDao.save();
    }

    public void setUserDao(IUserDao userDao) {
        this.userDao = userDao;
    }
}

配置 Spring 容器调用 set 方法进行注入

<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.zm.service.impl.UserServiceImpl">
    <!-- 构造方法注入 -->
    <!-- <constructor-arg index="0" type="com.zm.dao.IUserDao" ref="userDao"/> -->
    <!-- <constructor-arg name="userDao" ref="userDao"/> -->
    <!-- set 方法完成依赖注入 -->
    <property name="userDao" ref="userDao"></property>
</bean>

5.6.3 P 命名空间注入

P 命名空间注入本质也是 set 方法注入,但比起上述的 set 方法注入更加方便,主要体现在配置文件 applicationContext.xml 中。

首先,需要引入 P 命名空间:

xmlns:p="http://www.springframework.org/schema/p"

其次,需要修改注入方式:

<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.zm.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>

5.7 Bean 依赖注入的数据类型

上面操作,都是注入 Bean 对象,除了对象的引用可以注入,普通数据类型和集合都可以在容器中进行注入。

注入数据的三种数据类型:

  1. 普通数据类型
  2. 引用数据类型
  3. 集合数据类型

之前的操作都是对 UserDao 对象的引用进行注入的,属于引用数据类型注入。下面将以 set 方法注入为例,演示普通数据类型和集合数据类型的注入。

5.7.1 注入普通数据类型

public class UserDaoImpl implements IUserDao {
    private String username;
    private Integer age;

    public void setUsername(String username) {
        this.username = username;
    }

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

    @Override
    public void save() {
        System.out.println(username);
        System.out.println(age);
        System.out.println("UserDao: save...");
    }
}

<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl">
    <!-- ref: 用于引用数据类型的注入,value 是用于普通数据类型的注入 -->
    <property name="username" value="张三"/>
    <property name="age" value="25"/>
</bean>

5.7.2 注入集合数据类型

1. List 集合注入

public class User {
    private String username;
    private Integer age;

    public void setUsername(String username) {
        this.username = username;
    }

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





public class UserDaoImpl implements IUserDao {
    private List<Object> list;

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

    @Override
    public void save() {
        System.out.println("List:" + list);
    }
}


<bean id="user" class="com.zm.domain.User">
    <property name="username" value="布莱尔"/>
    <property name="age" value="18"/>
</bean>

<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl">
    <property name="list">
        <list>
            <value>aaa</value>
            <ref bean="user"/>
        </list>
    </property>
</bean>

2. Set 集合注入

public class UserDaoImpl implements IUserDao {
    private Set<Object> set;

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

    @Override
    public void save() {
        System.out.println("Set:" + set);
    }
}

<bean id="user" class="com.zm.domain.User">
    <property name="username" value="布莱尔"/>
    <property name="age" value="18"/>
</bean>

<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl">
    <property name="set">
        <set>
            <value>bbb</value>
            <ref bean="user"/>
        </set>
    </property>
</bean>

3. Array 数组注入

public class UserDaoImpl implements IUserDao {
    private Object[] array;

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

    @Override
    public void save() {
        System.out.println("Array:" + Arrays.toString(array));
    }
}

<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl">
    <property name="array">
        <array>
            <value>ccc</value>
            <ref bean="user"/>
        </array>
    </property>
</bean>

4. Map 集合注入

public class UserDaoImpl implements IUserDao {
    private Map<String,Object> map;

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

    @Override
    public void save() {
        System.out.println("Map:" + map);
    }
}
<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl">
    <property name="map">
        <map>
            <entry key="k1" value="ddd"/>
            <entry key="k2" value-ref="user"/>
        </map>
    </property>
</bean>

5. Properties 配置注入

public class UserDaoImpl implements IUserDao {
    private Properties properties;

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

    @Override
    public void save() {
        System.out.println("Properties:" + properties);
    }
}

<bean id="userDao" class="com.zm.dao.impl.UserDaoImpl">
    <property name="properties">
        <props>
            <prop key="k1">v1</prop>
            <prop key="k2">v2</prop>
            <prop key="k3">v3</prop>
        </props>
    </property>
</bean>

5.8 配置文件模块化

实际开发中,Spring 的配置内容非常多,这就导致 Spring 配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,也就是所谓的配置文件模块化。

并列的多个配置文件:

ApplicationContext ac = new ClassPathXmlApplicationContext("beans1.xml", "beans2.xml", "...");

主从配置文件:

<import resource="applicationContext-xxx.xml"/>

注意:

同一个 xml 中不能出现相同名称的 bean,如果出现会报错。

多个 xml 如果出现相同名称的 bean,不会报错,但是后加载的会覆盖前加载的 bean。

5.9 小结

Spring 的重点配置

<bean>标签:创建对象并放到 spring 的 IOC 容器

id 属性: 在容器中 Bean 实例的唯一标识,不允许重复
class 属性: 要实例化的 Bean 的全限定名
scope 属性: Bean 的作用范围,常用是 Singleton (默认) 和 prototype

<constructor-arg> 标签:属性注入

name 属性:属性名称
value 属性:注入的普通属性值
ref 属性:注入的对象引用值

<property> 标签:属性注入

name 属性:属性名称
value 属性:注入的普通属性值
ref 属性:注入的对象引用值
<list>
<set>
<array>
<map>
<properties>

<import> 标签: 导入其他的 Spring 的分文件


6. IOC 实战 - DbUtils

6.1 DbUtils 是什么?

DbUtils 是 Apache 的一款用于简化 Dao 代码的工具类,它底层封装了 JDBC 技术。
核心对象

QueryRunner queryRunner = new QueryRunner(DataSource dataSource);

核心方法:

  • int update() - 执行增、删、改语句
  • T query() - 执行查询语句
  • ResultSetHandler<T> - 这是一个接口,主要作用是将数据库返回的记录封装到实体对象

查询数据库所有账户信息到 Account 实体中:

public class DbUtilsTest {
    @Test
    public void findAllTest() throws Exception {
        // 创建 DBUtils 工具类,传入连接池
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        // 编写 sql
        String sql = "select * from account";
        // 执行 sql
        List<Account> list = queryRunner.query(sql, new BeanListHandler<Account> (Account.class));
        // 打印结果
        for (Account account : list) {
            System.out.println(account);
        }
    }
}

6.2 Spring 的 xml 整合 DbUtils

介绍

需求:基于 Spring 的 xml 配置实现账户的 CRUD 案例

步骤分析:

  1. 准备数据库环境
  2. 创建 java 项目,导入坐标
  3. 编写 Account 实体类
  4. 编写 AccountDao 接口和实现类
  5. 编写 AccountService 接口和实现类
  6. 编写 spring 核心配置文件
  7. 编写测试代码

实现

准备数据库环境

CREATE DATABASE `spring_db`;
USE `spring_db`;
CREATE TABLE `account` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(32) DEFAULT NULL,
    `money` double DEFAULT NULL,
    PRIMARY KEY (`id`)
);
insert  into `account`(`id`,`name`,`money`) values (1,'tom',1000),
(2,'jerry',1000);

创建 java 项目,导入坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zm</groupId>
    <artifactId>spring_dbutils</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--指定编码及版本-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.11</java.version>
        <maven.compiler.source>1.11</maven.compiler.source>
        <maven.compiler.target>1.11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>
</project>

编写 Account 实体类

public class Account {

    private Integer id;
    private String name;
    private Double money;

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }
}

编写 AccountDao 接口和实现类

public interface AccountDao {

    List<Account> findAll();

    Account findById(Integer id);

    void save(Account account);

    void update(Account account);

    void delete(Integer id);
    
}



public class AccountDaoImpl implements AccountDao {
    private QueryRunner queryRunner;

    public void setQueryRunner(QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }

    @Override
    public List<Account> findAll() {
        List<Account> list = null;

        // 编写 sql
        String sql = "select * from account";
        try {
            // 执行 sql
            list = queryRunner.query(sql, new BeanListHandler<Account>(Account.class));

        } catch (SQLException e) {
            e.printStackTrace();
        }

        return list;
    }

    @Override
    public Account findById(Integer id) {
        Account query = null;

        // 编写 sql
        String sql = "select * from account where id = ?";
        try {
            query = queryRunner.query(sql, new BeanHandler<Account>(Account.class), id);
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return query;
    }

    @Override
    public void save(Account account) {
        String sql = "insert into account values(null, ?, ?)";
        try {
            queryRunner.update(sql, account.getName(), account.getMoney());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void update(Account account) {
        String sql = "update `account` set `name` = ?, `money` = ? where `id` = ?";
        try {
            queryRunner.update(sql, account.getName(), account.getMoney(), account.getId());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void delete(Integer id) {
        String sql = "delete from `account` where `id` = ?";
        try {
            queryRunner.update(sql, id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

编写 AccountService 接口和实现类

public interface AccountService {
    List<Account> findAll();

    Account findById(Integer id);

    void save(Account account);

    void update(Account account);

    void delete(Integer id);
}




public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }

    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    @Override
    public void save(Account account) {
        accountDao.save(account);
    }

    @Override
    public void update(Account account) {
        accountDao.update(account);
    }

    @Override
    public void delete(Integer id) {
        accountDao.delete(id);
    }
}

编写 spring 核心配置文件

applicationContext.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"
       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

">

    <!-- 把数据库连接池交给 IOC 容器 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:///spring_db?characterEncoding=utf8&useSSL=false"/>
        <property name="username" value="root"/>
        <property name="password" value="password"/>
    </bean>

    <!--把 QueryRunner 交给 IOC 容器 -->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>

    <!-- 把 AccountDao 交给 IOC 容器 -->
    <bean id="accountDao" class="com.zm.dao.impl.AccountDaoImpl">
         <!-- 通过 set 的方式将queryRunner对象注入到AccountDao中 -->
        <property name="queryRunner" ref="queryRunner"/>
    </bean>

    <!-- 把 AccountService 交给 IOC 容器 -->
    <bean id="accountService" class="com.zm.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

</beans>

编写测试代码

public class AccountServiceTest {

    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    AccountService accountService = (AccountService) applicationContext.getBean("accountService");

    // 测试添加
    @Test
    public void testSave(){
        Account account = new Account();
        account.setName("zm");
        account.setMoney(888d);
        accountService.save(account);
    }

    // 测试查询
    @Test
    public void testFindById(){
        Account account = accountService.findById(3);
        System.out.println(account);
    }

    // 测试查询所有
    @Test
    public void testFindAll(){
        List<Account> all = accountService.findAll();
        for (Account account : all) {
            System.out.println(account);
        }
    }

    // 测试更新
    @Test
    public void testUpdate(){
        Account account = new Account();
        account.setId(3);
        account.setName("Blair");
        account.setMoney(2000d);
        accountService.update(account);
    }

    // 测试删除
    @Test
    public void testDelete(){
        accountService.delete(3);
    }

}

抽取 JDBC 配置文件

将数据库连接信息单独抽取出来放在jdbc.properties中,然后在applicationContext.xml 中加载 jdbc.properties 配置文件获得连接信息。 首先,需要引入 context 命名空间和约束路径:

* 命名空间:

 xmlns:context="http://www.springframework.org/schema/context"


* 约束路径:

 http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd 

<!-- 引入 jdbc.properties -->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 把数据库连接池交给 IOC 容器 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

6.3 小结

  • DataSource 的创建权交由 Spring 容器去完成
  • QueryRunner 的创建权交由 Spring 容器去完成,使用构造方法传递 DataSource
  • Spring 容器加载 properties 文件
    <context:property-placeholder location="xx.properties"/>
    <property name="" value="${key}"/>

7. Spring 注解开发

Spring 是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替 xml 配置文件可以简化配置,提高开发效率。

7.1 Spring 常用注解

介绍

Spring 常用注解主要是替代 <bean> 的配置

  • @Component - 使用在类上用于实例化 Bean
  • @Controller - 使用在 web 层类上用于实例化 Bean
  • @Service - 使用在 service 层类上用于实例化 Bean
  • @Repository - 使用在 dao 层类上用于实例化 Bean
  • @Autowired - 使用在字段上用于根据类型依赖注入;当使用注解注入属性时,set 方法可以省略
  • @Qualifier - 结合 @Autowired 一起使用,根据名称进行依赖注入;在自动按照类型注入基础之上,再按照 -Bean 的 id 注入;它给字段注入时必须和 @Autowired 一起使用,但是给方法参数注入时可以独立使用
  • @Resource - 相当于 @Autowired + @Qualifier,按照名称进行注入
  • @Value - 注入普通属性
  • @Scope - 标注 Bean 的作用范围
  • @PostConstruct - 使用在方法上标注该方法是 Bean 的初始化方法
  • @PreDestroy - 使用在方法上标注该方法是 Bean 的销毁方法

说明:JDK 11 以后完全移除了 javax 扩展导致不能使用 @resource 注解,如果要使用它需要在 Maven 引入依赖:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

注意:使用注解进行开发时,需要在 applicationContext.xml 中配置组件扫描,作用是指定哪个包及其子包下的 Bean 需要进行扫描以便识别使用注解配置的类、字段和方法。

<!-- 注解的组件扫描 -->
<context:component-scan base-package="com.zm"></context:component-scan>

7.2 实现

7.2.1 Bean 实例化(IOC)

  以xml方式配置:

<bean id="userDao1" class="com.zm.dao.impl.UserDaoImpl"></bean>

使用 @Component 或 @Repository 标识 UserDaoImpl 需要 Spring 进行实例化。

// @Component(value = "userDao")
@Repository // 如果没有写 value 属性值,Bean 的 id 为:类名首字母小写
public class UserDaoImpl implements UserDao {
    ...
}

7.2.2 属性依赖注入(DI)

   以xml方式配置:

<bean id="userService" class="com.zm.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao1"/>
</bean>

使用 @Autowired 或者 @Autowired + @Qulifier 或者 @Resource 进行 userDao 的注入

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    // @Qualifier("userDao1")
    // @Resource(name = "userDao1")
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.uDao = userDao;
    }
}

7.2.3 @Value

使用 @Value 进行字符串的注入,结合 SpEL (Spring Expression Language) 表达式获得配置参数

@Service
public class UserServiceImpl implements UserService {
    @Value("注入普通数据")
    private String str;

    @Value("${jdbc.driver}")
    private String driver;
}

7.2.4 @Scope

<bean scope=""/>
使用 @Scope 标注 Bean 的范围

@Service
@Scope("singleton")
public class UserServiceImpl implements UserService {
    ...
}

7.2.5 Bean 生命周期

<bean init-method="init" destroy-method="destory" />

使用 @PostConstruct 标注初始化方法,使用 @PreDestroy 标注销毁方法

@PostConstruct
public void init(){
    System.out.println("初始化方法....");
}

@PreDestroy
public void destroy(){
    System.out.println("销毁方法.....");
}

7.3 Spring 常用注解整合 DbUtils

步骤分析:

  1. 拷贝 xml 配置项目,改为注解配置项目
  2. 修改 AccountDaoImpl 实现类
  3. 修改 AccountServiceImpl 实现类
  4. 修改 Spring 核心配置文件
  5. 编写测试代码

修改 AccountDaoImpl 实现类

@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private QueryRunner queryRunner;
    ...
}

修改 AccountServiceImpl 实现类

@Service(value = "accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    ...
}

修改 spring 核心配置文件

<?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:component-scan base-package="com.zm"/>

    <!-- 引入 jdbc.properties -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 把数据库连接池交给 IOC 容器 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 把 QueryRunner 交给 IOC 容器 -->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>

</beans>

7.4 Spring 新注解

使用上面的注解还不能全部替代 xml 配置文件,还需要使用注解替代的配置如下:

  • 非自定义的 Bean 的配置:<bean>
  • 加载 properties 文件的配置:<context:property-placeholder>
  • 组件扫描的配置:<context:component-scan>
  • 引入其他文件:<import>
注解功能介绍
@Configuration- 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@Bean- 使用在方法上,标注将该方法的返回值存储到 Spring 容器中
@PropertySource- 用于加载 properties 文件中的配置
@ComponentScan- 用于指定 Spring 在初始化容器时要扫描的包
@Import- 用于导入其他配置类

7.5 Spring 纯注解整合 DbUtils

步骤分析:

  1. 编写 Spring 核心配置类
  2. 编写数据库配置信息类
  3. 编写测试代码

编写 Spring 核心配置类

@Configuration
@ComponentScan("com.zm")
@Import(DataSourceConfig.class)
public class SpringConfig {
    @Bean("queryRunner")
    public QueryRunner getQueryRunner(@Autowired DataSource dataSource){
        return new QueryRunner(dataSource);
    }
}

编写数据库配置信息类

@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {
    @Value("${jdbc.driverClassName}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")
    public DataSource getDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }
}

编写测试代码

public class AccountServiceTest {

    /* ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("com/zm/service/applicationContext.xml");
    AccountService accountService = (AccountService) applicationContext.getBean("accountService"); */

    // 当前改成了纯注解形式
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    AccountService accountService = (AccountService) applicationContext.getBean("accountService");

    ...
}

7.6 Spring 整合 Junit

7.6.1 普通 Junit 测试问题

在普通的测试类中,需要开发者手动加载配置文件并创建 Spring 容器,然后通过 Spring 相关 API 获得 Bean 实例;如果不这么做,那么无法从容器中获得对象。

开发者可以让 Spring Junit 负责创建 Spring 容器来简化这个操作,直接在测试类注入 Bean 实例;但是需要将配置文件的名称告诉它。

7.6.2 Spring 整合 Junit

步骤分析:

  1. 导入 spring 集成 Junit 的坐标
  2. 使用 @Runwith 注解替换原来的运行器
  3. 使用 @ContextConfiguration 指定配置文件或配置类
  4. 使用 @Autowired 注入需要测试的对象
  5. 创建测试方法进行测试

1. 导入 spring 集成 Junit 的坐标

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

2. 使用 @Runwith 注解替换原来的运行器

@RunWith(SpringJUnit4ClassRunner.class)
// @RunWith:指定JUnit 的运行环境   SpringJUnit4ClassRunner:是Spring提供的作为JUnit运行环境的类
public class AccountServiceTest {
    ...
}

3. 使用 @ContextConfiguration 指定配置文件或配置类

@RunWith(SpringJUnit4ClassRunner.class)
// @ContextConfiguration(value = {"classpath:applicationContext.xml"})
@ContextConfiguration(classes = {SpringConfig.class})
public class AccountServiceTest {
    ...   
}

4. 使用 @Autowired 注入需要测试的对象

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;
    ...   
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值