Spring03

Spring03

1. SpringAOP中相关术语的介绍



2. SpringAOP编程(xml方式)

2.1. 基于xmlSpring AOP开发入门

2.1.1. 创建工程并引入依赖

创建spring4_day03工程:


AOP开发,IOC的依赖不能少;同时引入AOP的依赖,坐标如下:

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-aspects</artifactId>

<version>4.2.4.RELEASE</version>

</dependency>

完整的pom.xml如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>cn.itcast</groupId>

  <artifactId>spring4_day03</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <dependencies>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>4.2.4.RELEASE</version>

</dependency>

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>1.2.12</version>

</dependency>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.12</version>

<scope>test</scope>

</dependency>

<!-- spring整合junit的包 -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-test</artifactId>

<version>4.2.4.RELEASE</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-aspects</artifactId>

<version>4.2.4.RELEASE</version>

</dependency>

  </dependencies>

<build>

<plugins>

<!-- maven编译插件 -->

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<version>3.5.1</version>

<configuration>

<source>1.7</source>

<target>1.7</target>

</configuration>

</plugin>

</plugins>

</build>

</project>

引入applicationContext.xmllog4j.properties


2.1.2. 编写接口和实现类


创建CustomerDao接口:

public interface CustomerDao {

 

public void save();

}

创建CustomerDao接口的实现类CustomerDaoImpl

public class CustomerDaoImpl implements CustomerDao {

 

@Override

public void save() {

System.out.println("持久层:客户保存...");

}

}

2.1.3. 配置相关类到Spring

CustomerDaoImpl配置在Spring

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

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

    xsi:schemaLocation="http://www.springframework.org/schema/beans

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

        http://www.springframework.org/schema/context

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

  

   <bean id="customerDao" class="cn.itcast.dao.impl.CustomerDaoImpl"></bean>   

</beans>

2.1.4. 编写切面类

创建切面类MyAspectXml


/**

 * 自定义切面类

 * @author kevin

 */

public class MyAspectXml {

 

public void writeLog(){

System.out.println("记录日志啦.....");

}

}

2.1.5. 配置切面类

把MyAspectXml切面类在applicationContext.xml中配置:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

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

    xsi:schemaLocation="http://www.springframework.org/schema/beans

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

        http://www.springframework.org/schema/context

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

  

    <bean id="customerDao" class="cn.itcast.dao.impl.CustomerDaoImpl"></bean>   

   <bean id="myAspectXml" class="cn.itcast.aspect.MyAspectXml"></bean>

</beans>

2.1.6. 进行AOP配置

applicationContext.xml配置AOP相关的信息:

<?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:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

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

        http://www.springframework.org/schema/context

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

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop.xsd">

  

   <bean id="customerDao" class="cn.itcast.dao.impl.CustomerDaoImpl"></bean>  

<bean id="myAspectXml" class="cn.itcast.aspect.MyAspectXml"></bean>

  <!-- AOP配置 -->

  <aop:config>

  <!-- 配置切入点:告诉spring框架哪些方法需要被增强 -->

  <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))" id="pointcut1"/>

  <!-- 配置切面:告诉spring框架调用切面类中的哪个方法来增强 -->

  <aop:aspect ref="myAspectXml">

  <aop:before method="writeLog" pointcut-ref="pointcut1"/>

  </aop:aspect>

  </aop:config>

</beans>

2.1.7. 编写测试类

创建单元测试类TestAOP,在其中创建单元测试方法test1:


@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestAOP {

 

@Autowired

private CustomerDao customerDao;

@Test

public void test1(){

customerDao.save();

}

}

运行test1方法,观察结果,发现save方法被增强了:


2.2. 切入点表达式的语法


切入点表达式语法:[修饰符] 返回类型 包名.类名.方法名(形式参数)

常见写法:

n execution(public * *(..)) 所有的public方法

n execution(* set*(..)) 所有set开头的方法

execution(* com.xyz.service.AccountService.*(..))    AccountService类中的所有方法

execution(* com.xyz.service.*.*(..)) com.xyz.service包下所有的方法

n execution(* com.xyz.service..*.*(..))   · com.xyz.service包及其子包下所有的方法

2.3. SpringAOP的通知类型

AOP开发入门中,我们写的是一个前置通知,表示在目标方法执行之前增强


那么,除了前置通知以外,Spring还提供了其它的通知类型:

1、前置通知

2、后置通知

3、环绕通知

4、异常通知

5、最终通知

2.3.1. 前置通知

应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志

CustomerDaoImpl接口:

public interface CustomerDao {

 

public void save();

}

CustomerDaoImpl实现类:

public class CustomerDaoImpl implements CustomerDao {

 

@Override

public void save() {

System.out.println("持久层:客户保存...");

}

 

}

现在需要对CustomerDaoImpl中的save方法前置增强,需要创建一个切面类MyAspectXml,在其中创建前置通知方法before:

/**

 * 切面类

 *

 * @author xiaokaibo

 *

 */

public class MyAspectXml {

 

/**

 * 前置通知方法 应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志

 * joinPoint:连接点,指的是被增强的那个方法

 */

public void before(JoinPoint joinPoint) {

String username = "rose";

if (!"admin".equals(username)) {

// 非admin用户,不具备权限,抛出异常

//joinPoint.getTarget().getClass().getName()获取目标类的名字

//joinPoint.getSignature().getName()获取被增强方法的名字

throw new RuntimeException("对不起!您没有对" + joinPoint.getTarget().getClass().getName() + "类中"

+ joinPoint.getSignature().getName() + "方法的访问权限");

}

}

}

applicationContext.xml中配置前置通知,在执行save方法之前,执行before方法

<?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.xsd

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop.xsd">

        <bean id="customerDao" class="cn.itcast.dao.impl.CustomerDaoImpl"></bean>

        <!-- 配置切面类 -->

        <bean id="myAspectXml" class="cn.itcast.aspect.MyAspectXml"></bean>

        

        <aop:config>

        <!-- 配置切入点表达式:告诉spring框架哪些方法需要被增强 -->

        <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))" id="pointcut1"/>

        <aop:aspect ref="myAspectXml">

        <!-- 配置前置通知:告诉spring在目标方法执行之前增强 -->

        <aop:before method="before" pointcut-ref="pointcut1"/>

        </aop:aspect>

        </aop:config>

