Spring

Spring

Featured image

第1章 Spring是什么

1.1 概念

1.2 优点

1.2.1 方便解耦,简化开发

1.2.2 方便集成各种优秀框架

1.2.3 降低 Java EE API 的使用难度

1.2.4 方便程序的测试

1.2.5 AOP 编程的支持

1.2.6 声明式事务的支持


第2章 Spring体系结构

2.1 Spring的体系结构

image

2.1.1 Data Access/Integration(数据访问/集成)

  1. JDBC 模块:提供了一个JDBC的抽象层,大幅度减少了在开发过程中对数据库操作的编码。
  2. ORM 模块:对流行的对象关系映射API,包括JPA、JDO、Hibernate和iBatis提供了的集成层。
  3. OXM 模块:提供了一个支持对象/XML映射的抽象层实现,如JAXB、Castor、XMLBeans、JiBX 和 XStream。
  4. JMS 模块:指Java消息服务,包含的功能为生产和消费的信息。
  5. Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类,并为所有的POJO。

2.1.2 Web模块

  1. Web模块:提供了基本的Web开发集成特性,例如多文件上传功能、使用的Servlet监听器的IoC容器初始化以及Web应用上下文。
  2. Servlet模块:包括Spring模型—视图—控制器(MVC)实现Web应用程序。
  3. Struts模块:包含支持类内的 Spring 应用程序,集成了经典的Struts Web层。
  4. Portlet模块:提供了在Portlet环境中使用MVC实现,类似Web-Servlet模块的功能。

2.1.3 Core Container(核心容器)

  1. Beans模块:提供了BeanFactory,是工厂模式的经典实现,Spring将管理对象称为Bean。
  2. Core核心模块:提供了Spring 框架的基本组成部分,包括IoC和DI功能。
  3. Context上下文模块:建立在核心和Beans模块的基础之上,它是访问定义和配置任何对象的媒介。ApplicationContext接口是上下文模块的焦点。
  4. Expression Language模块:是运行时查询和操作对象图的强大的表达式语言。

2.1.4 其他模块

  1. AOP模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。
  2. Aspects模块:提供与AspectJ的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
  3. Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
  4. Test模块:支持Spring组件,使用JUnit或TestNG框架的测试。

第3章 Spring的目录结构和JAR包介绍

3.1 JAR包介绍与下载

3.1.1 JAR包介绍

名称 作用
spring-core-3.2.13.RELEASE.jar 包含 Spring 框架基本的核心工具类,Spring 其他组件都要用到这个包中的类,是其他组件的基本核心。
spring-beans-3.2.13.RELEASE.jar 所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行Inversion of Control(IoC)或者Dependency Injection(DI)操作相关的所有类。
spring-context-3.2.13.RELEASE.jar RSpring 提供在基础IoC功能上的扩展服务,此外还提供许多企业级服务的支持,如邮件服务、任务调度、JNDI定位、EJB集成、远程访问、缓存以及各种视图层框架的封装等。
spring-expression-3.2.13.RELEASE.jar 定义了 Spring 的表达式语言。需要注意的是,在使用Spring开发时,除了Spring自带的JAR包以外,还需要一个第三方JAR包commons.logging处理日志信息。

3.1.2 JAR包下载


第4章 Spring IoC容器

4.1 BeanFactory

/** 创建BeanFactory实例时,
  * 需要提供Spring所管理容器的详细配置信息,
  * 这些信息通常采用 XML 文件形式管理。
  */
BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource("D://applicationContext.xml"));

4.2 ApplicationContext

4.2.1 ClassPathXmlApplicationContext

/** 该类从类路径ClassPath中寻找指定的XML配置文件,
  * 找到并装载完成ApplicationContext的实例化工作。
  */
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
// configLocation参数用于指定Spring配置文件的名称和位置,如applicationContext.xml

4.2.2 FileSystemXmlApplicationContext

/** 该类从指定的文件系统路径中寻找指定的XML配置文件,
  * 找到并装载完成ApplicationContext的实例化工作。
  */
ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
/** 它与ClassPathXmlApplicationContext的区别是:
  * 在读取 Spring 的配置文件时,
  * FileSystemXmlApplicationContext不再从类路径中读取配置文件,
  * 而是通过参数指定配置文件的位置,
  * 它可以获取类路径之外的资源,
  * 如“F:/workspaces/applicationContext.xml”。
  */

第5章 第一个Spring程序

5.1 详细步骤

1) 创建项目

spring-core-3.2.13.RELEASE.jar
spring-beans-3.2.13.RELEASE.jar
spring-context-3.2.13.RELEASE.jar
spring-expression-3.2.13.RELEASE.jar
commons-logging.1.2.jar

2) 创建 PersonDao 接口

package com.spring.ioc;

public interface PersonDao {
    public void add();
}

3) 创建接口实现类 PersonDaoImpl

package com.spring.ioc;

