
Spring 概述
Spring 是什么
Spring 是分层的 Java SE/EE 应用 full-stack (全栈式) 轻量级开源框架。
全栈式:对各种主流技术和框架都进行了整合,同时对三层架构都提供解决方案。
轻量级和重量级的划分主要依据就是看它使用了多少服务,启动时需要加载的资源多少以及耦合度等等。
提供了表现层 Spring MVC 和持久层 Spring JDBC Template 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
Spring 两大核心:
- IOC - Inverse Of Control 控制反转:把对象的创建权交给 Spring 
- AOP - Aspect Oriented Programming 面向切面编程:在不修改源码的情况下,对方法进行增强 
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) 
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)方便集成各种优秀框架
- 不排除各种优秀的开源框架,内部提供了对各种优秀框架的直接支持 
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
初识 IOC
概述
控制反转(Inverse Of Control)是一种设计思想,它的目的是指导我们设计出更加松耦合的程序。
控制:在 java 中指的是对象的控制权限(创建、销毁)。
反转:指的是对象控制权从由“开发者在类中手动控制”反转到由“ Spring 容器控制”。
例子:
- 传统方式 - 需要一个 - userDao实例,需要开发者自己手动创建- new UserDao();
- IOC 方式 - 需要一个 - userDao实例,直接从 spring 的 IOC 容器获得,对象的创建权交给了spring 控制
自定义 IOC 容器
介绍
需求:实现 Service 层与 Dao 层代码解耦合
步骤分析:
- 创建 java 项目,导入自定义 IOC 相关坐标 
- 编写 Dao 接口和实现类 
- 编写 Service 接口和实现类 
- 编写测试代码 
实现
创建 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.0modelVersion>
    <groupId>com.rendagroupId>
    <artifactId>jdbc_springartifactId>
    <version>1.0-SNAPSHOTversion>
    
    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8maven.compiler.encoding>
        <java.version>1.11java.version>
        <maven.compiler.source>1.11maven.compiler.source>
        <maven.compiler.target>1.11maven.compiler.target>
    properties>
    <dependencies>
        <dependency>
            <groupId>dom4jgroupId>
            <artifactId>dom4jartifactId>
            <version>1.6.1version>
        dependency>
        <dependency>
            <groupId>jaxengroupId>
            <artifactId>jaxenartifactId>
            <version>1.1.6version>
        dependency>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
        dependency>
    dependencies>
project>
编写 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.renda.dao.impl.UserDaoImpl").newInstance();
    }
    @Override
    public void save() {
        userDao.save();
    }
}
测试代码:
@Test
public void test1() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
    IUserService userService = new UserServiceImpl();
    userService.save();
}
改造步骤分析:
- 准备一个配置文件 
- 编写一个工厂工具类,工厂类使用 dom4j 来解析配置文件,获取到类的全路径 
- 使用反射生成对应类的实例对象,存到 Map 中,这个 Map 就是 IOC 容器 
为了解决反射方式的硬编码问题,先编写 beans.xml 配置文件
<beans>
    
    <bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">bean>
beans>
编写 BeanFactory 工具类
public class BeanFactory {
    private static Map 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 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.renda.dao.impl.UserDaoImpl").newInstance();
    // 反射方式 + 配置文件
    userDao = (IUserDao) BeanFactory.getBean("userDao");
}
小结
- 其实升级后的 - BeanFactory就是一个简单的 Spring 的 IOC 容器所具备的功能。
- 之前需要一个 - userDao实例,开发者自己手动创建- new UserDao();
- 现在需要一个 - userDao实例,直接从 spring 的 IOC 容器获得,对象的创建权交给了 spring 控制
- 最终目标:代码解耦合 
Spring 快速入门
介绍
需求:借助 spring 的 IOC 实现 service 层与 DAO 层代码解耦合
步骤分析:
- 创建 java 项目,导入 spring 开发基本坐标 
- 编写 DAO 接口和实现类 
- 创建 spring 核心配置文件 
- 在 spring 配置文件中配置 - UserDaoImpl
- 使用 spring 相关 API 获得 Bean 实例 
实现
创建 java 项目,导入 spring 开发基本坐标
<dependencies>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.1.5.RELEASEversion>
    dependency>
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
    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">
    
    <bean id="userDao" class="com.renda.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 的开发步骤总结
