Spring框架入门


Spring具有控制反转(IoC)和面向切面(AOP)两大核心。Java Spring 框架通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

Java Spring框架是什么?它有哪些好处?

			是一个轻量级的应用框架

1)方便解耦,简化开发
Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。
2)方便集成各种优秀框架
3)降低 Java EE API 的使用难度
4)方便程序的测试
5)AOP 编程的支持
Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。
6)声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无须手动编程。

Spring体系结构详解

Spring的体系结构

  1. Data Access/Integration(数据访问/集成)
    包括 JDBC、ORM、OXM、JMS 和 Transactions 模块
  2. Web 模块
    包括 Web、Servlet、Struts 和 Portlet 组件
  3. Core Container(核心容器)
    由 Beans 模块、Core 核心模块、Context 上下文模块和 Expression Language 表达式语言模块组成
Beans 模块:提供了 BeanFactory,是工厂模式的经典实现,Spring 将管理对象称为 Bean。

Core 核心模块:提供了 Spring 框架的基本组成部分,包括 IoC 和 DI 功能。

Context 上下文模块:建立在核心和 Beans 模块的基础之上,它是访问定义
和配置任何对象的媒介。ApplicationContext 接口是上下文模块的焦点。

Expression Language 模块:是运行时查询和操作对象图的强大的表达式语言。
  1. 其他模块
    Spring的其他模块还有 AOP、Aspects、Instrumentation 以及 Test 模块

Spring目录结构和基础JAR包介绍

spring jar包下载:
http://repo.spring.io/simple/libs-release-local/org/springframework/spring/
Spring 的目录介绍

在这里插入图片描述
在这里插入图片描述
使用 Spring 框架时,只需将 Spring 的四个基础包以及 commons-logging-1.2.jar 包复制到项目的 lib 目录,并发布到类路径中即可。
通过网址 http://commons.apache.org/proper/commons-logging/download_logging.cgi 下载。该 JAR 包现在最新版本为 commons-logging.1.2,下载完成后,解压即可找到。

Spring IoC容器:BeanFactory和ApplicationContext

IoC 是指在程序开发中,实例的创建不再由调用者管理,而是由 Spring 容器创建。因此,控制权由程序代码转移到了 Spring 容器中,控制权发生了反转,这就是 Spring 的 IoC 思想。
ApplicationContext
ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。该接口的全路径为 org.springframework.context.ApplicationContext,它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。

ApplicationContext 接口有两个常用的实现类,具体如下。

1)ClassPathXmlApplicationContext
该类从类路径 ClassPath 中寻找指定的 XML 配置文件,找到并装载
完成 ApplicationContext 的实例化工作,具体如下所示。
ApplicationContext applicationContext = new 
ClassPathXmlApplicationContext(String configLocation);

在上述代码中,configLocation 参数用于指定 Spring 配置文件的名称和位置,
如 applicationContext.xml。
2)FileSystemXmlApplicationContext
该类从指定的文件系统路径中寻找指定的 XML 配置文件,找到并装载完成 
ApplicationContext 的实例化工作,具体如下所示。
ApplicationContext applicationContext = new 
FileSystemXmlApplicationContext(String configLocation);

通常在 Java 项目中,会采用通过 ClassPathXmlApplicationContext 类实例化 ApplicationContext 容器的方式,而在 Web 项目中,ApplicationContext 容器的实例化工作会交由 Web 服务器完成。Web 服务器实例化 ApplicationContext 容器通常使用基于 ContextLoaderListener 实现的方式,
它只需要在 web.xml 中添加如下代码:

<!--指定Spring配置文件的位置,有多个配置文件时,以逗号分隔-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <!--spring将加载spring目录下的applicationContext.xml文件-->
    <param-value>
        classpath:spring/applicationContext.xml
    </param-value>
</context-param>
<!--指定以ContextLoaderListener方式启动Spring容器-->
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

第一个Spring程序

  1. 创建项目
    创建 Web 项目 springDemo01,将 Spring 的四个基础包以及 commons-logging-1.2.jar 包复制到项目的 lib 目录,并发布到类路径中
  2. 创建 PersonDao 接口
    在项目的 src 目录下创建一个名为 com.mengma.ioc 的包,然后在该包中创建一个名为 PersonDao 的接口,并在接口中添加一个 add() 方法,如下所示。
package com.mengma.ioc;
public interface PersonDao {
    public void add();
}
  1. 创建接口实现类 PersonDaoImpl
    在 com.mengma.ioc 包下创建 PersonDao 的实现类 PersonDaoImpl,编辑后如下所示。
package com.mengma.ioc;
public class PersonDaoImpl implements PersonDao {
    @Override
    public void add() {
        System.out.println("save()执行了...");
    }
}
  1. 创建 Spring 配置文件
    在 src 目录下创建 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: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.mengma.ioc.PersonDaoImpl" />
</beans>

在 Spring 容器中创建一个 id 为 personDao 的 bean 实例,其中 id 表示文件中的唯一标识符,class 属性表示指定需要实例化 Bean 的实全限定类名(包名+类名)。
5. 编写测试类
在 com.mengma.ioc 包下创建测试类 FirstTest,并在该类中添加一个名为 test1() 的方法,编辑后如下所示。

package com.mengma.ioc;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FirstTest {
    @Test
    public void testl() {
        // 定义Spring配置文件的路径
        String xmlPath = "applicationContext.xml";
        // 初始化Spring容器,加载配置文件
        ApplicationContext applicationContext = 
        new ClassPathXmlApplicationContext(
                xmlPath);
        // 通过容器获取personDao实例
        PersonDao personDao = (PersonDao) applicationContext
                .getBean("personDao");
        // 调用 personDao 的 add ()方法
        personDao.add();
    }
}
  1. 运行项目并查看结果
    使用 JUnit 测试运行 test1() 方法,运行成功后,控制台的输出结果如图 2 所示。

从图 2 的输出结果中可以看出,程序已经成功输出了“save()执行了…”语句。在程序执行时,对象的创建并不是通过 new 一个类完成的,而是由 Spring 容器管理实现的。这就是 Spring IoC 容器思想的工作机制。
在这里插入图片描述