</beans>

在TestAOP中创建单元测试方法test1

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestAOP {

 

@Autowired

private CustomerDao customerDao;

@Test

public void test1(){

customerDao.save();

}

}

2.3.2. 后置通知

特点:在目标方法运行后,返回值后执行增强代码逻辑。

应用场景:与业务相关的,如ATM取款机取款后,自动下发短信。

CustomerDao接口中增加delete方法,如下:

public interface CustomerDao {

 

public void save();

public Integer delete();

}

n 在目标类CustomerDaoImpl中编写目标方法delete方法,需要对该方法增强

public class CustomerDaoImpl implements CustomerDao {

 

@Override

public void save() {

System.out.println("持久层:客户保存...");

}

 

@Override

public Integer delete() {

System.out.println("持久层:客户删除...");

return 100;

}

}

在切面类MyAspectXml中定义一个后置通知方法afterReturning方法,并添加一个形参result,表示目标方法的返回值:

/**

 * 切面类

 *

 * @author xiaokaibo

 *

 */

public class MyAspectXml {

 

/**

 * 前置通知方法 应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志

 * joinPoint:连接点,指的是被增强的那个方法

 */

public void before(JoinPoint joinPoint) {

String username = "rose";

if (!"admin".equals(username)) {

// 非admin用户,不具备权限,抛出异常

//joinPoint.getTarget().getClass().getName()获取目标类的名字

//joinPoint.getSignature().getName()获取被增强方法的名字

throw new RuntimeException("对不起!您没有对" + joinPoint.getTarget().getClass().getName() + "类中"

+ joinPoint.getSignature().getName() + "方法的访问权限");

}

}

/**

 * 后置通知方法

 * 应用场景: ATM取款机取款后,自动下发短信

 * 参数result:被增强那个方法的返回值

 */

public void afterReturning(JoinPoint joinPoint,Object result){

Date date = new Date();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateStr = sdf.format(date);

System.out.println("尊敬的用户,您于" + dateStr + "取款" + result + "元");

}

}

applicationContext.xml中配置切入点及后后置通知,并指定returning属性的值为result,通知方法的形参的名字要与returning的值一致:

<?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.xsd

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop.xsd">

        <bean id="customerDao" class="cn.itcast.dao.impl.CustomerDaoImpl"></bean>

        <!-- 配置切面类 -->

        <bean id="myAspectXml" class="cn.itcast.aspect.MyAspectXml"></bean>

        

        <aop:config>

        <!-- 配置切入点表达式:告诉spring框架哪些方法需要被增强 -->

        <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))" id="pointcut1"/>

        <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.delete(..))" id="pointcut2"/>

        <aop:aspect ref="myAspectXml">

        <!-- 配置前置通知:告诉spring在目标方法执行之前增强 -->

        <aop:before method="before" pointcut-ref="pointcut1"/>

        <!-- 配置后置通知 -->

        <aop:after-returning method="afterReturning" pointcut-ref="pointcut2" returning="result"/>

        </aop:aspect>

        </aop:config>

</beans>

修改TestAOP中的test1方法,如下:

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestAOP {

 

@Autowired

private CustomerDao customerDao;

@Test

public void test1(){

//customerDao.save();

customerDao.delete();

}

}

n 测试结果如下:


2.3.2. 环绕通知

特点:目标执行前后,都进行增强(控制目标方法执行)

应用场景:日志、缓存、权限、性能监控、事务管理

增强代码的方法要求:

接受的参数:ProceedingJoinPoint(可执行的连接点)

返回值:Object返回值

抛出Throwable异常。

 

CustomerDao接口中增加update方法

public interface CustomerDao {

 

public void save();

public Integer delete();

public void update();

}

在目标类CustomerDaoImpl中编写目标方法update方法,需要对该方法增强

public class CustomerDaoImpl implements CustomerDao {

 

@Override

public void save() {

System.out.println("持久层:客户保存...");

}

 

@Override

public Integer delete() {

System.out.println("持久层:客户删除...");

return 100;

}

 

@Override

public void update() {

System.out.println("持久层:客户更新...");

}

 

}

在切面类MyAspectXml中定义一个环绕通知方法around方法,需要给该方法增加一个形参ProceedingJoinPoint ,表示正在执行的连接点(目标)。

/**

 * 切面类

 *

 * @author xiaokaibo

 *

 */

public class MyAspectXml {

 

/**

 * 前置通知方法 应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志

 * joinPoint:连接点,指的是被增强的那个方法

 */

public void before(JoinPoint joinPoint) {

String username = "rose";

if (!"admin".equals(username)) {

// 非admin用户,不具备权限,抛出异常

//joinPoint.getTarget().getClass().getName()获取目标类的名字

//joinPoint.getSignature().getName()获取被增强方法的名字

throw new RuntimeException("对不起!您没有对" + joinPoint.getTarget().getClass().getName() + "类中"

+ joinPoint.getSignature().getName() + "方法的访问权限");

}

}

/**

 * 后置通知方法

 * 应用场景: ATM取款机取款后,自动下发短信

 * 参数result:被增强那个方法的返回值

 */

public void afterReturning(JoinPoint joinPoint,Object result){

Date date = new Date();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateStr = sdf.format(date);

System.out.println("尊敬的用户,您于" + dateStr + "取款" + result + "元");

}

/**

 * 环绕通知方法

 * 应用场景:事务处理

 * @param proceedingJoinPoint 正在执行的连接点

 * @return

 */

public Object around(ProceedingJoinPoint proceedingJoinPoint){

System.out.println("开启事务");

//获取目标方法的参数

Object[] args = proceedingJoinPoint.getArgs();

Object result = null;

try {

//调用目标方法,获取目标方法的返回值

result = proceedingJoinPoint.proceed(args);

System.out.println("提交事务");

} catch (Throwable e) {

System.out.println("回滚事务");

}

//返回目标方法的返回值

return result;

}

}

说明:

ProceedingJoinPoint:表示正在执行的连接点,也就是目标方法

joinpoint.proceed表示调用目标方法

n 在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.xsd

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop.xsd">

        <bean id="customerDao" class="cn.itcast.dao.impl.CustomerDaoImpl"></bean>

        <!-- 配置切面类 -->

        <bean id="myAspectXml" class="cn.itcast.aspect.MyAspectXml"></bean>

        

        <aop:config>

        <!-- 配置切入点表达式:告诉spring框架哪些方法需要被增强 -->

        <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))" id="pointcut1"/>

        <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.delete(..))" id="pointcut2"/>

        <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.update(..))" id="pointcut3"/>

           <aop:aspect ref="myAspectXml">

        <!-- 配置前置通知:告诉spring在目标方法执行之前增强 -->

        <aop:before method="before" pointcut-ref="pointcut1"/>

        <!-- 配置后置通知 -->

        <aop:after-returning method="afterReturning" pointcut-ref="pointcut2" returning="result"/>

        <!-- 配置环绕通知 -->

        <aop:around method="around" pointcut-ref="pointcut3"/>

        </aop:aspect>

        </aop:config>

</beans>

n 修改TestAOP中的test1方法:

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestAOP {

 

@Autowired

private CustomerDao customerDao;

@Test

public void test1(){

//customerDao.save();

//customerDao.delete();

customerDao.update();

}

}

n 测试结果如下图:



2.3.4. 异常抛出通知

作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)

应用场景:处理异常(一般不可预知),记录日志

CustomerDao接口中增加list方法:

public interface CustomerDao {

 

public void save();

public Integer delete();

public void update();

public void list();

}

n 在目标类CustomerDaoImpl中编写目标方法list方法,需要对该方法增强

public class CustomerDaoImpl implements CustomerDao {

 

@Override

public void save() {

System.out.println("持久层:客户保存...");

}

 

@Override

public Integer delete() {

System.out.println("持久层:客户删除...");

return 100;

}

 

@Override

public void update() {

System.out.println("持久层:客户更新...");

}

 

@Override

public void list() {

System.out.println("持久层:客户查询...");

       //自己造一个异常

int i = 10 / 0;

}

 

 

}

在切面类MyAspectXml中定义一个异常通知方法afterThrowing方法,在方法加一个异常参数,名字叫ex

/**

 * 切面类

 *

 * @author xiaokaibo

 *

 */

public class MyAspectXml {

 

/**

 * 前置通知方法 应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志 joinPoint:连接点,指的是被增强的那个方法

 */

public void before(JoinPoint joinPoint) {

String username = "rose";

if (!"admin".equals(username)) {

// 非admin用户,不具备权限,抛出异常

// joinPoint.getTarget().getClass().getName()获取目标类的名字

// joinPoint.getSignature().getName()获取被增强方法的名字

throw new RuntimeException("对不起!您没有对" + joinPoint.getTarget().getClass().getName() + "类中"

+ joinPoint.getSignature().getName() + "方法的访问权限");

}

}

 

/**

 * 后置通知方法 应用场景: ATM取款机取款后,自动下发短信 参数result:被增强那个方法的返回值

 */

public void afterReturning(JoinPoint joinPoint, Object result) {

Date date = new Date();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateStr = sdf.format(date);

System.out.println("尊敬的用户,您于" + dateStr + "取款" + result + "元");

}

 

/**

 * 环绕通知方法 应用场景:事务处理

 *

 * @param proceedingJoinPoint

 *            正在执行的连接点

 * @return

 */

public Object around(ProceedingJoinPoint proceedingJoinPoint) {

System.out.println("开启事务");

// 获取目标方法的参数

Object[] args = proceedingJoinPoint.getArgs();

Object result = null;

try {

// 调用目标方法,获取目标方法的返回值

result = proceedingJoinPoint.proceed(args);

System.out.println("提交事务");

} catch (Throwable e) {

System.out.println("回滚事务");

}

// 返回目标方法的返回值

return result;

}

 

/**

 * 异常通知方法

 * 应用场景:处理异常

 * @param ex 目标方法抛出的异常,名字要与配置文件中配置的名字一致

 */

public void afterThrowing(JoinPoint joinPoint, Throwable ex) {

System.out.println("注意了:在" + joinPoint.getTarget().getClass().getName() + "中的"

+ joinPoint.getSignature().getName() + "方法中发生了异常:" + ex.getMessage());

}

}

n 在applicationContext.xml中配置切入点表达式及异常通知,需要给异常通知指定throwing属性,表示异常的名字,异常通知方法的形参名字要与此处一致:

<?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.xsd

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop.xsd">

        <bean id="customerDao" class="cn.itcast.dao.impl.CustomerDaoImpl"></bean>

        <!-- 配置切面类 -->

        <bean id="myAspectXml" class="cn.itcast.aspect.MyAspectXml"></bean>

        

        <aop:config>

        <!-- 配置切入点表达式:告诉spring框架哪些方法需要被增强 -->

        <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))" id="pointcut1"/>

        <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.delete(..))" id="pointcut2"/>

        <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.update(..))" id="pointcut3"/>

        <aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.list(..))" id="pointcut4"/>

        <aop:aspect ref="myAspectXml">

        <!-- 配置前置通知:告诉spring在目标方法执行之前增强 -->

        <aop:before method="before" pointcut-ref="pointcut1"/>

        <!-- 配置后置通知 -->

        <aop:after-returning method="afterReturning" pointcut-ref="pointcut2" returning="result"/>

        <!-- 配置环绕通知 -->

        <aop:around method="around" pointcut-ref="pointcut3"/>

        <!-- 配置异常通知 -->

        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/>

        </aop:aspect>

        </aop:config>

</beans>

n 修改TestAOP中的test1方法:

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestAOP {

 

@Autowired

private CustomerDao customerDao;

@Test

public void test1(){

//customerDao.save();

//customerDao.delete();

//customerDao.update();

customerDao.list();

}

}

n 测试结果如下:


2.3.5. 最终通知

作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)

应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象

 

在切面类MyAspect中定义一个最终通知方法after方法

/**

 * 切面类

 *

 * @author xiaokaibo

 *

 */