- 导入坐标 
- 创建 - Bean
- 创建 - applicationContext.xml
- 在配置文件中进行 Bean 配置 
- 创建 - ApplicationContext对象,执行- getBean
Spring 相关 API
API 继承体系介绍
Spring 的 API 体系异常庞大,现在只关注两个 BeanFactory 和 ApplicationContext
BeanFactory 是 ApplicationContext 的父接口,ApplicationContext 接口下又有 FileSystemXmlApplicationContext 和 ClassPathXmlApplicationContext 子接口
BeanFactory
BeanFactory 是 IOC 容器的核心接口,它定义了 IOC 的基本功能
特点:在第一次调用 getBean() 方法时,创建指定对象的实例
@Test
public void test2(){
    // 核心接口,不会创建 bean 对象存到容器中
    BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
    // getBean 的时候才真正创建 bean 对象
    IUserDao userDao = (IUserDao) xmlBeanFactory.getBean("userDao");
    // 调用方法
    userDao.save();
}
ApplicationContext
代表应用上下文对象,可以获得 spring 中 IOC 容器的 Bean 对象
特点:在 spring 容器启动时,加载并创建所有对象的实例
常用实现类:
- ClassPathXmlApplicationContext- 它是从类的根路径下加载配置文件,推荐使用这种。
- FileSystemXmlApplicationContext- 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
- AnnotationConfigApplicationContext- 当使用注解配置容器对象时,需要使用此类来创建 spring 容器,它用来读取注解。
常用方法:
- Object getBean(String name);- 根据 Bean 的 id 从容器中获得 Bean 实例,返回是 Object,需要强转。
- T getBean(ClassrequiredType);- 根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。
- T getBean(String name,ClassrequiredType);- 根据 Bean 的 id 和类型获得 Bean 实例,解决容器中相同类型 Bean 有多个情况。
Spring 配置文件
Bean 标签基本配置
用于配置对象交由 Spring 来创建。
默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
<bean id="" class=""/>
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 的垃圾回收器回收了
Bean 生命周期配置
applicationContext.xml
<bean id="userDao" class="com.renda.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();
}
Bean 实例化三种方式
无参构造方法实例化
它会根据默认无参构造方法来创建类对象,如果 bean 中没有默认无参构造函数,将会创建失败
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>
工厂静态方法实例化
应用场景:依赖的 jar 包中有个 A 类,A 类中有个静态方法 m1,m1 方法的返回值是一个 B 对象。如果频繁使用 B 对象,此时可以将 B 对象的创建权交给 spring 的 IOC 容器,以后在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。
public class StaticFactoryBean {
    public static IUserDao createUserDao(){
        return  new UserDaoImpl();
    }
}
<?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">
    
    
    
    <bean id="userDao" class="com.renda.factory.StaticFactoryBean" factory-method="createUserDao"/>
beans>
工厂普通方法实例化
应用场景:依赖的 jar 包中有个 A 类,A 类中有个普通方法 m1,m1 方法的返回值是一个 B 对象。如果我们频繁使用 B 对象,此时我们可以将 B 对象的创建权交给 spring 的 IOC 容器,以后我们在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。
public class DynamicFactoryBean {
    public IUserDao createUserDao(){
        return  new UserDaoImpl();
    }
}
<?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">
    
    
    
    
    <bean id="dynamicFactoryBean" class="com.renda.factory.DynamicFactoryBean"/>
    <bean id="userDao" factory-bean="dynamicFactoryBean" factory-method="createUserDao"/>
beans>
Bean 依赖注入方式
依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。
在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是通过框架把持久层对象传入业务层,而不用手动去获取。
Bean 依赖注入方式
构造方法
在 UserServiceImpl 中创建有参构造
public class UserServiceImpl implements IUserService {
    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.renda.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.renda.service.impl.UserServiceImpl">
    
