需求:使用AOP对转账案例进行优化,业务层仅保留核心业务,事务的控制使用AOP来完成
步骤分析
1. 新建maven项目,创建以下包类

2. 导入依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring_aop_xml</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- Spring的AOP开发依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- java单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
</dependencies>
</project>
3. pojo实体类、dao(impl)持久层、service(impl)业务层
package com.bjpowernode.pojo;
public class Account {
private Integer id;
private String name;
private Double money;
// 省略了 getter、setter、有参构造、无参构造、toString()方法
}
package com.bjpowernode.dao;
import com.bjpowernode.pojo.Account;
public interface AccountDao {
Account get(int id);
void updateMoney(String name,int money);
}
package com.bjpowernode.dao.impl;
import com.bjpowernode.dao.AccountDao;
import com.bjpowernode.pojo.Account;
import com.bjpowernode.utils.JDBCUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import java.sql.SQLException;
public class AccountDaoImpl implements AccountDao {
private QueryRunner qr;
public void setQr(QueryRunner qr) { this.qr = qr; }
private JDBCUtils jdbcUtils;
public void setJdbcUtils(JDBCUtils jdbcUtils) { this.jdbcUtils = jdbcUtils; }
@Override
public Account get(int id) {
try {
String sql = "select * from account where id=?";
return qr.query(sql, new BeanHandler<>(Account.class), id);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void updateMoney(String name, int money) {
String sql = "update account set money = money + ? where name = ?";
try {
qr.update(jdbcUtils.getConnection(),sql,money,name);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
package com.bjpowernode.service;
import com.bjpowernode.pojo.Account;
public interface AccountService {
Account get(int id);
void transfer(String fromUser,String toUser,int money);
}
package com.bjpowernode.service.impl;
import com.bjpowernode.dao.AccountDao;
import com.bjpowernode.pojo.Account;
import com.bjpowernode.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; }
@Override
public Account get(int id) { return accountDao.get(id); }
@Override
public void transfer(String fromUser, String toUser, int money) {
accountDao.updateMoney(fromUser, -money);
//int a = 1/0;
accountDao.updateMoney(toUser, money);
}
}
4. utils工具类
package com.bjpowernode.utils;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class JDBCUtils {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; }
// ThreadLocal和当前线程相关的对象,可以将对象绑定到线程上,在同一个线程中,获取到的始终是同一个对象
private static ThreadLocal<Connection> TL = new ThreadLocal<>();
public Connection getConnection() {
Connection conn = null;
try {
conn = TL.get(); // 从线程上获取同一连接对象
if ( conn == null ) {
// 当前线程第一次调用getConnection方法
conn = dataSource.getConnection();
// 将连接对象和线程进行绑定
TL.set(conn);
}
} catch (SQLException e) {
e.printStackTrace();
}
// System.out.println(conn);
return conn;
}
public void begin() {
try {
// 自动连接?:否,改手动
getConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void rollback() {
try {
getConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void commit() {
try {
getConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void release() {
try {
// 不是真正的关闭,而是将连接对象返回给连接池,底层是动态代理技术
getConnection().close();
// 将连接对象从当前线程移除
TL.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5. 通知类(对目标方法进行增强的类)
package com.bjpowernode.advice;
import com.bjpowernode.utils.JDBCUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import java.sql.SQLException;
// 事务管理器,对业务方法提供事务的增强处理
public class TransactionManager {
private JDBCUtils jdbcUtils;
public void setJdbcUtils(JDBCUtils jdbcUtils) {
this.jdbcUtils = jdbcUtils;
}
public void begin() {
System.out.println("开启事务");
try {
jdbcUtils.getConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void rollback() {
System.out.println("回滚事务");
try {
jdbcUtils.getConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void commit() {
System.out.println("提交事务");
try {
jdbcUtils.getConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 环绕通知:整合前面的通知
public Object around(ProceedingJoinPoint pjp) {
Object result = null;
try {
begin(); // 开启事务
result = pjp.proceed(); // 执行目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
rollback(); // 事务回滚
} finally {
commit(); // 事务提交
jdbcUtils.release(); // 释放
}
return result;
}
}
6. applicationContext.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns: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
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.bjpowernode"/>
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- set方法注入 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mydb" />
<property name="username" value="root" />
<property name="password" value="root"/>
</bean>
<!--DBUtils的核心类:QueryRunner-->
<bean id="qr" class="org.apache.commons.dbutils.QueryRunner">
<!-- 构造方法注入 -->
<constructor-arg name="ds" ref="dataSource"/>
</bean>
<!-- JDBCUtils -->
<bean id="jdbcutils" class="com.bjpowernode.utils.JDBCUtils">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置Dao、Service -->
<bean id="accountDao" class="com.bjpowernode.dao.impl.AccountDaoImpl">
<property name="qr" ref="qr" />
<property name="jdbcUtils" ref="jdbcutils" />
</bean>
<bean id="accountService" class="com.bjpowernode.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- AOP相关配置-->
<!-- 1. 配置通知类(对业务方法提供增强的类)-->
<bean id="transactionManager" class="com.bjpowernode.advice.TransactionManager" >
<property name="jdbcUtils" ref="jdbcutils" />
</bean>
<!-- 2. aop配置:使用哪个通知类对哪些方法进行增强 -->
<!-- 方式一
<aop:config>
<aop:aspect ref="transactionManager">
<!– pointcut: 切入点,即目标方法 –>
<!– 前置通知:在切入点执行之前,先运行transactionManager实例中的begin方法 –>
<aop:before method="begin" pointcut="execution(public void com.bjpowernode.service.impl.AccountServiceImpl.transfer(String,String,int))" />
<!– 发生异常时 –>
<aop:after-throwing method="rollback" pointcut="execution(public void com.bjpowernode.service.impl.AccountServiceImpl.transfer(String,String,int))" />
<!– 最终通知 –>
<aop:after method="commit" pointcut="execution(public void com.bjpowernode.service.impl.AccountServiceImpl.transfer(String,String,int))" />
</aop:aspect>
</aop:config>
-->
<!-- 方式二
<aop:config>
<!– pointcut: 切入点,即目标方法 –>
<aop:pointcut id="transfer" expression="execution(public void com.bjpowernode.service.impl.AccountServiceImpl.transfer(String,String,int))"/>
<!– 配置切面 ref: 使用哪个实例对切面(事务)进行增强 –>
<aop:aspect ref="transactionManager">
<!– 前置通知:在切入点执行之前 –>
<aop:before method="begin" pointcut-ref="transfer" />
<!– 发生异常时 –>
<aop:after-throwing method="rollback" pointcut-ref="transfer" />
<!– 最终通知:在切入点执行之后 –>
<aop:after method="commit" pointcut-ref="transfer" />
</aop:aspect>
</aop:config>
-->
<!-- 方式三:推荐!!! -->
<aop:config>
<!-- pointcut: 切入点,即目标方法 -->
<aop:pointcut id="transfer" expression="execution(public void com.bjpowernode.service.impl.AccountServiceImpl.transfer(String,String,int))"/>
<!-- 配置切面 ref: 使用哪个实例对切面(事务)进行增强 -->
<aop:aspect ref="transactionManager">
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="transfer" />
</aop:aspect>
</aop:config>
</beans>
7. 测试类
import com.bjpowernode.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration("classpath:applicationContext.xml") // 指定配置文件的所在路径
@RunWith(SpringJUnit4ClassRunner.class) // 指定使用spring编写的增强的junit类运行代码
public class Tester {
@Autowired
AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.get(1));
}
@Test
public void testUpdateMoney(){
accountService.transfer("tom","jack",100); // tom向jack转移100
}
}