public class PersonDaoImpl implements PersonDao {
    @Override
    public void add() {
        System.out.println("add()执行了...");
    }
}

4) 创建 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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <!-- 由 Spring容器创建该类的实例对象 -->
    <bean id="personDao" class="com.spring.ioc.PersonDaoImpl" />
    <bean id="personService" class="com.spring.ioc.PersonServiceImpl">
        <!-- 将personDao实例注入personService实例中 -->
        <property name="personDao" ref="personDao"/>
    </bean>
</beans>

5) 编写测试类

package com.spring.ioc;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FirstTest {
    @Test
    public void test1() {
        // 定义Spring配置文件的路径
        String xmlPath = "applicationContext.xml";
        // 初始化Spring容器,加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 通过容器获取personDao实例
        PersonDao personDao = (PersonDao) applicationContext
                .getBean("personDao");
        personDao.add();
    }
}

6) 运行项目并查看结果

add()执行了…


第6章 Spring DI(依赖注入)

6.1 依赖注入的两种实现方式

  1. 属性 setter 注入
    指 IoC 容器使用 setter 方法注入被依赖的实例。通过调用无参构造器或无参 static 工厂方法实例化 bean 后,调用该 bean 的 setter 方法,即可实现基于 setter 的 DI。
  2. 构造方法注入
    指 IoC 容器使用构造方法注入被依赖的实例。基于构造器的 DI 通过调用带参数的构造方法实现,每个参数代表一个依赖。

1) 创建 PersonService 接口

package com.spring.ioc;

public interface PersonService {
    public void addPerson();
}

2) 创建接口实现类 PersonServiceImpl

package com.spring.ioc;

public class PersonServiceImpl implements PersonService {
    // 定义接口声明
    private PersonDao personDao;
    // 提供set()方法,用于依赖注入
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }
    // 实现PersonService接口的方法
    @Override
    public void addPerson() {
        personDao.add(); // 调用PersonDao中的add()方法
        System.out.println("addPerson()执行了...");
    }
}

3) 在 applicationContext.xml 中添加配置信息

    <bean id="personService" class="com.spring.ioc.PersonServiceImpl">
        <!-- 将personDao实例注入personService实例中 -->
        <property name="personDao" ref="personDao"/>
    </bean>

4) 编写测试方法

  @Test
    public void test2() {
        // 定义Spring配置文件的路径
        String xmlPath = "applicationContext.xml";
        // 初始化Spring容器,加载   配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 通过容器获取personService实例
        PersonService personService = (PersonService) applicationContext
                .getBean("personService");
        // 调用personService的addPerson()方法
        personService.addPerson();
    }

5) 运行项目并查看结果

add()执行了…
addPerson()执行了…


第7章 Spring Bean

7.1 Spring Bean的配置及常用属性

7.1.1 定义 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-3.2.xsd">
    <!-- 使用id属性定义person1,其对应的实现类为com.mengma.person1 -->
    <bean id="person1" class="com.mengma.damain.Person1" />
    <!--使用name属性定义person2,其对应的实现类为com.mengma.domain.Person2-->
    <bean name="Person2" class="com.mengma.domain.Person2"/>
</beans>

在上述代码中,分别使用 id 和 name 属性定义了两个 Bean,并使用 class 元素指定了 Bean 对应的实现类。

7.1.2 元素的常用属性

属性名称 描述
id 是一个 Bean 的唯一标识符,Spring 容器对 Bean 的配置和管理都通过该属性完成
name Spring 容器同样可以通过此属性对容器中的 Bean 进行配置和管理,name 属性中可以为 Bean 指定多个名称,每个名称之间用逗号或分号隔开
class 该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,使用类的全限定名
scope 用于设定 Bean 实例的作用域,其属性值有 singleton(单例)、prototype(原型)、request、session 和 global Session。其默认值是 singleton
constructor-arg <bean>元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型
property <bean>元素的子元素,用于调用 Bean 实例中的 Set 方法完成属性赋值,从而完成依赖注入。该元素的 name 属性指定 Bean 实例中的相应属性名
ref <property> 和 <constructor-arg> 等元素的子元索,该元素中的 bean 属性用于指定对 Bean 工厂中某个 Bean 实例的引用
value <property> 和 <constractor-arg> 等元素的子元素,用于直接指定一个常量值
list 用于封装 List 或数组类型的依赖注入
set 用于封装 Set 类型属性的依赖注入
map 用于封装 Map 类型属性的依赖注入
entry <map> 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值

7.2 Spring实例化Bean的三种方法

7.2.1 构造器实例化

1) 创建项目并导入 JAR 包

2) 创建实体类

package com.spring.instance.constructor;

public class Person1 {
}

3) 创建 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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <bean id="person1" class="com.spring.instance.constructor.Person1" />
</beans>

4) 创建测试类