    <constructor-arg name="userDao" ref="userDao"/>
bean>
`set` 方法
在 UserServiceImpl 中创建 set 方法
public class UserServiceImpl implements IUserService {
    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.renda.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.renda.service.impl.UserServiceImpl">
    
    
    
    
    <property name="userDao" ref="userDao">property>
bean>
P 命名空间注入
P 命名空间注入本质也是 set 方法注入,但比起上述的 set 方法注入更加方便,主要体现在配置文件 applicationContext.xml 中。
首先,需要引入 P 命名空间:
xmlns:p="http://www.springframework.org/schema/p"
其次,需要修改注入方式:
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.renda.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
Bean 依赖注入的数据类型
上面操作,都是注入 Bean 对象,除了对象的引用可以注入,普通数据类型和集合都可以在容器中进行注入。
注入数据的三种数据类型:
- 普通数据类型 
- 引用数据类型 
- 集合数据类型 
之前的操作都是对 UserDao 对象的引用进行注入的,属于引用数据类型注入。下面将以 set 方法注入为例,演示普通数据类型和集合数据类型的注入。
注入普通数据类型
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.renda.dao.impl.UserDaoImpl">
    
    <property name="username" value="张人大"/>
    <property name="age" value="25"/>
bean>
注入集合数据类型
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 list;public void setList(List list) {this.list = list;
    }@Overridepublic void save() {
        System.out.println("List:" + list);
    }
}<bean id="user" class="com.renda.domain.User">
    <property name="username" value="布莱尔"/>
    <property name="age" value="18"/>
bean>
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">
    <property name="list">
        <list>
            <value>aaavalue>
            <ref bean="user"/>
        list>
    property>
bean>
Set 集合注入
public class UserDaoImpl implements IUserDao {
    private Set set;public void setSet(Set set) {this.set = set;
    }@Overridepublic void save() {
        System.out.println("Set:" + set);
    }
}<bean id="user" class="com.renda.domain.User">
    <property name="username" value="布莱尔"/>
    <property name="age" value="18"/>
bean>
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">
    <property name="set">
        <set>
            <value>bbbvalue>
            <ref bean="user"/>
        set>
    property>
bean>
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.renda.dao.impl.UserDaoImpl">
    <property name="array">
        <array>
            <value>cccvalue>
            <ref bean="user"/>
        array>
    property>
bean>
Map 集合注入
public class UserDaoImpl implements IUserDao {
    private Map map;public void setMap(Map map) {this.map = map;
    }@Overridepublic void save() {
        System.out.println("Map:" + map);
    }
}<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">
    <property name="map">
        <map>
            <entry key="k1" value="ddd"/>
            <entry key="k2" value-ref="user"/>
        map>
    property>
bean>
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.renda.dao.impl.UserDaoImpl">
    <property name="properties">
        <props>
            <prop key="k1">v1prop>
            <prop key="k2">v2prop>
            <prop key="k3">v3prop>
        props>
    property>
bean>
配置文件模块化
实际开发中,Spring 的配置内容非常多,这就导致 Spring 配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,也就是所谓的配置文件模块化。
并列的多个配置文件:
ApplicationContext ac = new ClassPathXmlApplicationContext("beans1.xml", "beans2.xml", "...");
主从配置文件:
<import resource="applicationContext-xxx.xml"/>
注意:
同一个 xml 中不能出现相同名称的 bean,如果出现会报错。
多个 xml 如果出现相同名称的 bean,不会报错,但是后加载的会覆盖前加载的 bean。
小结
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 的分文件
IOC 实战 - DbUtils
DbUtils 是什么?
DbUtils 是 Apache 的一款用于简化 Dao 代码的工具类,它底层封装了 JDBC 技术。
核心对象:
QueryRunner queryRunner = new QueryRunner(DataSource dataSource);
核心方法:
- int update()- 执行增、删、改语句
- T query()- 执行查询语句
- ResultSetHandler- 这是一个接口,主要作用是将数据库返回的记录封装到实体对象
查询数据库所有账户信息到 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 list = queryRunner.query(sql, new BeanListHandler (Account.class));// 打印结果for (Account account : list) {
            System.out.println(account);
        }
    }
}Spring 的 `xml` 整合 `DbUtils`
介绍
需求:基于 Spring 的 xml 配置实现账户的 CRUD 案例
步骤分析:
- 准备数据库环境 
- 创建 java 项目,导入坐标 
- 编写 Account 实体类 
- 编写 AccountDao 接口和实现类 
- 编写 AccountService 接口和实现类 
- 编写 spring 核心配置文件 
- 编写测试代码 
实现
准备数据库环境
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.0modelVersion>
    <groupId>com.rendagroupId>
    <artifactId>spring_dbutilsartifactId>
    <version>1.0-SNAPSHOTversion>
    
    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8maven.compiler.encoding>
        <java.version>1.11java.version>
        <maven.compiler.source>1.11maven.compiler.source>
        <maven.compiler.target>1.11maven.compiler.target>
    properties>
    <dependencies>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.47version>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.9version>
        dependency>
        <dependency>
            <groupId>commons-dbutilsgroupId>
            <artifactId>commons-dbutilsartifactId>
            <version>1.6version>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.1.5.RELEASEversion>
        dependency>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
        dependency>
        <dependency>
            <groupId>javax.annotationgroupId>
            <artifactId>javax.annotation-apiartifactId>
            <version>1.3.2version>
        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 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 findAll() {
        List list = null;// 编写 sql
        String sql = "select * from account";try {// 执行 sql
            list = queryRunner.query(sql, new BeanListHandler(Account.class));
        } catch (SQLException e) {
            e.printStackTrace();
        }return list;
    }@Overridepublic Account findById(Integer id) {
        Account query = null;// 编写 sql
        String sql = "select * from account where id = ?";try {
            query = queryRunner.query(sql, new BeanHandler(Account.class), id);
        } catch (SQLException e) {
            e.printStackTrace();
        }return query;
    }@Overridepublic void save(Account account) {
        String sql = "insert into account values(null, ?, ?)";try {
            queryRunner.update(sql, account.getName(), account.getMoney());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }@Overridepublic 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();
        }
    }@Overridepublic 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 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 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