public class MyAspectXml {

 

/**

 * 前置通知方法 应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志 joinPoint:连接点,指的是被增强的那个方法

 */

public void before(JoinPoint joinPoint) {

String username = "rose";

if (!"admin".equals(username)) {

// 非admin用户,不具备权限,抛出异常

// joinPoint.getTarget().getClass().getName()获取目标类的名字

// joinPoint.getSignature().getName()获取被增强方法的名字

throw new RuntimeException("对不起!您没有对" + joinPoint.getTarget().getClass().getName() + "类中"

+ joinPoint.getSignature().getName() + "方法的访问权限");

}

}

 

/**

 * 后置通知方法 应用场景: ATM取款机取款后,自动下发短信 参数result:被增强那个方法的返回值

 */

public void afterReturning(JoinPoint joinPoint, Object result) {

Date date = new Date();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateStr = sdf.format(date);

System.out.println("尊敬的用户,您于" + dateStr + "取款" + result + "元");

}

 

/**

 * 环绕通知方法 应用场景:事务处理

 *

 * @param proceedingJoinPoint

 *            正在执行的连接点

 * @return

 */

public Object around(ProceedingJoinPoint proceedingJoinPoint) {

System.out.println("开启事务");

// 获取目标方法的参数

Object[] args = proceedingJoinPoint.getArgs();

Object result = null;

try {

// 调用目标方法,获取目标方法的返回值

result = proceedingJoinPoint.proceed(args);

System.out.println("提交事务");

} catch (Throwable e) {

System.out.println("回滚事务");

}

// 返回目标方法的返回值

return result;

}

 

/**

 * 异常通知方法 应用场景:处理异常

 *

 * @param ex

 *            目标方法抛出的异常

 */

public void afterThrowing(JoinPoint joinPoint, Throwable ex) {

System.out.println("注意了:在" + joinPoint.getTarget().getClass().getName() + "中的"

+ joinPoint.getSignature().getName() + "方法中发生了异常:" + ex.getMessage());

}

 

/**

 * 最终通知方法 应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象

 *

 * @param joinPoint

 *            被增强的那个方法

 */

public void after(JoinPoint joinPoint) {

System.out.println("开始释放资源,对应的连接点信息为:" + joinPoint.getTarget().getClass().getName() + "的"

+ joinPoint.getSignature().getName() + "方法");

}

}

n 在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.xsd

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop.xsd">

<bean id="customerDao" class="cn.itcast.dao.impl.CustomerDaoImpl"></bean>

<bean id="myAspectXml" class="cn.itcast.aspect.MyAspectXml"></bean>

<!-- AOP配置 -->

<aop:config>

<!-- 配置切入点 -->

<aop:pointcut

expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))"

id="pointcut1" />

<aop:pointcut

expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.delete(..))"

id="pointcut2" />

<aop:pointcut

expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.update(..))"

id="pointcut3" />

<aop:pointcut

expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.list(..))"

id="pointcut4" />

<!-- 配置切面 -->

<aop:aspect ref="myAspectXml">

<!-- 前置通知 -->

<aop:before method="checkPrivilege" pointcut-ref="pointcut1" />

<!-- 后置通知 -->

<aop:after-returning method="afterReturning"

pointcut-ref="pointcut2" returning="result" />

<!-- 环绕通知 -->

<aop:around method="around" pointcut-ref="pointcut3" />

<!-- 抛出异常通知 -->

<aop:after-throwing method="afterThrowing"

pointcut-ref="pointcut4" throwing="ex" />

<!-- 最终通知 -->

<aop:after method="after" pointcut-ref="pointcut4" />

</aop:aspect>

</aop:config>

</beans>

再次运行TestAOP中的test1方法,结果如下:


n 把CustomerDaoImpl中的list方法中的异常注释掉,再次运行TestAOP中的test1方法:

public class CustomerDaoImpl implements CustomerDao {

 

@Override

public void save() {

System.out.println("持久层:客户保存...");

}

 

@Override

public Integer delete() {

System.out.println("持久层:客户删除...");

return 100;

}

 

@Override

public void update() {

System.out.println("持久层:客户更新...");

}

 

@Override

public void list() {

       System.out.println("持久层:客户查询...");

//自己造一个异常

//int i = 10 / 0;

}

 

 

}

测试结果如下:发现没有执行异常通知,但是依然执行了最终通知。


注意,最终通知和后置通知的区别:最终通知,不管异常与否,都执行;而后置通知在异常时不执行。

3. SpringAOP编程(注解方式)

3.1. 基于注解的Spring AOP开发入门

3.1.1. 创建工程,引入依赖,创建核心配置文件


pom.xml内容如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>cn.itcast</groupId>

  <artifactId>spring4_day03_annotation</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <dependencies>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>4.2.4.RELEASE</version>

</dependency>

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>1.2.12</version>

</dependency>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.12</version>

<scope>test</scope>

</dependency>

<!-- spring整合junit的包 -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-test</artifactId>

<version>4.2.4.RELEASE</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-aspects</artifactId>

<version>4.2.4.RELEASE</version>

</dependency>

  </dependencies>

<build>

<plugins>

<!-- maven编译插件 -->

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<version>3.5.1</version>

<configuration>

<source>1.7</source>

<target>1.7</target>

</configuration>

</plugin>

</plugins>

</build>

</project>

在applicationContext.xml中开启注解扫描、开启aspectj自动代理

<?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.xsd

        http://www.springframework.org/schema/aop

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

        http://www.springframework.org/schema/context

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

        <!-- 开启spring注解扫描 -->

       <context:component-scan base-package="cn.itcast"></context:component-scan>

       <!-- 开启aspectj自动代理 -->

       <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

        

</beans>

3.1.2. 创建接口和实现类


public interface CustomerDao {

 

public void save();

public Integer delete();

public void update();

public void list();

}

@Repository("customerDao")

public class CustomerDaoImpl implements CustomerDao {

 

@Override

public void save() {

System.out.println("持久层:客户保存...");

}

 

@Override

public Integer delete() {

System.out.println("持久层:客户删除...");

return 100;

}

 

@Override

public void update() {

System.out.println("持久层:客户更新...");

}

 

@Override

public void list() {

System.out.println("持久层:客户查询...");

int i = 10 / 0;

}

 

 

}