Spring DI(依赖注入)的实现方式:属性注入和构造注入

依赖注入(Dependency Injection,DI)和控制反转含义相同,它们是从两个角度描述的同一个概念。
Spring 容器在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,这样,调用者通过 Spring 容器获得被调用者实例,这称为依赖注入。

依赖注入主要有两种实现方式,分别是属性 setter 注入和构造方法注入。具体介绍如下。

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

通过案例演示基于 XML 方式的 Bean 的装配。

  1. 创建 Person 类
    在项目 springDemo02 中的 src 目录下,创建一个名称为 com.mengma.assembly 的包,在该包下创建一个 Person 类,如下所示。
package com.mengma.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;
    }
}

上述代码中,定义了 name 和 age 两个属性,并为其提供了 getter 和 setter 方法,由于要使用构造注入,所以需要提供有参的构造方法。为了能更清楚地看到输出结果,这里还重写了 toString() 方法。
2. 创建 Spring 配置文件
在 com.mengma.assembly 包下创建一个名为 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-3.2.xsd">
    <!-- 使用设值注入方式装配Person实例 -->
    <bean id="person1" class="com.mengma.assembly.Person">
        <property name="name" value="zhangsan" />
        <property name="age" value="20" />
    </bean>
    <!-- 使用构造方法装配Person实例 -->
    <bean id="person2" class="com.mengma.assembly.Person">
        <constructor-arg index="0" value="lisi" />
        <constructor-arg index="1" value="21" />
    </bean>
</beans>

上述代码中,首先使用了设值注入方式装配 Person 类的实例,其中 子元素用于调用 Bean 实例中的 setXxx() 方法完成属性赋值。然后使用了构造方式装配了 Person 类的实例,其中 元素用于定义构造方法的参数,其属性 index 表示其索引(从 0 开始),value 属性用于设置注入的值。
3. 创建测试类
在 com.mengma.assembly 包下创建一个名称为 XmlBeanAssemblyTest 的测试类,编辑后如下所示。

package com.mengma.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/mengma/assembly/applicationContext.xml";
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 设值方式输出结果
        System.out.println(applicationContext.getBean("person1"));
        // 构造方式输出结果
        System.out.println(applicationContext.getBean("person2"));
    }
}

上述代码中,分别获取并输出了 id 为 person1 和 person2 的实例。
4. 运行项目并查看结果
使用 JUnit 测试运行 test() 方法,运行成功后,控制台的输出结果如图 1 所示。
在这里插入图片描述
Spring 容器属性 setter 注入的方式,是实际开发中较为常用的一种方式。

Spring Bean的配置及常用属性

通常情况下,Spring 会以 XML 文件格式作为 Spring 的配置文件,这种配置方式通过 XML 文件注册并管理 Bean 之间的依赖关系。

XML 格式配置文件的根元素是 ,该元素包含了多个 子元素,每一个 子元素定义了一个 Bean,并描述了该 Bean 如何被装配到 Spring 容器中。

定义 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 对应的实现类。
在这里插入图片描述

Spring实例化Bean的三种方法

在 Spring 中,实例化 Bean 有三种方式,分别是构造器实例化、静态工厂方式实例化和实例工厂方式实例化。
1.构造器实例化

public class Person1 {
}
 <bean id="person1" class="com.mengma.instance.constructor.Person1" />

Spring 容器会加载配置文件的同时,会通过实现类 Person1 中默认的无参构造函数对 Bean 进行实例化。

2.静态工厂方式实例化

public class Person2 {
}
public class MyBeanFactory {
    // 创建Bean实例的静态工厂方法
    public static Person2 createBean() {
        return new Person2();
    }
}
 <bean id="person2" class="com.mengma.instance.static_factory.MyBeanFactory"
        factory-method="createBean" />
        

上述代码中,定义了一个 id 为 person2 的 Bean,其中 class 属性指定了其对应的工厂实现类为 MyBeanFactory,而 factory-method 属性用于告诉 Spring 容器调用工厂类中的 createBean() 方法获取 Bean 的实例。

3.实例工厂方式实例化

public class Person3 {
}
public class MyBeanFactory {
    public MyBeanFactory() {
        System.out.println("person3工厂实例化中");
    }
    // 创建Bean的方法
    public Person3 createBean() {
        return new Person3();
    }
}
 <!-- 配置实例工厂 -->
    <bean id="myBeanFactory" class="com.mengma.instance.factory.MyBeanFactory" />
    <!-- factory-bean属性指定一个实例工厂,factory-method属
    性确定使用工厂中的哪个方法 -->
    <bean id="person3" factory-bean="myBeanFactory" factory-method="createBean" />

上述代码中,首先配置了一个实例工厂 Bean,然后配置了需要实例化的 Bean。在 id 为 person3 的 Bean 中,使用 factory-bean 属性指定一个实例工厂,该属性值就是实例工厂的 id 属性值。使用 factory-method 属性确定使用工厂中的 createBean() 方法。

Spring中Bean的作用域

Spring 容器在初始化一个 Bean 的实例时,同时会指定该实例的作用域。Spring3 为 Bean 定义了五种作用域,具体如下。
1)singleton
单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。
2)prototype
原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。
3)request
在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。
4)session
在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。
5)global Session
在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。

在上述五种作用域中,singleton 和 prototype 是最常用的两种,接下来将对这两种作用域进行详细讲解。
singleton 作用域
singleton 是 Spring 容器默认的作用域,当一个 Bean 的作用域为 singleton 时,Spring 容器中只会存在一个共享的 Bean 实例,并且所有对 Bean 的请求,只要 id 与该 Bean 定义相匹配,就只会返回 Bean 的同一个实例。

通常情况下,这种单例模式对于无会话状态的 Bean(如 DAO 层、Service 层)来说,是最理想的选择。

在 Spring 配置文件中,可以使用 元素的 scope 属性,将 Bean 的作用域定义成 singleton,其配置方式如下所示:

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

使用 prototype 作用域的 Bean 会在每次请求该 Bean 时都会创建一个新的 Bean 实例。因此
对需要保持会话状态的 Bean(如 Struts2 的 Action 类)应该使用 prototype 作用域。