">
    
    <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>
    
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    bean>
    
    <bean id="accountDao" class="com.renda.dao.impl.AccountDaoImpl">
        <property name="queryRunner" ref="queryRunner"/>
    bean>
    
    <bean id="accountService" class="com.renda.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("renda");
        account.setMoney(888d);
        accountService.save(account);
    }
    // 测试查询
    @Test
    public void testFindById(){
        Account account = accountService.findById(3);
        System.out.println(account);
    }
    // 测试查询所有
    @Test
    public void testFindAll(){
        List all = accountService.findAll();for (Account account : all) {
            System.out.println(account);
        }
    }// 测试更新@Testpublic void testUpdate(){
        Account account = new Account();
        account.setId(3);
        account.setName("Blair");
        account.setMoney(2000d);
        accountService.update(account);
    }// 测试删除@Testpublic void testDelete(){
        accountService.delete(3);
    }
}抽取 JDBC 配置文件
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
<context:property-placeholder location="classpath:jdbc.properties"/>
<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>
小结
* DataSource 的创建权交由 Spring 容器去完成
* QueryRunner 的创建权交由 Spring 容器去完成,使用构造方法传递 DataSource
* Spring 容器加载 properties 文件    
<context:property-placeholder location="xx.properties"/>
<property name="" value="${key}"/>
Spring 注解开发
Spring 是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替 xml 配置文件可以简化配置,提高开发效率。
Spring 常用注解
介绍
Spring 常用注解主要是替代  的配置
- @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.annotationgroupId>
    <artifactId>javax.annotation-apiartifactId>
    <version>1.3.2version>