3.1.3. 编写切面类


在MyAspectAnnotation类上添加@Component注解,把该类配置在Spring中;同时,还得添加@Aspect注解,表示该类是一个切面类;

checkPrivilege方法上添加@Before注解,并指定切入点表达式

/**

 * 切面类

 * @author kevin

 */

@Component

@Aspect//表示该类是一个切面类

public class MyAspectAnnotation {

 

@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void writeLog(){

System.out.println("记录日志啦.....");

}

}

提示:此处的切面类可以不取id

3.1.4. 编写测试类

创建TestAOP测试类,在其中创建test1方法


@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestAOP {

 

@Autowired

private CustomerDao customerDao;

@Test

public void test1(){

customerDao.save();

}

}

观察结果,发现save方法被增强了:


3.2. 用注解来配置Spring AOP中的各种通知

3.2.1. 前置通知

/**

 * 切面类

 * @author xiaokaibo

 *

 */

@Component

@Aspect//表示该类是一个切面类

public class MyAspectAnnotation {

/*@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void writeLog(){

System.out.println("记录日志啦.....");

}*/

/**

 * 前置通知方法 应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志 joinPoint:连接点,指的是被增强的那个方法

 */

@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void before(JoinPoint joinPoint) {

String username = "rose";

if (!"admin".equals(username)) {

// 非admin用户,不具备权限,抛出异常

// joinPoint.getTarget().getClass().getName()获取目标类的名字

// joinPoint.getSignature().getName()获取被增强方法的名字

throw new RuntimeException("对不起!您没有对" + joinPoint.getTarget().getClass().getName() + "类中"

+ joinPoint.getSignature().getName() + "方法的访问权限");

}

}

}

运行TestAOP中的test1方法,测试结果如下(同xml配置的效果一下):


3.2.2. 后置通知

修改MyAspectAnnotation,在其中创建后置通知方法afterReturning,在该方法上添加@AfterReturning注解,通过value属性指定切入点表达式,returning属性指定返回值的名字,该方法形参的名字要与returning的值一致:

/**

 * 切面类

 * @author xiaokaibo

 *

 */

@Component

@Aspect//表示该类是一个切面类

public class MyAspectAnnotation {

/*@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void writeLog(){

System.out.println("记录日志啦.....");

}*/

/**

 * 前置通知方法 应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志 joinPoint:连接点,指的是被增强的那个方法

 */

@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void before(JoinPoint joinPoint) {

String username = "rose";

if (!"admin".equals(username)) {

// 非admin用户,不具备权限,抛出异常

// joinPoint.getTarget().getClass().getName()获取目标类的名字

// joinPoint.getSignature().getName()获取被增强方法的名字

throw new RuntimeException("对不起!您没有对" + joinPoint.getTarget().getClass().getName() + "类中"

+ joinPoint.getSignature().getName() + "方法的访问权限");

}

}

/**

 * 后置通知方法 应用场景: ATM取款机取款后,自动下发短信 参数result:被增强那个方法的返回值

 * returning属性指定目标方法返回值的名字

 */

@AfterReturning(value="execution(* cn.itcast.dao.impl.CustomerDaoImpl.delete(..))",returning="result")

public void afterReturning(JoinPoint joinPoint, Object result) {

Date date = new Date();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateStr = sdf.format(date);

System.out.println("尊敬的用户,您于" + dateStr + "取款" + result + "元");

}

}

修改TestAOP中的test1方法:

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestAOP {

 

@Autowired

private CustomerDao customerDao;

@Test

public void test1(){

//customerDao.save();

customerDao.delete();

}

}

运行结果如下:


3.2.3. 环绕通知

修改MyAspectAnnotation,在其中创建环绕通知方法around,在该方法上添加@Around注解,通过value属性指定切入点表达式:

/**

 * 切面类

 * @author xiaokaibo

 *

 */

@Component

@Aspect//表示该类是一个切面类

public class MyAspectAnnotation {

/*@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void writeLog(){

System.out.println("记录日志啦.....");

}*/

/**

 * 前置通知方法 应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志 joinPoint:连接点,指的是被增强的那个方法

 */

@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void before(JoinPoint joinPoint) {

String username = "rose";

if (!"admin".equals(username)) {

// 非admin用户,不具备权限,抛出异常

// joinPoint.getTarget().getClass().getName()获取目标类的名字

// joinPoint.getSignature().getName()获取被增强方法的名字

throw new RuntimeException("对不起!您没有对" + joinPoint.getTarget().getClass().getName() + "类中"

+ joinPoint.getSignature().getName() + "方法的访问权限");

}

}

/**

 * 后置通知方法 应用场景: ATM取款机取款后,自动下发短信 参数result:被增强那个方法的返回值

 * returning属性指定目标方法返回值的名字

 */

@AfterReturning(value="execution(* cn.itcast.dao.impl.CustomerDaoImpl.delete(..))",returning="result")

public void afterReturning(JoinPoint joinPoint, Object result) {

Date date = new Date();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateStr = sdf.format(date);

System.out.println("尊敬的用户,您于" + dateStr + "取款" + result + "元");

}

/**

 * 环绕通知方法 应用场景:事务处理

 *

 * @param proceedingJoinPoint

 *            正在执行的连接点

 * @return

 */

@Around("execution(* cn.itcast.dao.impl.CustomerDaoImpl.update(..))")

public Object around(ProceedingJoinPoint proceedingJoinPoint) {

System.out.println("开启事务");

// 获取目标方法的参数

Object[] args = proceedingJoinPoint.getArgs();

Object result = null;

try {

// 调用目标方法,获取目标方法的返回值

result = proceedingJoinPoint.proceed(args);

System.out.println("提交事务");

} catch (Throwable e) {

System.out.println("回滚事务");

}

// 返回目标方法的返回值

return result;

}

}

修改TestAOP中的test1方法:

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestAOP {

 

@Autowired

private CustomerDao customerDao;

@Test

public void test1(){

//customerDao.save();

//customerDao.delete();

customerDao.update();

}

}