package com.spring.instance.constructor;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InstanceTest1 {
    @Test
    public void test() {
        // 定义Spring配置文件的路径
        String xmlPath = "com/spring/instance/constructor/ApplicationContext.xml";
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 通过容器获取id为person1的实例
        System.out.println(applicationContext.getBean("person1"));
    }
}

5) 运行程序并查看结果

com.spring.instance.constructor.Person1@52e6fdee

7.2.2 静态工厂方式实例化

1) 创建实体类

package com.spring.instance.static_factory;

public class Person2 {
}

2) 创建静态工厂类

package com.spring.instance.static_factory;

public class MyBeanFactory {
    // 创建Bean实例的静态工厂方法
    public static Person2 createBean() {
        return new Person2();
    }
}

3) 创建 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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <bean id="person2" class="com.spring.instance.static_factory.MyBeanFactory"
          factory-method="createBean" />
</beans>

4) 创建测试类

package com.spring.instance.static_factory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InstanceTest2 {
    @Test
    public void test() {
        // 定义Spring配置文件的路径
        String xmlPath = "com/spring/instance/static_factory/applicationContext.xml"; 
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 通过容器获取id为person2实例
        System.out.println(applicationContext.getBean("person2"));
    }
}

5) 运行程序并查看结果

com.spring.instance.static_factory.Person2@9f116cc

7.2.3 实例工厂方式实例化

1) 创建实体类

package com.spring.instance.factory;

public class Person3 {
}

2) 创建实例工厂类

package com.spring.instance.factory;

public class MyBeanFactory {
    public MyBeanFactory() {
        System.out.println("person3工厂实例化中");
    }
    // 创建Bean的方法
    public Person3 createBean() {
        return new Person3();
    }
}

3) 创建 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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <!-- 配置实例工厂 -->
    <bean id="myBeanFactory" class="com.spring.instance.factory.MyBeanFactory" />
    <!-- factory-bean属性指定一个实例工厂,factory-method属性确定使用工厂中的哪个方法 -->
    <bean id="person3" factory-bean="myBeanFactory" factory-method="createBean" />
</beans>

4) 创建测试类

package com.spring.instance.factory;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InstanceTest3 {
    @Test
    public void test() {
        // 定义Spring配置文件的路径
        String xmlPath = "com/spring/instance/factory/applicationContext.xml"; 
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 通过容器获取id为person3实例
        System.out.println(applicationContext.getBean("person3"));
    }
}

5) 运行程序并查看结果

person3工厂实例化中
com.spring.instance.factory.Person3@6c64cb25

7.3 Spring中Bean的作用域

作用域的种类 说明
singleton 单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。
prototype 原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。
request 在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。
session 在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。
global Session 在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。

7.3.1 singleton 作用域

1) 创建类

2) 创建配置文件

<?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-3.2.xsd">
    <bean id="person" class="com.spring.scope.Person" scope="singleton"/>
</beans>

3) 创建测试类

package com.spring.scope;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class PersonTest {
    @Test
    public void test() {
        // 定义Spring配置文件路径
        String xmlPath = "com/spring/scope/applicationContext.xml";
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 输出获得实例
        System.out.println(applicationContext.getBean("person"));
        System.out.println(applicationContext.getBean("person"));
    }
}

4) 运行程序并查看结果

com.spring.scope.Person@52e6fdee
com.spring.scope.Person@52e6fdee

7.3.2 prototype 作用域

1) 修改配置文件

<bean id="person" class="com.mengma.scope.Person" scope="prototype"/>

2) 运行程序并查看结果

com.spring.scope.Person@59fd97a8
com.spring.scope.Person@f5ac9e4

7.4 Spring Bean的生命周期

image

  1. 根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean。
  2. 利用依赖注入完成 Bean 中所有属性值的配置注入。
  3. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
  4. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
  5. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  6. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
  7. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
  8. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
  10. 如果在 中指定了该 Bean 的作用范围为 scope="singleton",则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用范围为 scope="prototype",则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

7.5 Bean 的装配方式

7.5.1 Spring基于XML装配Bean

1) 创建 Person 类

package com.spring.assembly;

public class Person {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    // 重写toString()方法
    public String toString() {
        return "Person[name=" + name + ",age=" + age + "]";
    }
    // 默认无参的构造方法
    public Person() {
        super();
    }
    // 有参的构造方法
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
}

2) 创建 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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <!-- 使用设值注入方式装配Person实例 -->
    <bean id="person1" class="com.spring.assembly.Person">
        <property name="name" value="zhangsan" />
        <property name="age" value="20" />
    </bean>
    <!-- 使用构造方法装配Person实例 -->
    <bean id="person2" class="com.spring.assembly.Person">
        <constructor-arg index="0" value="lisi" />
        <constructor-arg index="1" value="21" />
    </bean>
</beans>

3) 创建测试类

package com.spring.assembly;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class XmlBeanAssemblyTest {
    @Test
    public void test() {
        // 定义Spring配置文件路径
        String xmlPath = "com/spring/assembly/applicationContext.xml";
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 设值方式输出结果
        System.out.println(applicationContext.getBean("person1"));
        // 构造方式输出结果
        System.out.println(applicationContext.getBean("person2"));
    }
}