在 Spring 配置文件中,要将 Bean 定义为 prototype 作用域,只需将 元素的 scope 属性值定义成 prototype,其示例代码如下所示:

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

Spring Bean的生命周期

了解 Spring 生命周期的意义就在于,可以利用 Bean 在其存活期间的指定时刻完成一些相关操作。这种时刻可能有很多,但一般情况下,会在 Bean 被初始化后和被销毁前执行一些相关操作。

Spring 容器可以管理 singleton 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。

而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。每次客户端请求 prototype 作用域的 Bean 时,Spring 容器都会创建一个新的实例,并且不会管那些被配置成 prototype 作用域的 Bean 的生命周期。
Spring 容器中 Bean 的生命周期流程如图 1 所示。
在这里插入图片描述
Bean 生命周期的整个执行过程描述如下。

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> 中指定了该 Bean 的作用范围为 scope="singleton",
则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;
如果在 <bean> 中指定了该 Bean 的作用范围为 scope="prototype",
则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。*

*11)如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 
Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,
则 Spring 将调用该方法对 Bean 进行销毁。*

Spring 为 Bean 提供了细致全面的生命周期过程,通过实现特定的接口或 的属性设置,都可以对 Bean 的生命周期过程产生影响。虽然可以随意配置 的属性,但是建议不要过多地使用 Bean 实现接口,因为这样会导致代码和 Spring 的聚合过于紧密。

*Spring基于Annotation装配Bean

Spring 容器支持多种形式的 Bean 的装配方式,如基于 XML 的 Bean 装配、基于 Annotation 的 Bean 装配和自动装配等。
Spring 基于 XML 的装配通常采用两种实现方式,即设值注入(Setter Injection)和构造注入(Constructor Injection)。如果应用中 Bean 的数量较多,会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。

Spring3 中定义了一系列的 Annotation(注解),常用的注解如下。

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

@Resource 中有两个重要属性:name 和 type。
Spring 将 name 属性解析为 Bean 实例名称,type 属性解析为 Bean 实例类型。
如果指定 name 属性,则按实例名称进行装配;
如果指定 type 属性,则按 Bean 类型进行装配。

如果都不指定,则先按 Bean 实例名称装配,
如果不能匹配,则再按照 Bean 类型进行装配;
如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。
7@Qualifier@Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,
Bean 的实例名称由 @Qualifier 注解的参数指定。

注解简单使用演示。

  1. 创建 DAO 层接口
    在 src 目录下创建一个名为 com.mengma.annotation 的包,在该包下创建一个名为 PersonDao 的接口,并添加一个 add() 方法,如下所示。
package com.mengma.annotation;
public interface PersonDao {
    public void add();
}
  1. 创建 DAO 层接口的实现类
    在 com.mengma.annotation 包下创建 PersonDao 接口的实现类 PersonDaoImpl,编辑后如下所示。
package com.mengma.annotation;
import org.springframework.stereotype.Repository;
@Repository("personDao")
public class PersonDaoImpl implements PersonDao {
    @Override
    public void add() {
        System.out.println("Dao层的add()方法执行了...");
    }
}

上述代码中,首先使用 @Repository 注解将 PersonDaoImpl 类标识为 Spring 中的 Bean,其写法相当于配置文件中 <bean id="personDao"class=“com.mengma.annotation.PersonDaoImpl”/> 的书写。然后在 add() 方法中输出一句话,用于验证是否成功调用了该方法。
3. 创建 Service 层接口
在 com.mengma.annotation 包下创建一个名为 PersonService 的接口,并添加一个 add() 方法,如下所示。

package com.mengma.annotation;
public interface PersonService {
    public void add();
}
  1. 创建 Service 层接口的实现类
    在 com.mengma.annotation 包下创建 PersonService 接口的实现类 PersonServiceImpl,编辑后如下所示。
package com.mengma.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()方法执行了...");
    }
}

上述代码中,首先使用 @Service 注解将 PersonServiceImpl 类标识为 Spring 中的 Bean,其写法相当于配置文件中 <bean id="personService"class=“com.mengma.annotation.PersonServiceImpl”/> 的书写。

然后使用 @Resource 注解标注在属性 personDao 上(也可标注在 personDao 的 setPersonDao() 方法上),这相当于配置文件中 <property name="personDao"ref=“personDao”/> 的写法。最后在该类的 add() 方法中调用 personDao 中的 add() 方法,并输出一句话。
5. 创建 Action
在 com.mengma.annotation 包下创建一个名为 PersonAction 的类,编辑后如下所示。

package com.mengma.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()方法执行了...");
    }
}

上述代码中,首先使用 @Controller 注解标注 PersonAction 类,其写法相当于在配置文件中编写 <bean id="personAction"class=“com.mengma.annotation.PersonAction”/>。

然后使用了 @Resource 注解标注在 personService 上,这相当于在配置文件内编写 <property name="personService"ref=“personService”/>。

最后在其 add() 方法中调用了 personService 中的 add() 方法,并输出一句话。
6. 创建 Spring 配置文件
在 com.mengma.annotation 包下创建一个名为 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: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.mengma.annotation"/>
</beans>

与之前的配置文件相比,上述代码的元素中增加了第 7 行、第 15 行和第 16 行中包含有 context 的代码,然后在第 18 行代码中,使用 context 命名空间的 component-scan 元素进行注解的扫描,其 base-package 属性用于通知 spring 所需要扫描的目录。
7. 创建测试类
在 com.mengma.annotation 包下创建一个名为 AnnotationTest 的测试类,编辑后如下所示。

package com.mengma.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/mengma/annotation/applicationContext.xml";
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 获得personAction实例
        PersonAction personAction = (PersonAction) applicationContext
                .getBean("personAction");
        // 调用personAction中的add()方法
        personAction.add();
    }
}

上述代码中,首先通过加载配置文件并获取 personAction 的实例,然后调用该实例的 add() 方法。
8. 运行程序并查看结果
使用 JUnit 测试运行 test() 方法,运行成功后,输出结果如图 1 所示。
在这里插入图片描述
从图 1 的输出结果中可以看出,DAO 层、Service 层和 Action 层的 add() 方法都成功输出了结果。由此可知,使用 Annotation 装配 Bean 的方式已经成功实现了。

Spring自动装配Bean

除了使用 XML 和 Annotation 的方式装配 Bean 以外,还有一种常用的装配方式——自动装配。自动装配就是指 Spring 容器可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。

要使用自动装配,就需要配置 元素的 autowire 属性。autowire 属性有五个值,具体说明如表 1 所示。
在这里插入图片描述
下面通过修改《Spring基于Annotation装配Bean》中的案例演示如何实现自动装配。首先去掉所有注解,在将 applicationContext.xml 配置文件修改成自动装配形式,如下所示。

<bean id="personDao" class="com.mengma.annotation.PersonDaoImpl" />
<bean id="personService" class="com.mengma.annotation.PersonServiceImpl"
    autowire="byName" />
<bean id="personAction" class="com.mengma.annotation.PersonAction"
    autowire="byName" />

在上述配置文件中,用于配置 personService 和 personAction 的 元素中除了 id 和 class 属性以外,还增加了 autowire 属性,并将其属性值设置为 byName(按属性名称自动装配)。

默认情况下,配置文件中需要通过 ref 装配 Bean,但设置了 autowire=“byName”,Spring 会在applicationContext.xml 配置文件中自动寻找与PersonServiceImpl类里属性名字 personDao 相同的 ,找到后,通过调用 setPersonDao(PersonDao personDao)方法将 id 为 personDao 的 Bean 注入 id 为 personService 的 Bean 中,这时就不需要通过 ref 装配了。

使用 JUnit 再次运行测试类中的 test() 方法,控制台的显示结果如图 1 所示。
在这里插入图片描述

Spring AOP(面向切面编程)是什么?

AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。

AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。

目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。

Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。

AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
AOP 的相关术语含义如下:
在这里插入图片描述

Spring JDK动态代理(附带实例)

动态代理模式介绍:
https://blog.csdn.net/Bonport/article/details/104906078
JDK 动态代理是通过 JDK 中的 java.lang.reflect.Proxy 类实现的。下面通过具体的案例演示 JDK 动态代理的使用。

  1. 创建项目
    在 MyEclipse 中创建一个名称为 springDemo03 的 Web 项目,将 Spring 支持和依赖的 JAR 包复制到 Web 项目的 WEB-INF/lib 目录中,并发布到类路径下。
  2. 创建接口 CustomerDao
    在项目的 src 目录下创建一个名为 com.mengma.dao 的包,在该包下创建一个 CustomerDao 接口,编辑后如下所示。
package com.mengma.dao;
public interface CustomerDao {
    public void add(); // 添加
    public void update(); // 修改
    public void delete(); // 删除
    public void find(); // 查询
}
  1. 创建实现类 CustomerDaoImpl
    在 com.mengma.dao 包下创建 CustomerDao 接口的实现类 CustomerDaoImpl,并实现该接口中的所有方法,如下所示。
package com.mengma.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("修改客户...");
    }
}
  1. 创建切面类 MyAspect
    在 src 目录下,创建一个名为 com.mengma.jdk 的包,在该包下创建一个切面类 MyAspect,编辑后如下所示。
package com.mengma.jdk;
public class MyAspect {
    public void myBefore() {
        System.out.println("方法执行之前");
    }
    public void myAfter() {
        System.out.println("方法执行之后");
    }
}

上述代码中,在切面中定义了两个增强的方法,分别为 myBefore() 方法和 myAfter() 方法,用于对目标类(CustomerDaoImpl)进行增强。
5. 创建代理类 MyBeanFactory
在 com.mengma.jdk 包下创建一个名为 MyBeanFactory 的类,在该类中使用 java.lang.reflect.Proxy 实现 JDK 动态代理,如下所示。

package com.mengma.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.mengma.dao.CustomerDao;
import com.mengma.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;
                    }
                });
    }
}

上述代码中,定义了一个静态的 getBean() 方法,这里模拟 Spring 框架的 IoC 思想,通过调用 getBean() 方法创建实例,第 14 行代码创建了 customerDao 实例。

第 16 行代码创建的切面类实例用于调用切面类中相应的方法;第 18~26 行就是使用代理类对创建的实例 customerDao 中的方法进行增强的代码,其中 Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法。

在目标类方法执行的前后,分别执行切面类中的 myBefore() 方法和 myAfter() 方法。
6. 创建测试类 JDKProxyTest
在 com.mengma.jdk 包下创建一个名为 JDKProxyTest 的测试类,如下所示。

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

上述代码中,在调用 getBean() 方法时,获取的是 CustomerDao 类的代理对象,然后调用了该对象中的方法。
7. 运行项目并查看结果
使用 JUnit 测试运行 test() 方法,运行成功后,控制台的输出结果如图 1 所示。

从图 1 的输出结果中可以看出,在调用目标类的方法前后,成功调用了增强的代码,由此说明,JDK 动态代理已经实现。
在这里插入图片描述

Spring CGLlB动态代理(附带实例)

通过《Spring JDK动态代理》教程的学习可以知道,JDK 动态代理使用起来非常简单,但是它也有一定的局限性,
局限是只能代理实现了接口的类,因为在Proxy.newProxyInstance()方法中,使用类加载器和目标类的接口在运行时生成了代理类。在编译后实际上可以发现一个包含$的class文件。
如果不希望实现接口,则可以使用 CGLIB 代理。
CGLIB原理是动态生成一个目标类的子类,子类重写目标类的所有不是final的方法(因为final方法无法重写),在方法体中织入了回调函数的调用。

CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。因此 CGLIB 要依赖于 ASM 的包,解压 Spring 的核心包 spring-core-3.2.2.RELEASE.jar,文件目录如图 1 所示。
在这里插入图片描述
在图 1 中可以看出,解压的核心包中包含 cglib 和 asm,也就是说 Spring3.2.13 版本的核心包已经集成了 CGLIB 所需要的包,所以在开发中不需要另外导入 ASM 的 JAR 包了。下面通过案例演示实现 CGLIB 的代理过程。

其使用方法是基于Enhancer类,Enhancer设置继承的父类(即为要代理的类),设置回调函数。