运行结果如下:


3.2.4. 异常通知

修改MyAspectAnnotation,在其中创建异常通知方法afterThrowing,在该方法上添加@AfterThrowing注解,通过value属性指定切入点表达式,通过throwing属性指定异常的名字。在该方法中增加一个形参,表示发生的异常,形参的名字与throwing属性的值一致:

/**

 * 切面类

 * @author xiaokaibo

 *

 */

@Component

@Aspect//表示该类是一个切面类

public class MyAspectAnnotation {

/*@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void writeLog(){

System.out.println("记录日志啦.....");

}*/

/**

 * 前置通知方法 应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志 joinPoint:连接点,指的是被增强的那个方法

 */

@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void before(JoinPoint joinPoint) {

String username = "rose";

if (!"admin".equals(username)) {

// 非admin用户,不具备权限,抛出异常

// joinPoint.getTarget().getClass().getName()获取目标类的名字

// joinPoint.getSignature().getName()获取被增强方法的名字

throw new RuntimeException("对不起!您没有对" + joinPoint.getTarget().getClass().getName() + "类中"

+ joinPoint.getSignature().getName() + "方法的访问权限");

}

}

/**

 * 后置通知方法 应用场景: ATM取款机取款后,自动下发短信 参数result:被增强那个方法的返回值

 * returning属性指定目标方法返回值的名字

 */

@AfterReturning(value="execution(* cn.itcast.dao.impl.CustomerDaoImpl.delete(..))",returning="result")

public void afterReturning(JoinPoint joinPoint, Object result) {

Date date = new Date();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateStr = sdf.format(date);

System.out.println("尊敬的用户,您于" + dateStr + "取款" + result + "元");

}

/**

 * 环绕通知方法 应用场景:事务处理

 *

 * @param proceedingJoinPoint

 *            正在执行的连接点

 * @return

 */

@Around("execution(* cn.itcast.dao.impl.CustomerDaoImpl.update(..))")

public Object around(ProceedingJoinPoint proceedingJoinPoint) {

System.out.println("开启事务");

// 获取目标方法的参数

Object[] args = proceedingJoinPoint.getArgs();

Object result = null;

try {

// 调用目标方法,获取目标方法的返回值

result = proceedingJoinPoint.proceed(args);

System.out.println("提交事务");

} catch (Throwable e) {

System.out.println("回滚事务");

}

// 返回目标方法的返回值

return result;

}

/**

 * 异常通知方法 应用场景:处理异常

 *

 * @param ex

 *            目标方法抛出的异常

 */

@AfterThrowing(value="execution(* cn.itcast.dao.impl.CustomerDaoImpl.list(..))",throwing="ex")

public void afterThrowing(JoinPoint joinPoint, Throwable ex) {

System.out.println("注意了:在" + joinPoint.getTarget().getClass().getName() + "中的"

+ joinPoint.getSignature().getName() + "方法中发生了异常:" + ex.getMessage());

}

}

修改TestAOP中的test1方法:





@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestAOP {

 

@Autowired

private CustomerDao customerDao;

@Test

public void test1(){

//customerDao.save();

//customerDao.delete();

//customerDao.update();

customerDao.list();

}

}

运行结果如下:


3.2.5. 最终通知

修改MyAspectAnnotation,在其中创建最终通知方法after,在该方法上添加@After注解,通过value属性指定切入点表达式:

/**

 * 切面类

 * @author xiaokaibo

 *

 */

@Component

@Aspect//表示该类是一个切面类

public class MyAspectAnnotation {

/*@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void writeLog(){

System.out.println("记录日志啦.....");

}*/

/**

 * 前置通知方法 应用场景: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志 joinPoint:连接点,指的是被增强的那个方法

 */

@Before("execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))")

public void before(JoinPoint joinPoint) {

String username = "rose";

if (!"admin".equals(username)) {

// 非admin用户,不具备权限,抛出异常

// joinPoint.getTarget().getClass().getName()获取目标类的名字

// joinPoint.getSignature().getName()获取被增强方法的名字

throw new RuntimeException("对不起!您没有对" + joinPoint.getTarget().getClass().getName() + "类中"

+ joinPoint.getSignature().getName() + "方法的访问权限");

}

}

/**

 * 后置通知方法 应用场景: ATM取款机取款后,自动下发短信 参数result:被增强那个方法的返回值

 * returning属性指定目标方法返回值的名字

 */

@AfterReturning(value="execution(* cn.itcast.dao.impl.CustomerDaoImpl.delete(..))",returning="result")

public void afterReturning(JoinPoint joinPoint, Object result) {

Date date = new Date();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

String dateStr = sdf.format(date);

System.out.println("尊敬的用户,您于" + dateStr + "取款" + result + "元");

}

/**

 * 环绕通知方法 应用场景:事务处理

 *

 * @param proceedingJoinPoint

 *            正在执行的连接点

 * @return

 */

@Around("execution(* cn.itcast.dao.impl.CustomerDaoImpl.update(..))")

public Object around(ProceedingJoinPoint proceedingJoinPoint) {

System.out.println("开启事务");

// 获取目标方法的参数

Object[] args = proceedingJoinPoint.getArgs();

Object result = null;

try {

// 调用目标方法,获取目标方法的返回值

result = proceedingJoinPoint.proceed(args);

System.out.println("提交事务");

} catch (Throwable e) {

System.out.println("回滚事务");

}

// 返回目标方法的返回值

return result;

}

/**

 * 异常通知方法 应用场景:处理异常

 *

 * @param ex

 *            目标方法抛出的异常

 */

@AfterThrowing(value="execution(* cn.itcast.dao.impl.CustomerDaoImpl.list(..))",throwing="ex")

public void afterThrowing(JoinPoint joinPoint, Throwable ex) {

System.out.println("注意了:在" + joinPoint.getTarget().getClass().getName() + "中的"

+ joinPoint.getSignature().getName() + "方法中发生了异常:" + ex.getMessage());

}

/**

 * 后置通知方法 应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )

 *

 * @param joinPoint

 *            被增强的那个方法

 */

@After("execution(* cn.itcast.dao.impl.CustomerDaoImpl.list(..))")

public void after(JoinPoint joinPoint) {

System.out.println("开始释放资源,对应的连接点信息为:" + joinPoint.getTarget().getClass().getName() + "的"

+ joinPoint.getSignature().getName() + "方法");

}

 

}