4) 运行项目并查看结果

Person[name=zhangsan,age=20]
Person[name=lisi,age=21]

7.5.2 Spring基于Annotation装配Bean

注解 说明
@Component 可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),
并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。
@Repository 用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service 通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller 通常作用在控制层(如 Struts2 的 Action),用于将控制层的类标识为 Spring 中的 Bean,
其功能与 @Component 相同。
@Autowired 用于对 Bean 的属性变量、属性的 Set 方法及构造函数进行标注,
配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型进行装配。
@Resource 其作用与 Autowired 一样。其区别在于 @Autowired 默认按照 Bean 类型装配,
而 @Resource 默认按照 Bean 实例名称进行装配。
@Resource 中有两个重要属性:name 和 type。
Spring 将 name 属性解析为 Bean 实例名称,type 属性解析为 Bean 实例类型。
如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配。
@Qualifier 与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,
Bean 的实例名称由 @Qualifier 注解的参数指定。

1) 创建 DAO 层接口

package com.spring.annotation;

public interface PersonDao {
    public void add();
}

2) 创建 DAO 层接口的实现类

package com.spring.annotation;

import org.springframework.stereotype.Repository;
@Repository("personDao")
public class PersonDaoImpl implements PersonDao {
    @Override
    public void add() {
        System.out.println("Dao层的add()方法执行了...");
    }
}

3) 创建 Service 层接口

package com.spring.annotation;
public interface PersonService {
    public void add();
}

4) 创建 Service 层接口的实现类

package com.spring.annotation;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service("personService")
public class PersonServiceImpl implements PersonService {
    @Resource(name = "personDao")
    private PersonDao personDao;
    public PersonDao getPersonDao() {
        return personDao;
    }
    @Override
    public void add() {
        personDao.add();// 调用personDao中的add()方法
        System.out.println("Service层的add()方法执行了...");
    }
}

5) 创建 Action

package com.spring.annotation;

import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
@Controller("personAction")
public class PersonAction {
    @Resource(name = "personService")
    private PersonService personService;
    public PersonService getPersonService() {
        return personService;
    }
    public void add() {
        personService.add(); // 调用personService中的add()方法
        System.out.println("Action层的add()方法执行了...");
    }
}

6) 创建 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:aop="http://www.springframework.org/schema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    <!--使用context命名空间通知spring扫描指定目录进行注解的解析-->
    <context:component-scan base-package="com.spring.annotation"/>
</beans>

7) 创建测试类

package com.spring.annotation;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
    @Test
    public void test() {
        // 定义Spring配置文件路径
        String xmlPath = "com/spring/annotation/applicationContext.xml";
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 获得personAction实例
        PersonAction personAction = (PersonAction) applicationContext
                .getBean("personAction");
        // 调用personAction中的add()方法
        personAction.add();
    }
}

8) 运行程序并查看结果

Dao层的add()方法执行了…
Service层的add()方法执行了…
Action层的add()方法执行了…

7.5.3 Spring自动装配Bean

名称 说明
byName 根据Property的name自动装配,如果一个Bean的name和另一个Bean中的Property的name相同,则自动装配这个Bean到Property中。
byType 根据Property的数据类型(Type)自动装配,如果一个Bean的数据类型兼容另一个Bean中Property的数据类型,则自动装配。
constructor 根据构造方法的参数的数据类型,进行byType模式的自动装配。
autodetect 如果发现默认的构造方法,则用constructor模式,否则用byType模式。
no 默认情况下,不使用自动装配,Bean依赖必须通过ref元素定义

1) 创建包

注意将各个包含路径“ com/spring/annotation ”的地方修改为“ com/spring/autowire ”。

2) 修改PersonAction类和PersonServiceImpl类

package com.spring.autowire;

import org.springframework.stereotype.Controller;
import javax.annotation.Resource;

@Controller("personAction")
public class PersonAction {
    @Resource(name = "personService")
    private PersonService personService;
    public PersonService getPersonService() {
        return personService;
    }

    public void setPersonService(PersonService personService) {
        this.personService = personService;
    }

    public void add() {
        personService.add(); // 调用personService中的add()方法
        System.out.println("Action层的add()方法执行了...");
    }
}
package com.spring.autowire;

import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service("personService")
public class PersonServiceImpl implements PersonService {
    @Resource(name = "personDao")
    private PersonDao personDao;
    public PersonDao getPersonDao() {
        return personDao;
    }

    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }

    @Override
    public void add() {
        personDao.add();// 调用personDao中的add()方法
        System.out.println("Service层的add()方法执行了...");
    }

}

3) 修改 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    <bean id="personDao" class="com.spring.autowire.PersonDaoImpl" />
    <bean id="personService" class="com.spring.autowire.PersonServiceImpl"
          autowire="byName" />
    <bean id="personAction" class="com.spring.autowire.PersonAction"
          autowire="byName" />