Spring CGLlB动态代理实例

  1. 创建目标类 GoodsDao
    在 com.mengma.dao 包下创建目标类 GoodsDao,在类中定义增、删、改、查方法,并在每个方法编写输出语句,如下所示。
    纯文本复制
package com.mengma.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("修改商品...");
    }
}
  1. 创建切面类 MyAspect
    在 src 目录下,创建一个名为 com.mengma.aspect 的包,在该包下创建一个切面类 MyAspect,编辑后如下所示。
package com.mengma.aspect;
public class MyAspect {
    public void myBefore() {
        System.out.println("方法执行之前");
    }
    public void myAfter() {
        System.out.println("方法执行之后");
    }
}
  1. 创建代理类 MyBeanFactory
    在 src 目录下创建一个名为 com.mengma.cglib 的包,该包下创建类 MyBeanFactory,如下所示。
package com.mengma.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.mengma.dao.GoodsDao;
import com.mengma.aspect.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 = methodProxy.invokeSuper(proxy, args);; // 目标方法执行
                myAspect.myAfter(); // 后增强
                return obj;
            }
        });
        // 创建代理类
        GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
        return goodsDaoProxy;
    }
}

上述代码中,应用了 CGLIB 的核心类 Enhancer。在第 19 行代码调用了 Enhancer 类的 setSuperclass() 方法,确定目标对象。

第 21 行代码调用 setCallback() 方法添加回调函数;第 24 行代码的 intercept() 方法相当于 JDK 动态代理方式中的 invoke() 方法,该方法会在目标方法执行的前后,对切面类中的方法进行增强;第 33~34 行代码调用 Enhancer 类的 create() 方法创建代理类,最后将代理类返回。

Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable

其中MethodProxy proxy参数一般是用来调用原来的对应方法的。用method调用会再次进入拦截器。为了避免这种情况,应该使用接口方法中第四个参数methodProxy调用invokeSuper方法。

  1. 创建测试类
    在 com.mengma.cglib 包下创建测试类 CGLIBProxyTest,编辑后如下所示。
package com.mengma.cglib;
import org.junit.Test;
import com.mengma.dao.GoodsDao;
public class CGLIBProxyTest {
    @Test
    public void test() {
        // 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
        GoodsDao goodsDao = MyBeanFactory.getBean();
        // 执行方法
        goodsDao.add();
        goodsDao.update();
        goodsDao.delete();
        goodsDao.find();
    }
}

上述代码中,调用 getBean() 方法时,依然获取的是 goodsDao 的代理对象,然后调用该对象的方法。使用 JUnit 测试运行 test() 方法,运行成功后,控制台的输出结果如图 2 所示。
在这里插入图片描述
从图 2 的输出结果中可以看出,在调用目标类的方法前后,也成功调用了增强的代码,由此说明,使用 CGLIB 代理的方式同样实现了手动代理。

Spring通知类型及 使用ProxyFactoryBean创建AOP代理

Spring 通知类型
通知(Advice)其实就是对目标切入点进行增强的内容,Spring AOP 为通知(Advice)提供了 org.aopalliance.aop.Advice 接口。

Spring 通知按照在目标类方法的连接点位置,可以分为以下五种类型,如表 1 所示。
在这里插入图片描述
声明式 Spring AOP

Spring 创建一个 AOP 代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean,这个类对应的切入点和通知提供了完整的控制能力,并可以生成指定的内容。

ProxyFactoryBean 类中的常用可配置属性如表 2 所示。
在这里插入图片描述
在 Spring 通知中,环绕通知是一个非常典型的应用。下面通过环绕通知的案例演示 Spring 创建 AOP 代理的过程。

  1. 导入 JAR 包
    在核心 JAR 包的基础上,再向 springDemo03 项目的 lib 目录中导入 AOP 的 JAR 包,具体如下。
    spring-aop-3.2.13.RELEASE.jar:是 Spring 为 AOP 提供的实现,在 Spring 的包中已经提供。
    com.springsource.org.aopalliance-1.0.0.jar:是 AOP 提供的规范,可以在 Spring 的官网网址 https://repo.spring.io/webapp/#/search/quick/ 中进行搜索并下载。
  2. 创建切面类 MyAspect
    在 src 目录下创建一个名为 com.mengma.factorybean 的包,在该包下创建切面类 MyAspect,如下所示。
package com.mengma.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;
    }
}

上述代码中,MyAspect 类实现了 MethodInterceptor 接口,并实现了接口的 invoke() 方法。MethodInterceptor 接口是 Spring AOP 的 JAR 包提供的,而 invoke() 方法用于确定目标方法 mi,并告诉 Spring 要在目标方法前后执行哪些方法,这里为了演示效果在目标方法前后分别向控制台输出了相应语句。

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

上述代码中,首先配置目标类和通知,然后使用 ProxyFactoryBean 类生成代理对象;第 14 行代码配置了代理实现的接口;第 16 行代码配置了代理的目标对象;第 18 行代码配置了需要植入目标的通知;当第 20 行代码中的 value 属性值为 true 时,表示使用 CGLIB 代理,属性值为 false 时,表示使用 JDK 动态代理。

  1. 创建测试类
    在 com.mengma.factorybean 包下创建一个名为 FactoryBeanTest 的测试类,编辑后如下所示。
package com.mengma.factorybean;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mengma.dao.CustomerDao;
public class FactoryBeanTest {
    @Test
    public void test() {
        String xmlPath = "com/mengma/factorybean/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        CustomerDao customerDao = (CustomerDao) applicationContext
                .getBean("customerDaoProxy");
        customerDao.add();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}
  1. 运行项目并查看结果
    使用 JUnit 测试运行 test() 方法,运行成功后,控制台的输出结果如图 1 所示。
    在这里插入图片描述

*Spring使用AspectJ开发AOP:基于XML和基于Annotation

建议使用 AspectJ 方式开发 AOP。

使用 AspectJ 开发 AOP 通常有两种方式:
一.基于 XML 的声明式。
二.基于 Annotation 的声明式。

接下来将对这两种 AOP 的开发方式进行讲解。

一.基于XML的声明式
基于 XML 的声明式是指通过 Spring 配置文件的方式定义切面、切入点及声明通知,而所有的切面和通知都必须定义在 aop:config 元素中。

下面通过案例演示 Spring 中如何使用基于 XML 的声明式实现 AOP 的开发。