再次运行TestAOP中的test1方法,结果如下


小结:

1、概念

2、Aop的开发流程:

a) 编写目标bean

b) 编写切面类、通知方法

c) 把目标bean、切面bean交给sprng管理

d) 配置切入点表达式

e) 指定用切面类的哪个方法来增强

3、常见通知:

a) 前置通知

b) 后置通知

c) 环绕通知

d) 异常通知

e) 最终通知

4xml和注解的两种配置形式

4. AOP应用:事务管理(扩展)

4.1. 案例分析

我们都知道,事务要加在业务层。以前,我们需要在每个业务层的方法之前手动开启事务,当方法正常结束之后提交事务,如果方法异常了就回滚事务。最后,释放连接。这样做,很繁琐。


可以利用AOP的思想来帮我们管理事务,为业务层的每个方法增强:

前置通知:开启事务

后置通知:提交事务

异常通知:回滚事务

最终通知:释放资源

 

我们以转账为例,下面搭建转账环境:

1.1. 环境搭建

1.1.1. 创建测试表

CREATE TABLE account(

id BIGINT PRIMARY KEY AUTO_INCREMENT,

NAME VARCHAR(40),

money DOUBLE

)CHARACTER SET utf8 COLLATE utf8_general_ci;

 

1.1.2. 创建工程,引入依赖

 

pom.xml内容如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>cn.itcast</groupId>

<artifactId>spring4_day03_aopTx</artifactId>

<version>0.0.1-SNAPSHOT</version>

<dependencies>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>4.2.4.RELEASE</version>

</dependency>

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>1.2.12</version>

</dependency>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.12</version>

<scope>test</scope>

</dependency>

<!-- spring整合junit的包 -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-test</artifactId>

<version>4.2.4.RELEASE</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-aspects</artifactId>

<version>4.2.4.RELEASE</version>

</dependency>

<!-- mysql驱动包 -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.18</version>

<scope>runtime</scope>

</dependency>

<!-- dbutils -->

<dependency>

<groupId>commons-dbutils</groupId>

<artifactId>commons-dbutils</artifactId>

<version>1.7</version>

</dependency>

<!-- c3p0包 -->

<dependency>

<groupId>c3p0</groupId>

<artifactId>c3p0</artifactId>

<version>0.9.1.2</version>

</dependency>

 

 

</dependencies>

<build>

<plugins>

<!-- maven编译插件 -->

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<version>3.5.1</version>

<configuration>

<source>1.7</source>

<target>1.7</target>

</configuration>

</plugin>

</plugins>

</build>

</project>

1.1.3. 编写获取数据库连接工具类

 

n 编写DataSourceUtils工具类