</beans>

4) 运行程序并查看结果

Dao层的add()方法执行了…
Service层的add()方法执行了…
Action层的add()方法执行了…


第8章 Spring AOP

8.1 AOP的概念

名称 说明
Joinpoint(连接点) 指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。
Pointcut(切入点) 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。
Advice(通知) 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。
Target(目标) 指代理的目标对象。
Weaving(植入) 指把增强代码应用到目标上,生成代理对象的过程。
Proxy(代理) 指生成的代理对象。
Aspect(切面) 切入点和通知的结合。

8.2 Spring JDK动态代理

8.2.1 JDK 动态代理的示例

1) 创建项目

com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.2.jar
mysql-connector-java-8.0.20.jar
spring-aop-4.3.6.RELEASE.jar
spring-aspects-4.3.6.RELEASE.jar
spring-beans-4.3.6.RELEASE.jar
spring-context-4.3.6.RELEASE.jar
spring-core-4.3.6.RELEASE.jar
spring-expression-4.3.6.RELEASE.jar
spring-tx-4.3.6.RELEASE.jar

2) 创建接口 CustomerDao

package com.spring.dao;

public interface CustomerDao {
    public void add(); // 添加
    public void update(); // 修改
    public void delete(); // 删除
    public void find(); // 查询
}

3) 创建实现类 CustomerDaoImpl

package com.spring.dao;

public class CustomerDaoImpl implements CustomerDao {
    @Override
    public void add() {
        System.out.println("添加客户...");
    }
    @Override
    public void update() {
        System.out.println("修改客户...");
    }
    @Override
    public void delete() {
        System.out.println("删除客户...");
    }
    @Override
    public void find() {
        System.out.println("修改客户...");
    }
}

4) 创建切面类 MyAspect

package com.spring.jdk;
public class MyAspect {
    public void myBefore() {
        System.out.println("方法执行之前");
    }
    public void myAfter() {
        System.out.println("方法执行之后");
    }
}

5) 创建代理类 MyBeanFactory

package com.spring.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.spring.dao.CustomerDao;
import com.spring.dao.CustomerDaoImpl;
public class MyBeanFactory {
    public static CustomerDao getBean() {
        // 准备目标类
        final CustomerDao customerDao = new CustomerDaoImpl();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 使用代理类,进行增强
        return (CustomerDao) Proxy.newProxyInstance(
                MyBeanFactory.class.getClassLoader(),
                new Class[] { CustomerDao.class }, new InvocationHandler() {
                    public Object invoke(Object proxy, Method method,
                                         Object[] args) throws Throwable {
                        myAspect.myBefore(); // 前增强
                        Object obj = method.invoke(customerDao, args);
                        myAspect.myAfter(); // 后增强
                        return obj;
                    }
                });
                
/** 使用代理类对创建的实例 customerDao 中的方法进行增强,
  * 其中 Proxy 的 newProxyInstance()方法的  
  * 第一个参数是当前类的类加载器,
  * 第二参数是所创建实例的实现类的接口,
  * 第三个参数就是需要增强的方法。
  */
    }
}

6) 创建测试类 JDKProxyTest

package com.spring.jdk;
import org.junit.Test;
import com.spring.dao.CustomerDao;
public class JDKProxyTest {
    @Test
    public void test() {
        // 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
        CustomerDao customerDao = MyBeanFactory.getBean();
        // 执行方法
        customerDao.add();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

7) 运行项目并查看结果

方法执行之前
添加客户…
方法执行之后
方法执行之前
修改客户…
方法执行之后
方法执行之前
删除客户…
方法执行之后
方法执行之前
修改客户…
方法执行之后

8.3 Spring CGLIB动态代理

8.3.1 CGLIB 动态代理的示例

1) 创建目标类 GoodsDao

package com.spring.dao;
public class GoodsDao {
    public void add() {
        System.out.println("添加商品...");
    }
    public void update() {
        System.out.println("修改商品...");
    }
    public void delete() {
        System.out.println("删除商品...");
    }
    public void find() {
        System.out.println("修改商品...");
    }
}

2) 创建代理类 MyBeanFactory

package com.spring.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.spring.dao.GoodsDao;
import com.spring.jdk.MyAspect;
public class MyBeanFactory {
    public static GoodsDao getBean() {
        // 准备目标类
        final GoodsDao goodsDao = new GoodsDao();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
        Enhancer enhancer = new Enhancer();
        // 确定需要增强的类
        enhancer.setSuperclass(goodsDao.getClass());
        // 添加回调函数
        enhancer.setCallback(new MethodInterceptor() {
            // intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
            @Override
            public Object intercept(Object proxy, Method method, Object[] args,
                                    MethodProxy methodProxy) throws Throwable {
                myAspect.myBefore(); // 前增强
                Object obj = method.invoke(goodsDao, args); // 目标方法执行
                myAspect.myAfter(); // 后增强
                return obj;
            }
        });
        // 创建代理类
        GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
        return goodsDaoProxy;
    }
}