  1. 导入 JAR 包
    使用 AspectJ 除了需要导入 Spring AOP 的 JAR 包以外,还需要导入与 AspectJ 相关的 JAR 包,具体如下。
    spring-aspects-3.2.13.RELEASE.jar:Spring 为 AspectJ 提供的实现,在 Spring 的包中已经提供。
    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar:是 AspectJ 提供的规范,可以在官方网址 https://repo.spring.io/webapp/#/search/quick/ 中搜索并下载。
  2. 创建切面类 MyAspect
    在 src 目录下创建一个名为 com.mengma.aspectj.xml 的包,在该包下创建切面类 MyAspect,编辑后如下所示。
package com.mengma.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("最终通知");
    }
}

上述代码中,分别定义了几种不同的通知类型方法,在这些方法中,通过 JoinPoint 参数可以获得目标对象的类名、目标方法名和目标方法参数等。需要注意的是,环绕通知必须接收一个类型为 ProceedingJoinPoint 的参数,返回值必须是 Object 类型,且必须抛出异常。异常通知中可以传入 Throwable 类型的参数,用于输出异常信息。
3. 创建 Spring 配置文件
在 com.mengma.aspectj.xml 包下创建 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: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.mengma.dao.CustomerDaoImpl" />
    <!--切面类 -->
    <bean id="myAspect" class="com.mengma.aspectj.xml.MyAspect"></bean>
    <!--AOP 编程 -->
    <aop:config>
        <aop:aspect ref="myAspect">
            <!-- 配置切入点,通知最后增强哪些方法 -->
            <aop:pointcut expression="execution ( * com.mengma.dao.*.* (..))"
                id="myPointCut" />
            <!--前置通知,关联通知 Advice和切入点PointCut -->
            <aop:before method="myBefore" pointeut-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>

上述代码中,首先在第 4、7、8 行代码中分别导入了 AOP 的命名空间。第 12 行代码指定了切面类。

第 17、18 行代码配置了切入点,通知需要增强哪些方法,expression="execution(com.mengma.dao..*(…))的意思是增强 com.mengma.dao 包下所有的方法。

第 20~32 行代码用于关联通知(Advice)和切入点(PointCut)。以第 20 行代码前置通知为例,aop:before 标签的 method 属性用于指定通知,pointcut-ref 属性用于指定切入点,也就是要增强的方法,其他几种通知的配置可以参考代码注释。
4. 创建测试类
在 com.mengma.aspectj.xml 包下创建测试类 XMLTest,如下所示。

package com.mengma.aspectj.xml;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mengma.dao.CustomerDao;
public class XMLTest {
    @Test
    public void test() {
        String xmlPath = "com/mengma/aspectj/xml/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 从spring容器获取实例
        CustomerDao customerDao = (CustomerDao) applicationContext
                .getBean("customerDao");
        // 执行方法
        customerDao.add();
    }
}
  1. 运行项目并查看结果
    使用 JUnit 测试运行 test() 方法,运行成功后,控制台的输出结果如图 1 所示。
    在这里插入图片描述

为了更好地演示异常通知,接下来在 CustomerDaoImpl 类的 add() 方法中添加一行会抛出异常的代码,如“int i=1/0;”,重新运行 XMLTest 测试类,可以看到异常通知执行了,此时控制台的输出结果如图 2 所示。
运行结果图 2  运行结果

从图 1 和图 2 的输出结果中可以看出,基于 XML 声明式的 AOP 开发已经成功实现。

二.基于 Annotation 的声明式
在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关的配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。

为此,AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理。

关于 Annotation 注解的介绍如表 1 所示。
在这里插入图片描述
下面使用注解的方式重新实现《基于XML的声明式》部分的功能。

  1. 创建切面类 MyAspect
    在 src 目录下创建一个名为 com.mengma.aspectj.annotation 的包,在该包下创建一个切面类 MyAspect,如下所示。
package com.mengma.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("最终通知");
    }
}

上述代码中,第 13 行 @Aspect 注解用于声明这是一个切面类,该类作为组件使用,所以要添加 @Component 注解才能生效。第 19 行中 @Poincut 注解用于配置切入点,取代 XML 文件中配置切入点的代码。

在每个通知相应的方法上都添加了注解声明,并且将切入点方法名“myPointCut”作为参数传递给要执行的方法,如需其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。

  1. 为目标类添加注解
    在 com.mengma.dao.CustomerDaoImpl 目标类中添加注解 @Repository(“customerDao”)。
  2. 创建Spring配置文件
    在 com.mengma.aspectj.annotation 包下创建 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: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.mengma包下的所有注解-->
    <context:component-scan base-package="com.mengma"/>
    <!-- 使切面开启自动代理 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

上述代码中,首先导入了 AOP 命名空间及其配套的约束,使切面类中的 @AspectJ 注解能够正常工作;第 13 行代码添加了扫描包,使注解生效。需要注意的是,这里还包括目标类 com.mengma.dao.CustomerDaoImpl 的注解,所以 base-package 的值为 com.mengma;第 15 行代码的作用是切面开启自动代理。
4. 创建测试类
在 com.mengma.aspectj.annotation 包下创建一个名为 AnnotationTest 的测试类,如下所示。

package com.mengma.aspectj.annotation;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mengma.dao.CustomerDao;
public class AnnotationTest {
    @Test
    public void test() {
        String xmlPath = "com/mengma/aspectj/xml/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 从spring容器获取实例
        CustomerDao customerDao = (CustomerDao) applicationContext
                .getBean("customerDao");
        // 执行方法
        customerDao.add();
    }
}
  1. 运行项目并查看结果
    使用 JUnit 测试运行 test() 方法,运行成功后,控制台的输出结果如图 3 所示。

运行结果图 3  运行结果

删除 add() 方法中的“int i=1/0;”,重新运行 test() 方法,此时控制台的输出结果如图 4 所示。