public class DataSourceUtils {

 

//线程本地变量,用于把Connection和当前线程绑定

private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

 

/**

 * 从数据源中获取connection

 *

 * @param dataSource

 * @param useCurrentConnection

 * @return

 */

public static Connection getConnection(DataSource dataSource) {

try {

// 从当前线程中获取connection

Connection connection = tl.get();

// 如果当前线程中没有connection,则创建一个新的connection,并和当前线程绑定

if (connection == null) {

connection = dataSource.getConnection();

tl.set(connection);

}

// 返回从当前线程中获取的connection

return connection;

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

 

/**

 * 释放连接

 * @param currentConnection

 */

public static void releaseConnection() {

//从当前线程中获取绑定的Connection

Connection connection = tl.get();

try {

connection.close();

//解除绑定

tl.remove();

} catch (SQLException e) {

e.printStackTrace();

}

}

}

编写DbAssit工具类获取线程中绑定的Connection

public class DbAssit {

 

private DataSource dataSource;//数据源

public void setDataSource(DataSource dataSource) {

this.dataSource = dataSource;

}

/**

 * 获取当前线程中的连接

 * @return

 */

public Connection getCurrentConnection(){

return DataSourceUtils.getConnection(dataSource);

}

/**

 * 释放连接

 */

public void releaseConnection(){

DataSourceUtils.releaseConnection();

}

}

1.1.4. 编写daoservice

n 编写账户实体类

 

public class Account implements Serializable {

 

private static final long serialVersionUID = 1L;

private Long id;

private String name;

private Double money;

public Long getId() {

return id;

}

public void setId(Long id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public Double getMoney() {

return money;

}

public void setMoney(Double money) {

this.money = money;

}

@Override

public String toString() {

return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";

}

 

}

编写AccountDao接口

 

public interface AccountDao {

/**

 * 根据id查询账户

 * @param id

 * @return

 */

public Account findById(Long id);

 

/**

 * 更新账户

 * @param account

 */

public void update(Account account);

}

编写AccountDao接口的实现类

 

public class AccountDaoImpl implements AccountDao {

 

private QueryRunner queryRunner;//注入queryRunner,执行sql语句

private DbAssit dbAssit;//注入dbAssit,获取当前线程中的连接

 

public void setQueryRunner(QueryRunner queryRunner) {

this.queryRunner = queryRunner;

}

 

public void setDbAssit(DbAssit dbAssit) {

this.dbAssit = dbAssit;

}

 

@Override

public void update(Account account) {

try {

this.queryRunner.update(dbAssit.getCurrentConnection(),

"update account set name = ?,money = ? where id = ?", account.getName(), account.getMoney(),

account.getId());

} catch (SQLException e) {

e.printStackTrace();

}

}

 

@Override

public Account findById(Long id) {

Account account = null;

try {

account = this.queryRunner.query(dbAssit.getCurrentConnection(), "select * from account where id = ?",

new BeanHandler<Account>(Account.class), id);

} catch (SQLException e) {

e.printStackTrace();

}

return account;

}

 

}

编写AccountService

 

public interface AccountService {

 

/**

 * 转账方法

 * @param fromId 转出账户id

 * @param toId 转入账户id

 * @param money 转账金额

 */

public void transfer(Long fromId,Long toId,Double money);

}

n 编写AccountService的实现类

 

public class AccountServiceImpl implements AccountService {

private AccountDao accountDao;

 

public void setAccountDao(AccountDao accountDao) {

this.accountDao = accountDao;

}

 

 

 

@Override

public void transfer(Long fromId, Long toId, Double money) {

//查询转出账户

Account fromAccount = accountDao.findById(fromId);

//查询转入账户

Account toAccount = accountDao.findById(toId);

//转出账户减钱

fromAccount.setMoney(fromAccount.getMoney() - money);

//转入账户加钱

toAccount.setMoney(toAccount.getMoney() + money);

//更新转出账户

accountDao.update(fromAccount);

//更新转入账户

accountDao.update(toAccount);

}

 

}

1.1.5. daoservice配置到spring

引入配置文件:

 

把相关bean配置到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:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

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

        http://www.springframework.org/schema/context

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

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop.xsd">

        

<bean id="accountService" class="cn.itcast.service.impl.AccountServiceImpl">

<!-- 注入dao -->

<property name="accountDao" ref="accountDao"></property>

</bean>

<bean id="accountDao" class="cn.itcast.dao.impl.AccountDaoImpl">

<!-- 注入queryRunner -->

<property name="queryRunner" ref="queryRunner"></property>

<!-- 注入dbAssit -->

<property name="dbAssit" ref="dbAssit"></property>

</bean>

<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">

</bean>

<bean id="dbAssit" class="cn.itcast.dbassit.DbAssit">

<!-- 注入数据源 -->

<property name="dataSource" ref="dataSource"></property>

</bean>

<!-- 配置数据源 -->

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

<property name="driverClass" value="com.mysql.jdbc.Driver"></property>

<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/estore"></property>

<property name="user" value="root"></property>

<property name="password" value="123456"></property>

</bean>

</beans>

1.1.6. 测试

编写单元测试类

 

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestTx {

@Autowired

private AccountService accountService;

 

@Test

public void test1(){

accountService.transfer(1L, 2L, 100.0);

}

}

测试之前,表中的数据如下:

 

测试之后,表中的数据如下:

 

再测试转账异常的情况,在转账业务中造一个异常:

@Override

public void transfer(Long fromId, Long toId, Double money) {

//查询转出账户

Account fromAccount = accountDao.findById(fromId);

//查询转入账户

Account toAccount = accountDao.findById(toId);

//转出账户减钱

fromAccount.setMoney(fromAccount.getMoney() - money);

//转入账户加钱

toAccount.setMoney(toAccount.getMoney() + money);

//更新转出账户

accountDao.update(fromAccount);

int i = 10 / 0;

//更新转入账户

accountDao.update(toAccount);

}

测试完之后,表中的数据如下:

 

发现数据不对,原因是转账里的两个操作是独立的,不在一个事务中。

怎么解决呢?采用AOP对转账方法进行增强,增加事务的功能。

1.2. 编写事务管理器类

 

/**

 * 事务管理器类,专门用来处理事务

 * @author xiaokaibo

 *

 */

public class TransactionManager {

 

private DbAssit dbAssit;

 

public void setDbAssit(DbAssit dbAssit) {

this.dbAssit = dbAssit;

}

/**

 * 开启事务

 */

public void beginTransaction(){

try {

//把connection的自动提交功能关闭

dbAssit.getCurrentConnection().setAutoCommit(false);

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

/**

 * 提交事务

 */

public void commitTransaction(){

try {

dbAssit.getCurrentConnection().commit();

} catch (SQLException e) {

e.printStackTrace();

}

}

/**

 * 回滚事务

 */

public void rollbackTransaction(){

try {

dbAssit.getCurrentConnection().rollback();

} catch (SQLException e) {

e.printStackTrace();

}

}

/**

 * 释放资源

 */

public void releaseConnection(){

dbAssit.releaseConnection();

}

}

1.3. 配置AOP

<?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:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

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

        http://www.springframework.org/schema/context

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

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop.xsd">

        

<bean id="accountService" class="cn.itcast.service.impl.AccountServiceImpl">

<!-- 注入dao -->

<property name="accountDao" ref="accountDao"></property>

</bean>

<bean id="accountDao" class="cn.itcast.dao.impl.AccountDaoImpl">

<!-- 注入queryRunner -->

<property name="queryRunner" ref="queryRunner"></property>

<!-- 注入dbAssit -->

<property name="dbAssit" ref="dbAssit"></property>

</bean>

<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">

</bean>

<bean id="dbAssit" class="cn.itcast.dbassit.DbAssit">

<!-- 注入数据源 -->

<property name="dataSource" ref="dataSource"></property>

</bean>

<!-- 配置数据源 -->

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

<property name="driverClass" value="com.mysql.jdbc.Driver"></property>

<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/estore"></property>

<property name="user" value="root"></property>

<property name="password" value="123456"></property>

</bean>

<!-- 配置事务管理器 -->

<bean id="transactionManager" class="cn.itcast.dbassit.TransactionManager">

<!-- 注入dbAssit -->

<property name="dbAssit" ref="dbAssit"></property>

</bean>

<!-- aop配置 -->

<aop:config>

<!-- 配置切入点表达式:告诉spring对业务层的所有方法加事务 -->

<aop:pointcut expression="execution(* cn.itcast.service.impl.*.*(..))" id="pt"/>

<aop:aspect ref="transactionManager">

<!-- 前置通知:开启事务 -->

<aop:before method="beginTransaction" pointcut-ref="pt"/>

<!-- 后置通知:提交事务 -->

<aop:after-returning method="commitTransaction" pointcut-ref="pt"/>

<!-- 异常通知:回滚事务 -->

<aop:after-throwing method="rollbackTransaction" pointcut-ref="pt"/>

<!-- 最终通知:释放资源 -->

<aop:after method="releaseConnection" pointcut-ref="pt"/>

</aop:aspect>

</aop:config>

</beans>

1.4. 测试

发现转账失败,表中的数据依然是正确的,表示AOP起到了作用,对业务层的方法增强了事务管理的功能:

















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值