dependency>
注意:使用注解进行开发时,需要在 applicationContext.xml 中配置组件扫描,作用是指定哪个包及其子包下的 Bean 需要进行扫描以便识别使用注解配置的类、字段和方法。
<context:component-scan base-package="com.renda">context:component-scan>
实现
Bean 实例化(IOC)
<bean id="userDao1" class="com.renda.dao.impl.UserDaoImpl">bean>
使用 @Component 或 @Repository 标识 UserDaoImpl 需要 Spring 进行实例化。
// @Component(value = "userDao")
@Repository // 如果没有写 value 属性值,Bean 的 id 为:类名首字母小写
public class UserDaoImpl implements UserDao {
    ...
}
属性依赖注入(DI)
<bean id="userService" class="com.lagou.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;
    }
}
`@Value`
使用 @Value 进行字符串的注入,结合 SpEL (Spring Expression Language) 表达式获得配置参数
@Service
public class UserServiceImpl implements UserService {
    @Value("注入普通数据")
    private String str;
    @Value("${jdbc.driver}")
    private String driver;
}
`@Scope`
<bean scope=""/>
使用 @Scope 标注 Bean 的范围
@Service
@Scope("singleton")
public class UserServiceImpl implements UserService {
    ...
}
`Bean` 生命周期
<bean init-method="init" destroy-method="destory" />
使用 @PostConstruct 标注初始化方法,使用 @PreDestroy 标注销毁方法
@PostConstruct
public void init(){
    System.out.println("初始化方法....");
}
@PreDestroy
public void destroy(){
    System.out.println("销毁方法.....");
}
Spring 常用注解整合 `DbUtils`
步骤分析:
- 拷贝 - xml配置项目,改为注解配置项目
- 修改 - AccountDaoImpl实现类
- 修改 - AccountServiceImpl实现类
- 修改 Spring 核心配置文件 
- 编写测试代码 
修改 `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.renda"/>
    
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <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>
    
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"/>
    bean>
beans>
Spring 新注解
使用上面的注解还不能全部替代 xml 配置文件,还需要使用注解替代的配置如下:
非自定义的 Bean 的配置:<bean>
加载 properties 文件的配置:<context:property-placeholder>
组件扫描的配置:<context:component-scan>
引入其他文件:<import>
- @Configuration- 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
- @Bean- 使用在方法上,标注将该方法的返回值存储到 Spring 容器中
- @PropertySource- 用于加载 properties 文件中的配置
- @ComponentScan- 用于指定 Spring 在初始化容器时要扫描的包
- @Import- 用于导入其他配置类
Spring 纯注解整合 `DbUtils`
步骤分析:
- 编写 Spring 核心配置类 
- 编写数据库配置信息类 
- 编写测试代码 
编写 Spring 核心配置类
@Configuration
@ComponentScan("com.renda")
@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/renda/service/applicationContext.xml");
    AccountService accountService = (AccountService) applicationContext.getBean("accountService"); */
    // 当前改成了纯注解形式
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    AccountService accountService = (AccountService) applicationContext.getBean("accountService");
    ...
}
Spring 整合 Junit
普通 Junit 测试问题
在普通的测试类中,需要开发者手动加载配置文件并创建 Spring 容器,然后通过 Spring 相关 API 获得 Bean 实例;如果不这么做,那么无法从容器中获得对象。
开发者可以让 Spring Junit 负责创建 Spring 容器来简化这个操作,直接在测试类注入 Bean 实例;但是需要将配置文件的名称告诉它。
Spring 整合 Junit
步骤分析:
- 导入 spring 集成 Junit 的坐标 
- 使用 - @Runwith注解替换原来的运行器
- 使用 - @ContextConfiguration指定配置文件或配置类
- 使用 - @Autowired注入需要测试的对象
- 创建测试方法进行测试 
导入 spring 集成 Junit 的坐标
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-testartifactId>
    <version>5.1.5.RELEASEversion>
dependency>
<dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.12version>
dependency>
使用 `@Runwith` 注解替换原来的运行器
@RunWith(SpringJUnit4ClassRunner.class)
public class AccountServiceTest {
    ...
}
使用 `@ContextConfiguration` 指定配置文件或配置类
@RunWith(SpringJUnit4ClassRunner.class)
// @ContextConfiguration(value = {"classpath:applicationContext.xml"})
@ContextConfiguration(classes = {SpringConfig.class})
public class AccountServiceTest {
     ...   
}
使用 `@Autowired` 注入需要测试的对象
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;
    ...   
}