运行结果图 4  运行结果

从图 3 和图 4 的输出结果中可以看出,已成功使用 Annotation 的方式实现了 AOP 开发。与其他方式相比,基于 Annotation 方式实现 AOP 的效果是最方便的方式,所以实际开发中推荐使用注解的方式。

Spring JDBCTemplate简介

Spring 框架针对数据库开发中的应用提供了 JDBCTemplate 类,该类是 Spring 对 JDBC 支持的核心,它提供了所有对数据库操作功能的支持。

Spring 框架提供的JDBC支持主要由四个包组成,分别是 core(核心包)、object(对象包)、dataSource(数据源包)和 support(支持包),org.springframework.jdbc.core.JdbcTemplate 类就包含在核心包中。作为 Spring JDBC 的核心,JdbcTemplate 类中包含了所有数据库操作的基本方法。

JdbcTemplate 类继承自抽象类 JdbcAccessor,同时实现了 JdbcOperations 接口。其直接父类 JdbcAccessor 为子类提供了一些访问数据库时使用的公共属性,具体介绍如下。
1)DataSource
其主要功能是获取数据库连接,具体实现时还可以引入对数据库连接的缓冲池和分布式事务的支持,它可以作为访问数据库资源的标准接口。
2)SQLExceptionTranslator
org.springframework.jdbc.support.SQLExceptionTranslator 接口负责对 SQLException 进行转译工作。通过必要的设置或者获取 SQLExceptionTranslator 中的方法,可以使 JdbcTemplate 在需要处理 SQLException 时,委托 SQLExceptionTranslator 的实现类完成相关的转译工作。

JdbcOperations 接口定义了在 JdbcTemplate 类中可以使用的操作集合,包括添加、修改、查询和删除等操作。

Spring 中 JDBC 的相关信息是在 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="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver" /> 
        <!--连接数据库的url-->
        <property name= "url" value="jdbc:mysql://localhost:3306/spring" />
        <!--连接数据库的用户名-->
        <property name="username" value="root" />
        <!--连接数据库的密码-->
        <property name="password" value="root" />
    </bean>
    <!--配置JDBC模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--默认必须使用数据源-->
        <property name="datasource" ref="dataSource"/>
    </bean>
    <!--配置注入类-->
    <bean id="xxx" class="xxx">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    ...
</beans>

在上述代码中,定义了三个 Bean,分别是 dataSource、jdbcTemplate 和需要注入类的 Bean。其中 dataSource 对应的是 DriverManagerDataSource 类,用于对数据源进行配置;jdbcTemplate 对应 JdbcTemplate 类,该类中定义了 JdbcTemplate 的相关配置。

在 dataSource 中,定义了四个连接数据库的属性,如表 1 所示。

表 1 dataSource的四个属性属性名	含义

表 1 中的属性值需要根据数据库类型或者机器配置的不同进行相应设置。如果数据库类型不同,则需要更改驱动名称。如果数据库不在本地,则需要将 localhost 替换成相应的主机 IP。

在定义 jdbcTemplate 时,需要将 dataSource 注入 jdbcTemplate 中。而在其他的类中要使用 jdbcTemplate,也需要将 jdbcTemplate 注入使用类中(通常注入 dao 类中)。

Spring事务管理接口:PlatformTransactionManager、TransactionDefinition和TransactionStatus

Spring 的事务管理是基于 AOP 实现的,而 AOP 是以方法为单位的。Spring 的事务属性分别为传播行为、隔离级别、只读和超时属性,这些属性提供了事务应用的方法和描述策略。

在 Java EE 开发经常采用的分层模式中,Spring 的事务处理位于业务逻辑层,它提供了针对事务的解决方案。

在 Spring 解压包的 libs 目录中,包含一个名称为 spring-tx-3.2.13.RELEASE.jar 的文件,该文件是 Spring 提供的用于事务管理的 JAR 包,其中包括事务管理的三个核心接口:PlatformTransactionManager、TransactionDefinition 和 TransactionStatus。

这三个核心接口的作用及其提供的方法如下。

  1. PlatformTransactionManager
    PlatformTransactionManager 接口是 Spring 提供的平台事务管理器,用于管理事务。该接口中提供了三个事务操作方法,具体如下。
    TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。
    void commit(TransactionStatus status):用于提交事务。
    void rollback(TransactionStatus status):用于回滚事务。

在项目中,Spring 将 xml 中配置的事务详细信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作。

  1. TransactionDefinition
    TransactionDefinition 接口是事务定义(描述)的对象,它提供了事务相关信息获取的方法,其中包括五个操作,具体如下。
    String getName():获取事务对象名称。
    int getIsolationLevel():获取事务的隔离级别。
    int getPropagationBehavior():获取事务的传播行为。
    int getTimeout():获取事务的超时时间。
    boolean isReadOnly():获取事务是否只读。

在上述五个方法的描述中,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。传播行为的种类如表 1 所示。

在这里插入图片描述
在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务。

通常情况下,数据的查询不会改变原数据,所以不需要进行事务管理,而对于数据的增加、修改和删除等操作,必须进行事务管理。如果没有指定事务的传播行为,则 Spring3 默认的传播行为是 required。

  1. TransactionStatus
    TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作,具体如表 2 所示。
    在这里插入图片描述

Spring声明式事务管理(基于XML方式实现)

Spring 的事务管理有两种方式:一种是传统的编程式事务管理,即通过编写代码实现的事务管理;另一种是基于 AOP 技术实现的声明式事务管理。由于在实际开发中,编程式事务管理很少使用,所以我们只对 Spring 的声明式事务管理进行详细讲解。

Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

Spring 实现声明式事务管理主要有两种方式:
一,基于 XML 方式的声明式事务管理。
二,通过 Annotation 注解方式的事务管理。