3) 创建测试类

package com.spring.cglib;
import org.junit.Test;
import com.spring.dao.GoodsDao;
public class CGLIBProxyTest {
    @Test
    public void test() {
        // 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
        GoodsDao goodsDao = MyBeanFactory.getBean();
        // 执行方法
        goodsDao.add();
        goodsDao.update();
        goodsDao.delete();
        goodsDao.find();
    }
}

4) 运行项目并查看结果

方法执行之前
添加商品…
方法执行之后
方法执行之前
修改商品…
方法执行之后
方法执行之前
删除商品…
方法执行之后
方法执行之前
修改商品…
方法执行之后

8.4 Spring 通知类型及创建AOP代理

8.4.1 Spring 通知类型

名称 说明
前置通知 org.springframework.aop.MethodBeforeAdvice
在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。
后置通知 org.springframework.aop.AfterReturningAdvice
在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。
环绕通知 org.aopalliance.intercept.MethodInterceptor
在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。
异常通知 org.springframework.aop.ThrowsAdvice
在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。
引介通知 org.springframework.aop.IntroductionInterceptor
在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。

8.4.2 声明式 Spring AOP

属性名称 描述
target 代理的目标对象
proxyInterfaces 代理要实现的接口,如果有多个接口,则可以使用以下格式赋值:
<list>
<value></value>
...
</list>
proxyTargetClass 是否对类代理而不是接口,设置为 true 时,使用 CGLIB 代理
interceptorNames 需要植入目标的 Advice
singleton 返回的代理是否为单例,默认为 true(返回单实例)
optimize 当设置为 true 时,强制使用 CGLIB

1) 创建切面类 MyAspect

package com.spring.factorybean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
//需要实现接口,确定哪个通知,及告诉Spring应该执行哪个方法
public class MyAspect implements MethodInterceptor {
    public Object invoke(MethodInvocation mi) throws Throwable {
        System.out.println("方法执行之前");
        // 执行目标方法
        Object obj = mi.proceed();
        System.out.println("方法执行之后");
        return obj;
    }
}

2) 创建 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--目标类 -->
    <bean id="customerDao" class="com.spring.dao.CustomerDaoImpl" />
    <!-- 通知 advice -->
    <bean id="myAspect" class="com.spring.factorybean.MyAspect" />
    <!--生成代理对象 -->
    <bean id="customerDaoProxy"
          class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--代理实现的接口 -->
        <property name="proxyInterfaces" value="com.spring.dao.CustomerDao" />
        <!--代理的目标对象 -->
        <property name="target" ref="customerDao" />
        <!--用通知增强目标 -->
        <property name="interceptorNames" value="myAspect" />
        <!-- 如何生成代理,true:使用cglib; false :使用jdk动态代理 -->
        <property name="proxyTargetClass" value="true" />
    </bean>
</beans>

3) 创建测试类

package com.spring.factorybean;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.dao.CustomerDao;
public class FactoryBeanTest {
    @Test
    public void test() {
        String xmlPath = "com/spring/factorybean/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        CustomerDao customerDao = (CustomerDao) applicationContext
                .getBean("customerDaoProxy");
        customerDao.add();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

4) 运行项目并查看结果

方法执行之前
添加客户…
方法执行之后
方法执行之前
修改客户…
方法执行之后
方法执行之前
删除客户…
方法执行之后
方法执行之前
修改客户…
方法执行之后

8.5 使用AspectJ开发AOP

基于 XML 的声明式。
基于 Annotation 的声明式。

8.5.1 基于XML的声明式

1) 创建切面类 MyAspect

package com.spring.aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
//切面类
public class MyAspect {
    // 前置通知
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知,目标:");
        System.out.print(joinPoint.getTarget() + "方法名称:");
        System.out.println(joinPoint.getSignature().getName());
    }
    // 后置通知
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    // 环绕通知
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
            throws Throwable {
        System.out.println("环绕开始"); // 开始
        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
        System.out.println("环绕结束"); // 结束
        return obj;
    }
    // 异常通知
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知" + "出错了" + e.getMessage());
    }
    // 最终通知
    public void myAfter() {
        System.out.println("最终通知");
    }
}

