AOP开发,IOC的依赖不能少;同时引入AOP的依赖,坐标如下:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>4.2.4.RELEASEversion>
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.0modelVersion>
<groupId>cn.itcastgroupId>
<artifactId>spring4_day03artifactId>
<version>0.0.1-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.2.4.RELEASEversion>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.12version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>4.2.4.RELEASEversion>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.5.1version>
<configuration>
<source>1.7source>
<target>1.7target>
configuration>
plugin>
plugins>
build>
project>
n 引入applicationContext.xml和log4j.properties
创建CustomerDao接口:
public interface CustomerDao {
public void save();
}
创建CustomerDao接口的实现类CustomerDaoImpl:
public class CustomerDaoImpl implements CustomerDao {
@Override
public void save() {
System.out.println("持久层:客户保存...");
}
}
n 把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>
/**
* 自定义切面类
* @author kevin
*/
public class MyAspectXml {
public void writeLog(){
System.out.println("记录日志啦.....");
}
}
把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>
beans>
在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>
beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAOP {
@Autowired
private CustomerDao customerDao;
@Test
public void test1(){
customerDao.save();
}
}
运行test1方法,观察结果,发现save方法被增强了:
切入点表达式语法:[修饰符] 返回类型 包名.类名.方法名(形式参数)
常见写法:
n execution(public * *(..)) 所有的public方法
n execution(* set*(..)) 所有set开头的方法
n execution(* com.xyz.service.AccountService.*(..)) AccountService类中的所有方法
n execution(* com.xyz.service.*.*(..)) com.xyz.service包下所有的方法
n execution(* com.xyz.service..*.*(..)) · com.xyz.service包及其子包下所有的方法
在AOP开发入门中,我们写的是一个前置通知,表示在目标方法执行之前增强
那么,除了前置通知以外,Spring还提供了其它的通知类型:
1、前置通知
2、后置通知
3、环绕通知
4、异常通知
5、最终通知
应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志
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>
<aop:pointcut expression="execution(* cn.itcast.dao.impl.CustomerDaoImpl.save(..))" id="pointcut1"/>
<aop:aspect ref="myAspectXml">
<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();
}
}
特点:在目标方法运行后,返回值后执行增强代码逻辑。
应用场景:与业务相关的,如ATM取款机取款后,自动下发短信。
n 在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;
}
}
n 在切面类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 + "元");
}
}
n 在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>
<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">
<aop:before method="before" pointcut-ref="pointcut1"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut2" returning="result"/>
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();
}
}
n 测试结果如下:
特点:目标执行前后,都进行增强(控制目标方法执行)
应用场景:日志、缓存、权限、性能监控、事务管理
增强代码的方法要求:
接受的参数:ProceedingJoinPoint(可执行的连接点)
返回值:Object返回值
抛出Throwable异常。
n 在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("持久层:客户更新...");
}
}
n 在切面类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>
<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">
<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 测试结果如下图:
作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
应用场景:处理异常(一般不可预知),记录日志
n 在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;
}
}
n 在切面类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>
<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="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 测试结果如下:
作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)
应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
n 在切面类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: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;
}
}
测试结果如下:发现没有执行异常通知,但是依然执行了最终通知。
注意,最终通知和后置通知的区别:最终通知,不管异常与否,都执行;而后置通知在异常时不执行。
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.0modelVersion>
<groupId>cn.itcastgroupId>
<artifactId>spring4_day03_annotationartifactId>
<version>0.0.1-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.2.4.RELEASEversion>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.12version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>4.2.4.RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>4.2.4.RELEASEversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.5.1version>
<configuration>
<source>1.7source>
<target>1.7target>
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">
beans>
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;
}
}
在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
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAOP {
@Autowired
private CustomerDao customerDao;
@Test
public void test1(){
customerDao.save();
}
}
观察结果,发现save方法被增强了:
/**
* 切面类
* @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配置的效果一下):
n 修改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 + "元");
}
}
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();
}
}
运行结果如下:
n 修改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;
}
}
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 修改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());
}
}
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 修改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) 最终通知
4、xml和注解的两种配置形式
我们都知道,事务要加在业务层。以前,我们需要在每个业务层的方法之前手动开启事务,当方法正常结束之后提交事务,如果方法异常了就回滚事务。最后,释放连接。这样做,很繁琐。
可以利用AOP的思想来帮我们管理事务,为业务层的每个方法增强:
前置通知:开启事务
后置通知:提交事务
异常通知:回滚事务
最终通知:释放资源
我们以转账为例,下面搭建转账环境:
CREATE TABLE account(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(40),
money DOUBLE
)CHARACTER SET utf8 COLLATE utf8_general_ci;
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.0modelVersion>
<groupId>cn.itcastgroupId>
<artifactId>spring4_day03_aopTxartifactId>
<version>0.0.1-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.2.4.RELEASEversion>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.12version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>4.2.4.RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>4.2.4.RELEASEversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.18version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>commons-dbutilsgroupId>
<artifactId>commons-dbutilsartifactId>
<version>1.7version>
dependency>
<dependency>
<groupId>c3p0groupId>
<artifactId>c3p0artifactId>
<version>0.9.1.2version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.5.1version>
<configuration>
<source>1.7source>
<target>1.7target>
configuration>
plugin>
plugins>
build>
project>
n 编写DataSourceUtils工具类
public class DataSourceUtils {
//线程本地变量,用于把Connection和当前线程绑定
private static ThreadLocal
/**
* 从数据源中获取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();
}
}
}
n 编写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();
}
}
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 + "]";
}
}
n 编写AccountDao接口
public interface AccountDao {
/**
* 根据id查询账户
* @param id
* @return
*/
public Account findById(Long id);
/**
* 更新账户
* @param account
*/
public void update(Account account);
}
n 编写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
} catch (SQLException e) {
e.printStackTrace();
}
return account;
}
}
n 编写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);
}
}
引入配置文件:
把相关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">
<property name="accountDao" ref="accountDao">property>
bean>
<bean id="accountDao" class="cn.itcast.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner">property>
<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>
编写单元测试类
@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对转账方法进行增强,增加事务的功能。
/**
* 事务管理器类,专门用来处理事务
* @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();
}
}
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">
<property name="accountDao" ref="accountDao">property>
bean>
<bean id="accountDao" class="cn.itcast.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner">property>
<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">
<property name="dbAssit" ref="dbAssit">property>
bean>
<aop:config>
<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>
发现转账失败,表中的数据依然是正确的,表示AOP起到了作用,对业务层的方法增强了事务管理的功能: