一、转账案例原代码
1.项目目录
2.各个文件代码
(1)AccountDao接口
package com.june.dao;
public interface AccountDao {
//转出操作
public void out(String outUser,Double money);
//转入操作
public void in(String inUser,Double money);
}
(2)AccountDaoImpl类
package com.june.dao.impl;
import com.june.dao.AccountDao;
import com.june.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
@Repository("accountDao") //生成该类实例存放到IOC容器中
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;
@Autowired
private ConnectionUtils connectionUtils;
/**
* 转出操作
*/
@Override
public void out(String outUser, Double money) {
String sql="update account set money=money - ? where name = ?";
try {
queryRunner.update(connectionUtils.getThreadConnection(),sql,money,outUser);
//获取到的connection其实就是开启事务时,绑定到线程上的connection
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 转入操作
*/
@Override
public void in(String inUser, Double money) {
String sql="update account set money=money + ? where name = ?";
try {
queryRunner.update(connectionUtils.getThreadConnection(),sql,money,inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(3)Account类
package com.june.domain;
public class Account {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer 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 +
'}';
}
}
(4)CglibProxyFactory类
package com.june.proxy;
import com.june.service.AccountService;
import com.june.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* Cglib动态代理工厂类,对目标类进行方法的动态增强(添加上事务控制)
* 目标类:AccountServiceImpl
* 目标类父类:AccountService
* 目标方法:transfer()
*/
@Component
public class CglibProxyFactory {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
public AccountService createAccountServiceCglibProxy(){
//编写cglib对应的API来生成代理对象进行返回
/**
* 参数1:目标类的字节码对象
* 参数2:动作类,当代理对象调用目标对象中的原方法时,那么会执行intercept()方法
*/
//accountServiceProxy是生成的代理对象
AccountService accountServiceProxy = (AccountService)Enhancer.create(accountService.getClass(), new MethodInterceptor() {
/**
*
* @param o:生成的代理对象
* @param method:被调用的目标方法的引用
* @param objects:被调用的目标方法所用到的参数
* @param methodProxy:代理方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try {
//手动开启事务:调用事务管理器中的开启事务方法
transactionManager.beginTransaction();
//让被代理对象的原方法执行
method.invoke(accountService, objects);
//手动提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
//手动回滚事务
transactionManager.rollback();
} finally {
//手动释放资源
transactionManager.release();
}
return null;
}
});
return accountServiceProxy;
}
}
(5)JDKProxyFactory类
package com.june.proxy;
import com.june.service.AccountService;
import com.june.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK动态代理工厂类,对目标类进行方法的动态增强
* 目标类:AccountServiceImpl
* 目标接口:AccountService
*/
@Component
public class JDKProxyFactory {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
/**
* 采用JDK动态代理技术来生成目标类(被代理类)的代理对象
*/
public AccountService createAccountServiceJdkProxy(){
/* ClassLoader loader:类加载器,借助被代理对象获取到类加载器
Class<?>[] interfaces:被代理类所需要实现的全部接口
InvocationHandler h:当代理对象调用接口中的任意方法时,都会执行InvocationHandler中的invoke()方法(所以可以在invoke()方法里完成一些动态增强)
*/
//accountServiceProxy是生成的代理对象
AccountService accountServiceProxy = (AccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
/**
* @param proxy :当前的代理对象引用
* @param method:被调用的目标方法的引用
* @param args:被调用的目标方法所用到的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//若是transfer()方法,则进行增强;若不是,则只执行原方法
if (method.getName().equals("transfer")){//获得被调用方法的名称
System.out.println("进行了前置增强");
//手动开启事务:调用事务管理器中的开启事务方法
transactionManager.beginTransaction();
//让被代理对象的原方法执行
method.invoke(accountService, args);
System.out.println("进行了后置增强");
//手动提交事务
transactionManager.commit();
}else {
//让被代理对象的原方法执行
method.invoke(accountService, args);
}
} catch (Exception e) {
e.printStackTrace();
//手动回滚事务
transactionManager.rollback();
}finally {
//手动释放资源
transactionManager.release();
}
return null;
}
});
return accountServiceProxy;
}
}
(6)AccountService接口
package com.june.service;
public interface AccountService {
//转账方法
public void transfer(String outUser,String inUser,Double money);
public void save();
public void update();
public void delete();
}
(7)AccountServiceImpl类
package com.june.service.impl;
import com.june.dao.AccountDao;
import com.june.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账方法
*/
@Override
public void transfer(String outUser, String inUser, Double money) {
//调用减钱方法
accountDao.out(outUser,money);
//int i=1/0;
//调用加钱方法
accountDao.in(inUser,money);
}
@Override
public void save() {
System.out.println("save方法");
}
@Override
public void update() {
System.out.println("update方法");
}
@Override
public void delete() {
System.out.println("delete方法");
}
}
(8)AccountServiceTest类
package com.june.test;
import com.june.proxy.CglibProxyFactory;
import com.june.proxy.JDKProxyFactory;
import com.june.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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Autowired
private JDKProxyFactory proxyFactory;
@Autowired
private CglibProxyFactory cglibProxyFactory;
@Test
public void testTransfer(){
accountService.transfer("tom","jerry",100d);
}
/**
* 测试JDK动态代理优化转账案例
*/
@Test
public void testTransferProxyJDK(){
//当前返回的实际上是AccountService的代理对象proxy
AccountService accountServiceJdkProxy = proxyFactory.createAccountServiceJdkProxy();
//代理对象proxy调用接口中的任意方法时,都会执行底层的invoke()方法
//accountServiceJdkProxy.transfer("tom","jerry",100d);
accountServiceJdkProxy.save();
}
/**
* 测试CGLIB动态代理优化转账案例
*/
@Test
public void testTransferProxyCglib(){
//当前返回的实际上是AccountService的代理对象proxy
AccountService accountServiceJdkProxy = cglibProxyFactory.createAccountServiceCglibProxy();
//代理对象proxy调用接口中的任意方法时,都会执行底层的invoke()方法
accountServiceJdkProxy.transfer("tom","jerry",100d);
}
}
(9)ConnectionUtils类
package com.june.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 连接工具类:从数据源中国获取一个连接,并且将获取到的连接与线程进行绑定
* ThreadLocal:线程内部的存储类,可以在指定的线程内存储数据 key:threadLocal(当前线程) value:任意类型的值(Connection)
* 保证dao层在执行多个方法时,所用到的connection是同一个,借助ThreadLocal
*/
@Component //生成该类的实例对象,存到IOC容器中
public class ConnectionUtils {
@Autowired
private DataSource dataSource;
private ThreadLocal<Connection> threadLocal=new ThreadLocal<>();
/**
* 获取当前线程上绑定的连接
* 如果获取到的连接为空,那么就要从数据源中获取连接,并且放到ThreadLocal中(绑定到当前线程)
*/
public Connection getThreadConnection(){
//1.先从ThreadLocal上获取连接
Connection connection=threadLocal.get();
//2.判断当前线程中是否有Connection
if (connection==null){
//3.从数据源中获取一个连接,并且存入ThreadLocal中
try {
connection=dataSource.getConnection();//这时就不为空了
threadLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
/**
* 解除当前线程的连接绑定
*/
public void removeThreadConnection(){
threadLocal.remove();
}
}
(10)TransactionManager类
package com.june.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 事务管理器工具类
* 包含:开启事务,提交事务,回滚事务,释放资源(所用到的connection对象应该是同一个)
*/
@Component
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
/**
* 开启事务
*/
public void beginTransaction(){
Connection connection = connectionUtils.getThreadConnection();
try {
connection.setAutoCommit(false);//开启了一个手动事务:把自动提交事务改为手动提交
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
Connection connection = connectionUtils.getThreadConnection();//虽然和上面beginTransaction()方法都用到了getThreadConnection()方法,但是获取到的连接是同一个
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
Connection connection = connectionUtils.getThreadConnection();//
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 释放资源
*/
public void release(){
Connection connection = connectionUtils.getThreadConnection();
try {
//1.将手动事务改回自动提交事务
connection.setAutoCommit(true);
//2.将连接归还到连接池
connectionUtils.getThreadConnection().close();
//3.解除线程绑定
connectionUtils.removeThreadConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(11)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">
<!--开启注解扫描-->
<context:component-scan base-package="com.june"></context:component-scan>
<!--引入properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--配置DataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
</beans>
(12)jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_db
jdbc.username=root
jdbc.password=123456
(13)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>com.june</groupId>
<artifactId>spring_transfer</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.15</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
3.测试
测试时执行AccountServiceTest类中的testTransferProxyJDK()方法和testTransferProxyCglib()方法
在AccountServiceImpl类中的transfer()方法中取消注释int i=1/0;
也可以观察到回滚的有效性
二、转账案例AOP优化——xml方式优化(完整代码)
前言:依然使用前面的转账案例,将两个代理工厂对象直接删除!改为spring的aop思想来实现
思想:确定切入点、通知类&通知方法,然后在核心配置文件applicationContext.xml中借助标签来完成切面配置
改变:在一、的基础上,主要改变了applicationContext.xml,小改TransactionManager类,在测试类中增加测试方法,在pom.xml中新增依赖
1.项目目录
2.各个文件代码
(1)AccountDao接口
package com.june.dao;
public interface AccountDao {
//转出操作
public void out(String outUser,Double money);
//转入操作
public void in(String inUser,Double money);
}
(2)AccountDaoImpl类
package com.june.dao.impl;
import com.june.dao.AccountDao;
import com.june.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
@Repository("accountDao") //生成该类实例存放到IOC容器中
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;
@Autowired
private ConnectionUtils connectionUtils;
/**
* 转出操作
*/
@Override
public void out(String outUser, Double money) {
String sql="update account set money=money - ? where name = ?";
try {
queryRunner.update(connectionUtils.getThreadConnection(),sql,money,outUser);
//获取到的connection其实就是开启事务时,绑定到线程上的connection
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 转入操作
*/
@Override
public void in(String inUser, Double money) {
String sql="update account set money=money + ? where name = ?";
try {
queryRunner.update(connectionUtils.getThreadConnection(),sql,money,inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(3)Account类
package com.june.domain;
public class Account {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer 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 +
'}';
}
}
(4)CglibProxyFactory类
package com.june.proxy;
import com.june.service.AccountService;
import com.june.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* Cglib动态代理工厂类,对目标类进行方法的动态增强(添加上事务控制)
* 目标类:AccountServiceImpl
* 目标类父类:AccountService
* 目标方法:transfer()
*/
@Component
public class CglibProxyFactory {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
public AccountService createAccountServiceCglibProxy(){
//编写cglib对应的API来生成代理对象进行返回
/**
* 参数1:目标类的字节码对象
* 参数2:动作类,当代理对象调用目标对象中的原方法时,那么会执行intercept()方法
*/
//accountServiceProxy是生成的代理对象
AccountService accountServiceProxy = (AccountService)Enhancer.create(accountService.getClass(), new MethodInterceptor() {
/**
*
* @param o:生成的代理对象
* @param method:被调用的目标方法的引用
* @param objects:被调用的目标方法所用到的参数
* @param methodProxy:代理方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try {
//手动开启事务:调用事务管理器中的开启事务方法
transactionManager.beginTransaction();
//让被代理对象的原方法执行
method.invoke(accountService, objects);
//手动提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
//手动回滚事务
transactionManager.rollback();
} finally {
//手动释放资源
transactionManager.release();
}
return null;
}
});
return accountServiceProxy;
}
}
(5)JDKProxyFactory类
package com.june.proxy;
import com.june.service.AccountService;
import com.june.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK动态代理工厂类,对目标类进行方法的动态增强
* 目标类:AccountServiceImpl
* 目标接口:AccountService
*/
@Component
public class JDKProxyFactory {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
/**
* 采用JDK动态代理技术来生成目标类(被代理类)的代理对象
*/
public AccountService createAccountServiceJdkProxy(){
/* ClassLoader loader:类加载器,借助被代理对象获取到类加载器
Class<?>[] interfaces:被代理类所需要实现的全部接口
InvocationHandler h:当代理对象调用接口中的任意方法时,都会执行InvocationHandler中的invoke()方法(所以可以在invoke()方法里完成一些动态增强)
*/
//accountServiceProxy是生成的代理对象
AccountService accountServiceProxy = (AccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
/**
* @param proxy :当前的代理对象引用
* @param method:被调用的目标方法的引用
* @param args:被调用的目标方法所用到的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//若是transfer()方法,则进行增强;若不是,则只执行原方法
if (method.getName().equals("transfer")){//获得被调用方法的名称
System.out.println("进行了前置增强");
//手动开启事务:调用事务管理器中的开启事务方法
transactionManager.beginTransaction();
//让被代理对象的原方法执行
method.invoke(accountService, args);
System.out.println("进行了后置增强");
//手动提交事务
transactionManager.commit();
}else {
//让被代理对象的原方法执行
method.invoke(accountService, args);
}
} catch (Exception e) {
e.printStackTrace();
//手动回滚事务
transactionManager.rollback();
}finally {
//手动释放资源
transactionManager.release();
}
return null;
}
});
return accountServiceProxy;
}
}
(6)AccountService接口
package com.june.service;
public interface AccountService {
//转账方法
public void transfer(String outUser,String inUser,Double money);
public void save();
public void update();
public void delete();
}
(7)AccountServiceImpl类
package com.june.service.impl;
import com.june.dao.AccountDao;
import com.june.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账方法
* 切入点,添加上事务控制的效果
*/
@Override
public void transfer(String outUser, String inUser, Double money) {
//调用减钱方法
accountDao.out(outUser,money);
int i=1/0;
//调用加钱方法
accountDao.in(inUser,money);
}
@Override
public void save() {
System.out.println("save方法");
}
@Override
public void update() {
System.out.println("update方法");
}
@Override
public void delete() {
System.out.println("delete方法");
}
}
(8)AccountServiceTest类
package com.june.test;
import com.june.proxy.CglibProxyFactory;
import com.june.proxy.JDKProxyFactory;
import com.june.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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Autowired
private JDKProxyFactory proxyFactory;
@Autowired
private CglibProxyFactory cglibProxyFactory;
@Test
public void testTransfer(){
accountService.transfer("tom","jerry",100d);
}
/**
* 测试JDK动态代理优化转账案例
*/
@Test
public void testTransferProxyJDK(){
//当前返回的实际上是AccountService的代理对象proxy
AccountService accountServiceJdkProxy = proxyFactory.createAccountServiceJdkProxy();
//代理对象proxy调用接口中的任意方法时,都会执行底层的invoke()方法
//accountServiceJdkProxy.transfer("tom","jerry",100d);
accountServiceJdkProxy.save();
}
/**
* 测试CGLIB动态代理优化转账案例
*/
@Test
public void testTransferProxyCglib(){
//当前返回的实际上是AccountService的代理对象proxy
AccountService accountServiceJdkProxy = cglibProxyFactory.createAccountServiceCglibProxy();
//代理对象proxy调用接口中的任意方法时,都会执行底层的invoke()方法
accountServiceJdkProxy.transfer("tom","jerry",100d);
}
}
(9)ConnectionUtils类
package com.june.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 连接工具类:从数据源中国获取一个连接,并且将获取到的连接与线程进行绑定
* ThreadLocal:线程内部的存储类,可以在指定的线程内存储数据 key:threadLocal(当前线程) value:任意类型的值(Connection)
* 保证dao层在执行多个方法时,所用到的connection是同一个,借助ThreadLocal
*/
@Component //生成该类的实例对象,存到IOC容器中
public class ConnectionUtils {
@Autowired
private DataSource dataSource;
private ThreadLocal<Connection> threadLocal=new ThreadLocal<>();
/**
* 获取当前线程上绑定的连接
* 如果获取到的连接为空,那么就要从数据源中获取连接,并且放到ThreadLocal中(绑定到当前线程)
*/
public Connection getThreadConnection(){
//1.先从ThreadLocal上获取连接
Connection connection=threadLocal.get();
//2.判断当前线程中是否有Connection
if (connection==null){
//3.从数据源中获取一个连接,并且存入ThreadLocal中
try {
connection=dataSource.getConnection();//这时就不为空了
threadLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
/**
* 解除当前线程的连接绑定
*/
public void removeThreadConnection(){
threadLocal.remove();
}
}
(10)TransactionManager类
package com.june.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 事务管理器工具类
* 包含:开启事务,提交事务,回滚事务,释放资源(所用到的connection对象应该是同一个)
* 通知类,下面的方法是通知方法
*/
@Component("transactionManager")
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
/**
* 开启事务
*/
public void beginTransaction(){
Connection connection = connectionUtils.getThreadConnection();
try {
connection.setAutoCommit(false);//开启了一个手动事务:把自动提交事务改为手动提交
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
Connection connection = connectionUtils.getThreadConnection();//虽然和上面beginTransaction()方法都用到了getThreadConnection()方法,但是获取到的连接是同一个
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
Connection connection = connectionUtils.getThreadConnection();//
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 释放资源
*/
public void release(){
Connection connection = connectionUtils.getThreadConnection();
try {
//1.将手动事务改回自动提交事务
connection.setAutoCommit(true);
//2.将连接归还到连接池
connectionUtils.getThreadConnection().close();
//3.解除线程绑定
connectionUtils.removeThreadConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(11)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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--开启注解扫描-->
<context:component-scan base-package="com.june"></context:component-scan>
<!--引入properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--配置DataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--AOP配置-->
<aop:config>
<!--1.切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(* com.june.service.impl.AccountServiceImpl.*(..))"/>
<!--2.切面配置-->
<aop:aspect ref="transactionManager">
<aop:before method="beginTransaction" pointcut-ref="myPointcut"></aop:before>
<aop:after-returning method="commit" pointcut-ref="myPointcut"></aop:after-returning>
<aop:after-throwing method="rollback" pointcut-ref="myPointcut"></aop:after-throwing>
<aop:after method="release" pointcut-ref="myPointcut"></aop:after>
</aop:aspect>
</aop:config>
</beans>
(12)jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_db
jdbc.username=root
jdbc.password=123456
(13)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>com.june</groupId>
<artifactId>spring_transfer</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.15</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- aspectj的织入(切点表达式需要用到该jar包) -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
</project>
3.测试
在applicationContext.xml中借助标签来完成切面配置后,在AccountServiceTest类中注入AccountService时,就注入了由spring自动产生的代理对象,这个代理对象去调用transfer()方法时,就会完成对应的增强,即测试时执行AccountServiceTest类中的testTransfer()方法
在AccountServiceImpl类中的transfer()方法中取消注释int i=1/0;
也可以观察到回滚的有效性
三、转账案例AOP优化——注解方式优化(完整代码)
前言:依然使用前面的转账案例,将两个代理工厂对象直接删除!改为spring的aop思想来实现
思想:确定切入点、通知类&通知方法,然后在核心配置文件applicationContext.xml中借助标签来完成切面配置
主要改变了applicationContext.xml,小改TransactionManager类,在测试类中增加测试方法,在pom.xml中新增依赖
思想:找到通知类添加注解,在切面类中完成切入点+通知的结合
改变:在二、的基础上,在applicationContext.xml中不再需要AOP配置,但要开启AOP的自动代理,改变TransactionManager类
1.项目目录
2.各个文件代码
(1)AccountDao接口
package com.june.dao;
public interface AccountDao {
//转出操作
public void out(String outUser,Double money);
//转入操作
public void in(String inUser,Double money);
}
(2)AccountDaoImpl类
package com.june.dao.impl;
import com.june.dao.AccountDao;
import com.june.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
@Repository("accountDao") //生成该类实例存放到IOC容器中
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;
@Autowired
private ConnectionUtils connectionUtils;
/**
* 转出操作
*/
@Override
public void out(String outUser, Double money) {
String sql="update account set money=money - ? where name = ?";
try {
queryRunner.update(connectionUtils.getThreadConnection(),sql,money,outUser);
//获取到的connection其实就是开启事务时,绑定到线程上的connection
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 转入操作
*/
@Override
public void in(String inUser, Double money) {
String sql="update account set money=money + ? where name = ?";
try {
queryRunner.update(connectionUtils.getThreadConnection(),sql,money,inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(3)Account类
package com.june.domain;
public class Account {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer 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 +
'}';
}
}
(4)CglibProxyFactory类
package com.june.proxy;
import com.june.service.AccountService;
import com.june.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* Cglib动态代理工厂类,对目标类进行方法的动态增强(添加上事务控制)
* 目标类:AccountServiceImpl
* 目标类父类:AccountService
* 目标方法:transfer()
*/
@Component
public class CglibProxyFactory {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
public AccountService createAccountServiceCglibProxy(){
//编写cglib对应的API来生成代理对象进行返回
/**
* 参数1:目标类的字节码对象
* 参数2:动作类,当代理对象调用目标对象中的原方法时,那么会执行intercept()方法
*/
//accountServiceProxy是生成的代理对象
AccountService accountServiceProxy = (AccountService)Enhancer.create(accountService.getClass(), new MethodInterceptor() {
/**
*
* @param o:生成的代理对象
* @param method:被调用的目标方法的引用
* @param objects:被调用的目标方法所用到的参数
* @param methodProxy:代理方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try {
//手动开启事务:调用事务管理器中的开启事务方法
transactionManager.beginTransaction();
//让被代理对象的原方法执行
method.invoke(accountService, objects);
//手动提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
//手动回滚事务
transactionManager.rollback();
} finally {
//手动释放资源
transactionManager.release();
}
return null;
}
});
return accountServiceProxy;
}
}
(5)JDKProxyFactory类
package com.june.proxy;
import com.june.service.AccountService;
import com.june.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK动态代理工厂类,对目标类进行方法的动态增强
* 目标类:AccountServiceImpl
* 目标接口:AccountService
*/
@Component
public class JDKProxyFactory {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
/**
* 采用JDK动态代理技术来生成目标类(被代理类)的代理对象
*/
public AccountService createAccountServiceJdkProxy(){
/* ClassLoader loader:类加载器,借助被代理对象获取到类加载器
Class<?>[] interfaces:被代理类所需要实现的全部接口
InvocationHandler h:当代理对象调用接口中的任意方法时,都会执行InvocationHandler中的invoke()方法(所以可以在invoke()方法里完成一些动态增强)
*/
//accountServiceProxy是生成的代理对象
AccountService accountServiceProxy = (AccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
/**
* @param proxy :当前的代理对象引用
* @param method:被调用的目标方法的引用
* @param args:被调用的目标方法所用到的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//若是transfer()方法,则进行增强;若不是,则只执行原方法
if (method.getName().equals("transfer")){//获得被调用方法的名称
System.out.println("进行了前置增强");
//手动开启事务:调用事务管理器中的开启事务方法
transactionManager.beginTransaction();
//让被代理对象的原方法执行
method.invoke(accountService, args);
System.out.println("进行了后置增强");
//手动提交事务
transactionManager.commit();
}else {
//让被代理对象的原方法执行
method.invoke(accountService, args);
}
} catch (Exception e) {
e.printStackTrace();
//手动回滚事务
transactionManager.rollback();
}finally {
//手动释放资源
transactionManager.release();
}
return null;
}
});
return accountServiceProxy;
}
}
(6)AccountService接口
package com.june.service;
public interface AccountService {
//转账方法
public void transfer(String outUser,String inUser,Double money);
public void save();
public void update();
public void delete();
}
(7)AccountServiceImpl类
package com.june.service.impl;
import com.june.dao.AccountDao;
import com.june.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账方法
* 切入点,添加上事务控制的效果
*/
@Override
public void transfer(String outUser, String inUser, Double money) {
//调用减钱方法
accountDao.out(outUser,money);
int i=1/0;
//调用加钱方法
accountDao.in(inUser,money);
}
@Override
public void save() {
System.out.println("save方法");
}
@Override
public void update() {
System.out.println("update方法");
}
@Override
public void delete() {
System.out.println("delete方法");
}
}
(8)AccountServiceTest类
package com.june.test;
import com.june.proxy.CglibProxyFactory;
import com.june.proxy.JDKProxyFactory;
import com.june.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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Autowired
private JDKProxyFactory proxyFactory;
@Autowired
private CglibProxyFactory cglibProxyFactory;
@Test
public void testTransfer(){
accountService.transfer("tom","jerry",100d);
}
/**
* 测试JDK动态代理优化转账案例
*/
@Test
public void testTransferProxyJDK(){
//当前返回的实际上是AccountService的代理对象proxy
AccountService accountServiceJdkProxy = proxyFactory.createAccountServiceJdkProxy();
//代理对象proxy调用接口中的任意方法时,都会执行底层的invoke()方法
//accountServiceJdkProxy.transfer("tom","jerry",100d);
accountServiceJdkProxy.save();
}
/**
* 测试CGLIB动态代理优化转账案例
*/
@Test
public void testTransferProxyCglib(){
//当前返回的实际上是AccountService的代理对象proxy
AccountService accountServiceJdkProxy = cglibProxyFactory.createAccountServiceCglibProxy();
//代理对象proxy调用接口中的任意方法时,都会执行底层的invoke()方法
accountServiceJdkProxy.transfer("tom","jerry",100d);
}
}
(9)ConnectionUtils类
package com.june.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 连接工具类:从数据源中国获取一个连接,并且将获取到的连接与线程进行绑定
* ThreadLocal:线程内部的存储类,可以在指定的线程内存储数据 key:threadLocal(当前线程) value:任意类型的值(Connection)
* 保证dao层在执行多个方法时,所用到的connection是同一个,借助ThreadLocal
*/
@Component //生成该类的实例对象,存到IOC容器中
public class ConnectionUtils {
@Autowired
private DataSource dataSource;
private ThreadLocal<Connection> threadLocal=new ThreadLocal<>();
/**
* 获取当前线程上绑定的连接
* 如果获取到的连接为空,那么就要从数据源中获取连接,并且放到ThreadLocal中(绑定到当前线程)
*/
public Connection getThreadConnection(){
//1.先从ThreadLocal上获取连接
Connection connection=threadLocal.get();
//2.判断当前线程中是否有Connection
if (connection==null){
//3.从数据源中获取一个连接,并且存入ThreadLocal中
try {
connection=dataSource.getConnection();//这时就不为空了
threadLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
/**
* 解除当前线程的连接绑定
*/
public void removeThreadConnection(){
threadLocal.remove();
}
}
(10)TransactionManager类
package com.june.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 事务管理器工具类
* 包含:开启事务,提交事务,回滚事务,释放资源(所用到的connection对象应该是同一个)
* 通知类,下面的方法是通知方法
*/
@Component("transactionManager")
@Aspect //表明该类为切面类
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
@Around("execution(* com.june.service.impl.AccountServiceImpl.*(..))")
public Object around(ProceedingJoinPoint pjp) throws SQLException {
Object proceed=null;
try {
//开启手动事务
connectionUtils.getThreadConnection().setAutoCommit(false);
//切入点方法执行
proceed = pjp.proceed();//相当于调用原方法
//手动提交事务
connectionUtils.getThreadConnection().commit();
} catch (Throwable e) {
e.printStackTrace();
//手动回滚事务
connectionUtils.getThreadConnection().rollback();
}finally {
//1.将手动事务改回自动提交事务
connectionUtils.getThreadConnection().setAutoCommit(true);
//2.将连接归还到连接池
connectionUtils.getThreadConnection().close();
//3.解除线程绑定
connectionUtils.removeThreadConnection();
}
return null;
}
/**
* 开启事务
*/
public void beginTransaction(){
Connection connection = connectionUtils.getThreadConnection();
try {
connection.setAutoCommit(false);//开启了一个手动事务:把自动提交事务改为手动提交
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
Connection connection = connectionUtils.getThreadConnection();//虽然和上面beginTransaction()方法都用到了getThreadConnection()方法,但是获取到的连接是同一个
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
Connection connection = connectionUtils.getThreadConnection();
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 释放资源
*/
public void release(){
Connection connection = connectionUtils.getThreadConnection();
try {
//1.将手动事务改回自动提交事务
connection.setAutoCommit(true);
//2.将连接归还到连接池
connectionUtils.getThreadConnection().close();
//3.解除线程绑定
connectionUtils.removeThreadConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(11)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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--开启注解扫描-->
<context:component-scan base-package="com.june"></context:component-scan>
<!--引入properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--配置DataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--AOP配置-->
<!-- <aop:config>
<!–1.切点表达式–>
<aop:pointcut id="myPointcut" expression="execution(* com.june.service.impl.AccountServiceImpl.*(..))"/>
<!–2.切面配置–>
<aop:aspect ref="transactionManager">
<aop:before method="beginTransaction" pointcut-ref="myPointcut"></aop:before>
<aop:after-returning method="commit" pointcut-ref="myPointcut"></aop:after-returning>
<aop:after-throwing method="rollback" pointcut-ref="myPointcut"></aop:after-throwing>
<aop:after method="release" pointcut-ref="myPointcut"></aop:after>
</aop:aspect>
</aop:config>-->
<!--开启AOP的自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
(12)jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_db
jdbc.username=root
jdbc.password=123456
(13)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>com.june</groupId>
<artifactId>spring_transfer</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.15</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- aspectj的织入(切点表达式需要用到该jar包) -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
</project>
3.测试
测试时执行AccountServiceTest类中的testTransfer()方法
在AccountServiceImpl类中的transfer()方法中取消注释int i=1/0;
也可以观察到回滚的有效性