2) 创建 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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
    <!--目标类 -->
    <bean id="customerDao" class="com.spring.dao.CustomerDaoImpl" />
    <!--切面类 -->
    <bean id="myAspect" class="com.spring.aspectj.xml.MyAspect"></bean>
    <!--AOP 编程 -->
    <aop:config>
        <aop:aspect ref="myAspect">
            <!-- 配置切入点,通知最后增强哪些方法 -->
            <aop:pointcut expression="execution ( * com.spring.dao.*.* (..))"
                          id="myPointCut" />
            <!--前置通知,关联通知 Advice和切入点PointCut -->
            <aop:before method="myBefore" pointcut-ref="myPointCut" />
            <!--后置通知,在方法返回之后执行,就可以获得返回值returning 属性 -->
            <aop:after-returning method="myAfterReturning"
                                 pointcut-ref="myPointCut" returning="returnVal" />
            <!--环绕通知 -->
            <aop:around method="myAround" pointcut-ref="myPointCut" />
            <!--抛出通知:用于处理程序发生异常,可以接收当前方法产生的异常 -->
            <!-- *注意:如果程序没有异常,则不会执行增强 -->
            <!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
            <aop:after-throwing method="myAfterThrowing"
                                pointcut-ref="myPointCut" throwing="e" />
            <!--最终通知:无论程序发生任何事情,都将执行 -->
            <aop:after method="myAfter" pointcut-ref="myPointCut" />
        </aop:aspect>
    </aop:config>
</beans>

3) 创建测试类

package com.spring.aspectj.xml;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.dao.CustomerDao;
public class XMLTest {
    @Test
    public void test() {
        String xmlPath = "com/spring/aspectj/xml/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 从spring容器获取实例
        CustomerDao customerDao = (CustomerDao) applicationContext
                .getBean("customerDao");
        // 执行方法
        customerDao.add();
    }
}

4) 运行项目并查看结果

前置通知,目标:com.spring.dao.CustomerDaoImpl@50029372方法名称:add
环绕开始
添加客户…
最终通知
环绕结束
后置通知,方法名称:add

前置通知,目标:com.spring.dao.CustomerDaoImpl@28194a50方法名称:add
环绕开始
最终通知
异常通知出错了/ by zero

8.5.2 基于 Annotation 的声明式

名称 说明
@Aspect 用于定义一个切面。
@Before 用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning 用于定义后置通知,相当于 AfterReturningAdvice。
@Around 用于定义环绕通知,相当于MethodInterceptor。
@AfterThrowing 用于定义抛出通知,相当于ThrowAdvice。
@After 用于定义最终final通知,不管是否异常,该通知都会执行。
@DeclareParents 用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。

1) 创建切面类 MyAspect

package com.spring.aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//切面类
@Aspect
@Component
public class MyAspect {
    // 用于取代:<aop:pointcut
    // expression="execution(*com.mengma.dao..*.*(..))" id="myPointCut"/>
    // 要求:方法必须是private,没有值,名称自定义,没有参数
    @Pointcut("execution(*com.mengma.dao..*.*(..))")
    private void myPointCut() {
    }
    // 前置通知
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知,目标:");
        System.out.print(joinPoint.getTarget() + "方法名称:");
        System.out.println(joinPoint.getSignature().getName());
    }
    // 后置通知
    @AfterReturning(value = "myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    // 环绕通知
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
            throws Throwable {
        System.out.println("环绕开始"); // 开始
        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
        System.out.println("环绕结束"); // 结束
        return obj;
    }
    // 异常通知
    @AfterThrowing(value = "myPointCut()", throwing = "e")
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知" + "出错了" + e.getMessage());
    }
    // 最终通知
    @After("myPointCut()")
    public void myAfter() {
        System.out.println("最终通知");
    }
}

2) 为目标类添加注解

3) 创建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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    <!--扫描含com.spring包下的所有注解-->
    <context:component-scan base-package="com.spring"/>
    <!-- 使切面开启自动代理 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

4) 创建测试类

package com.spring.aspectj.annotation;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.dao.CustomerDao;
public class AnnotationTest {
    @Test
    public void test() {
        String xmlPath = "com/spring/aspectj/xml/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 从spring容器获取实例
        CustomerDao customerDao = (CustomerDao) applicationContext
                .getBean("customerDao");
        // 执行方法
        customerDao.add();
    }
}

5) 运行项目并查看结果

前置通知,目标:com.spring.dao.CustomerDaoImpl@4b8729ff方法名称:add
环绕开始
添加客户…
最终通知
环绕结束
后置通知,方法名称:add


第9章 Spring JDBC 与事务管理

9.1 Spring JDBC

9.2 事务管理

基于 XML 方式的声明式事务管理。
通过 Annotation 注解方式的事务管理。

9.2.1 基于 XML 方式的事务管理

1) 创建项目

com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.apache.commons.logging-1.1.1.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.2.jar
mysql-connector-java-8.0.20.jar
spring-aop-4.3.6.RELEASE.jar
spring-aspects-4.3.6.RELEASE.jar
spring-beans-4.3.6.RELEASE.jar
spring-context-4.3.6.RELEASE.jar
spring-core-4.3.6.RELEASE.jar
spring-expression-4.3.6.RELEASE.jar
spring-jdbc-4.1.2.RELEASE.jar
spring-test-3.2.13.RELEASE.jar
spring-tx-4.3.6.RELEASE.jar

2) 创建数据库、表以及插入数据