本节通过银行转账的案例讲解如何使用 XML 的方式实现 Spring 的声明式事务处理。

  1. 创建项目
    在 MyEclipse 中创建一个名为 springDemo03 的 Web 项目,将 Spring 支持和依赖的 JAR 包复制到 Web 项目的 lib 目录中,并添加到类路径下。所添加的 JAR 包如图 1 所示。
    在这里插入图片描述
    从图 1 中可以看出,这里增加导入了 spring-tx-3.2.13.RELEASE.jar(事务管理),以及 MySQL 驱动、JDBC 和 C3P0 的 JAR 包。
  2. 创建数据库、表以及插入数据
    在 MySQL 中创建一个名为 spring 的数据库,然后在该数据库中创建一个 account 表,并向表中插入两条数据,其 SQL 执行语句如下所示:
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);
  1. 创建 c3p0-db.properties
    在项目的 src 下创建一个名为 c3p0-db.properties 的配置文件,这里使用 C3P0 数据源,需要在该文件中添加如下配置:
jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.jdbcUrl = jdbc:mysql://localhost:3306/spring
jdbc.user = root
jdbc.password = root
  1. 实现 DAO
    1)创建 AccountDao 接口
    在项目的 src 目录下创建一个名为 com.mengma.dao 的包,在该包下创建一个接口 AccountDao,并在接口中创建汇款和收款的方法,如下所示。
package com.mengma.dao;
public interface AccountDao {
    // 汇款
    public void out(String outUser, int money);
    // 收款
    public void in(String inUser, int money);
}

上述代码中,定义了 out() 和 in() 两个方法,分别用于表示汇款和收款。
2)创建DAO层接口实现类
在项目的 src 目录下创建一个名为 com.mengma.dao.impl 的包,在该包下创建实现类 AccountDaoImpl,如下所示。

package com.mengma.dao.impl;
import org.springframework.jdbc.core.JdbcTemplate;
import com.mengma.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);
    }
}

上述代码中,使用 JdbcTemplate 类的 update() 方法实现了更新操作。

  1. 实现 Service
    1)创建 Service 层接口
    在项目的 src 目录下创建一个名为 com.mengma.service 的包,在该包下创建接口 AccountService,如下所示。
package com.mengma.service;
public interface AccountService {
    // 转账
    public void transfer(String outUser, String inUser, int money);
}
2)创建 Service 层接口实现类
在项目的 src 目录下创建一个名为 com.mengma.service.impl 的包,在该包下创建实现类 AccountServiceImpl,如下所示。
package com.mengma.service.impl;
import com.mengma.dao.AccountDao;
public class AccountServiceImpl {
    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);
    }
}

上述代码中可以看出,该类实现了 AccountService 接口,并对转账的方法进行了实现,根据参数的不同调用 DAO 层相应的方法。

  1. 创建 Spring 配置文件
    在项目的 src 目录下创建 Spirng 配置文件 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"
    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.mengma.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate" />
    </bean>
    <!-- 配置service -->
    <bean id="accountService" class="com.mengma.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.mengma.service.*.*(..))"
            id="txPointCut" />
        <!-- 切面:将切入点与通知整合 -->
        <aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" />
    </aop:config>
    
</beans>

上述代码中,首先在 标记的第 6、13 和 14 行代码分别添加了 AOP 所需的命名空间声明。第 42~50 行代码使用 tx:advice 标记配置事务通知内容。

第 52~58 行代码使用 aop:config 标记定义切面,其中第 54 行代码应用了 AspectJ 表达式,代表 com.mengma.service 包下所有类的所有方法都应用事务规则,第 57 行代码使用 aop:advistor 标记将切入点与事务通知整合,基于 AOP 的声明式事务配置完成。
7. 创建测试类
在项目的 src 目录下创建 com.mengma.test 的包,在该包下创建测试类 AccountTest,如下所示。

package com.mengma.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mengma.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);
    }
}

上述代码中模拟了银行转账业务,从 zhangsan 的账户向 lisi 的账户中转入 100 元。使用 JUnit 测试运行 test() 方法,运行成功后,查询 account 表,可以看出,zhangsan 成功向 lisi 转账 100 元。

*Spring声明式事务管理(基于Annotation注解方式实现)

在 Spring 中,除了使用基于 XML 的方式可以实现声明式事务管理以外,还可以通过 Annotation 注解的方式实现声明式事务管理。

使用 Annotation 的方式非常简单,只需要在项目中做两件事,具体如下。
1)在 Spring 容器中注册驱动,代码如下所示:

<tx:annotation-driven transaction-manager="txManager"/>

2)在需要使用事务的业务类或者方法中添加注解 @Transactional,并配置 @Transactional 的参数。关于 @Transactional 的参数如图 1 所示。
在这里插入图片描述
下面通过修改《Spring基于XML实现事务管理》教程中银行转账的案例讲解如何使用 Annotation 注解的方式实现 Spring 声明式事务管理。

  1. 注册驱动
    修改 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"
    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.mengma.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate" />
    </bean>
    <!-- 配置service -->
    <bean id="accountService" class="com.mengma.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>

上述代码中可以看出,与原来的配置文件相比,这里只修改了事务管理器部分,新添加并注册了事务管理器的驱动。

需要注意的是,在学习 AOP 注解方式开发时,需要在配置文件中开启注解处理器,指定扫描哪些包下的注解,这里没有开启注解处理器是因为在第 33~35 行手动配置了 AccountServiceImpl,而 @Transactional 注解就配置在该类中,所以会直接生效。

  1. 添加 @Transactional 注解
    修改 AccountServiceImpl,在文件中添加 @Transactional 注解及参数,添加后如下所示。
package com.mengma.service.impl;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.mengma.dao.AccountDao;
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
public class AccountServiceImpl {
    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);
    }
}

需要注意的是,在使用 @Transactional 注解时,参数之间用“,”进行分隔。

使用 JUnit 测试再次运行 test() 方法,说明使用基于 Annotation 注解的方式同样实现了 Spring 的声明式事务管理。

SSH框架(Struts2+Spring+Hibernate)搭建整合详细步骤

(Struts2+Spring+Hibernate)搭建整合详细步骤:
https://blog.csdn.net/Bonport/article/details/104843085

SSM(Spring+Spring MVC+MyBatis)框架整合搭建详细步骤

spring+springmvc+mybatis简单整合详细步骤:
https://blog.csdn.net/Bonport/article/details/106057000

参考内容

参考内容:
http://c.biancheng.net/spring/

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值