CREATE DATABASE spring;
USE spring;
CREATE TABLE account (
    id INT (11) PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(20) NOT NULL,
    money INT DEFAULT NULL
);
INSERT INTO account VALUES (1,'zhangsan',1000);
INSERT INTO account VALUES (2,'lisi',1000);
id username money
1 zhangsan 1000
2 lisi 1000

3) 创建 c3p0-db.properties

jdbc.driverClass = com.mysql.cj.jdbc.Driver
jdbc.jdbcUrl = jdbc:mysql://localhost:3306/spring?serverTimezone=UTC
jdbc.user = root
jdbc.password = root

4) 实现 DAO

1) 创建 AccountDao 接口

package com.spring.dao;
public interface AccountDao {
    // 汇款
    public void out(String outUser, int money);
    // 收款
    public void in(String inUser, int money);
}

2) 创建 DAO 层接口实现类

package com.spring.dao.impl;
import org.springframework.jdbc.core.JdbcTemplate;
import com.spring.dao.AccountDao;
public class AccountDaoImpl implements AccountDao {
    private JdbcTemplate jdbcTemplate;
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    // 汇款的实现方法
    public void out(String outUser, int money) {
        this.jdbcTemplate.update("UPDATE account SET money = money-? WHERE username = ?", money, outUser);
    }
    // 收款的实现方法
    public void in(String inUser, int money) {
        this.jdbcTemplate.update("UPDATE account SET money = money+? WHERE username = ?", money, inUser);
    }
}

5) 实现 Service

1) 创建 Service 层接口

package com.spring.service;
public interface AccountService {
    // 转账
    public void transfer(String outUser, String inUser, int money);
}

2) 创建 Service 层接口实现类

package com.spring.service.impl;

import com.spring.dao.AccountDao;
import com.spring.service.AccountService;

public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    public void transfer(String outUser, String inUser, int money) {
        this.accountDao.out(outUser, money);
        this.accountDao.in(inUser, money);
    }
}

6) 创建 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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
    <!-- 加载properties文件 -->
    <context:property-placeholder location="classpath:c3p0-db.properties" />
    <!-- 配置数据源,读取properties文件信息 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}" />
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}" />
        <property name="user" value="${jdbc.user}" />
        <property name="password" value="${jdbc.password}" />
    </bean>
    <!-- 配置jdbc模板 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 配置dao -->
    <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate" />
    </bean>
    <!-- 配置service -->
    <bean id="accountService" class="com.spring.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao" />
    </bean>
    <!-- 事务管理器,依赖于数据源 -->
    <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 编写通知:对事务进行增强(通知),需要编写切入点和具体执行事务的细节 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <!-- 给切入点方法添加事务详情,name表示方法名称,*表示任意方法名称,propagation用于设置传播行为,read-only表示隔离级别,是否只读 -->
            <tx:method name="find*" propagation="SUPPORTS"
                       rollback-for="Exception" />
            <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"
                       read-only="false" />
        </tx:attributes>
    </tx:advice>
    <!-- aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 -->
    <aop:config>
        <!-- 切入点 -->
        <aop:pointcut expression="execution(* com.spring.service.*.*(..))"
                      id="txPointCut" />
        <!-- 切面:将切入点与通知整合 -->
        <aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" />
    </aop:config>
</beans>

7) 创建测试类

package com.spring.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.service.AccountService;
public class AccountTest {
    @Test
    public void test() {
        // 获得Spring容器,并操作
        String xmlPath = "applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        AccountService accountService = (AccountService) applicationContext
                .getBean("accountService");
        accountService.transfer("zhangsan", "lisi", 100);
    }
}

8) 运行程序并查看结果

id username money
1 zhangsan 900
2 lisi 1100
public void transfer(String outUser, String inUser, int money) {
    this.accountDao.out(outUser, money);
    //模拟断电
    int i = 1/0;
    this.accountDao.in(inUser, money);
}

9.2.2 基于 Annotation 注解的事务管理

1) 注册驱动

<?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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
    <!-- 加载properties文件 -->
    <context:property-placeholder location="classpath:c3p0-db.properties" />
    <!-- 配置数据源,读取properties文件信息 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}" />
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}" />
        <property name="user" value="${jdbc.user}" />
        <property name="password" value="${jdbc.password}" />
    </bean>
    <!-- 配置jdbc模板 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 配置dao -->
    <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate" />
    </bean>
    <!-- 配置service -->
    <bean id="accountService" class="com.spring.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao" />
    </bean>
    <!-- 事务管理器,依赖于数据源 -->
    <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 注册事务管理驱动 -->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>

2) 添加 @Transactional 注解

package com.spring.service.impl;

import com.spring.dao.AccountDao;
import com.spring.service.AccountService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(String outUser, String inUser, int money) {
        this.accountDao.out(outUser, money);
        int i = 1/0;
        this.accountDao.in(inUser, money);
    }
}

3) 运行程序并查看结果