一、概述
- 問題
- 完善user案例
- 分析案例中的問題
- 動態代理技術
- 動態代理的另一種實現方式
- 解決案例中的問題
- AOP概念
- spring中AOP的相關術語
- spring中基於XML和註解的AOP配置
一、問題
1.1 業務場景
在轉賬的業務中,需要保證轉出方扣錢成功,同時要保證轉入方收到相同金額的錢,這就要求整個轉賬的過程在同一個事務中
- 業務層代碼
public class UserServiceImpl implements UserService {
private UserDao userDao;
private TransactionManager txManager;
// 使用set注入dao
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
@Override
public int addOne(User user) {
try {
// 1. 開啓事務
txManager.start();
// 2. 執行操作
int count = userDao.addOne(user);
// 3. 提交事務
txManager.commit();
// 4. 返回數據
return count;
} catch (Exception e) {
// 5. 異常回滾
txManager.rollBack();
} finally {
// 6. 結束事務
txManager.release();
}
return 0;
}
@Override
public int deleteOne(int id) {
try {
// 1. 開啓事務
txManager.start();
// 2. 執行操作
int count = userDao.deleteOne(id);
// 3. 提交事務
txManager.commit();
// 4. 返回數據
return count;
} catch (Exception e) {
// 5. 異常回滾
txManager.rollBack();
} finally {
// 6. 結束事務
txManager.release();
}
return 0;
}
@Override
public int updateOne(User user) {
try {
// 1. 開啓事務
txManager.start();
// 2. 執行操作
int count = userDao.updateOne(user);
// 3. 提交事務
txManager.commit();
// 4. 返回數據
return count;
} catch (Exception e) {
// 5. 異常回滾
txManager.rollBack();
} finally {
// 6. 結束事務
txManager.release();
}
return 0;
}
@Override
public User findOne(int id) {
try {
// 1. 開啓事務
txManager.start();
// 2. 執行操作
User user = userDao.findOne(id);
// 3. 提交事務
txManager.commit();
// 4. 返回結果
return user;
} catch (Exception e) {
// 5. 異常回滾
txManager.rollBack();
} finally {
// 6. 結束事務
txManager.release();
}
return null;
}
@Override
public List<User> findAll() {
try {
// 1. 開啓事務
txManager.start();
// 2. 執行操作
List<User> list = userDao.findAll();
// 3. 提交事務
txManager.commit();
// 4. 返回結果
return list;
} catch (Exception e) {
// 5. 異常回滾
txManager.rollBack();
} finally {
// 6. 結束事務
txManager.release();
}
return null;
}
/**
* 轉賬操作
* @author: [email protected]
* @date: 2020/3/20 11:36 下午
* @param originAccount
* @param targetAccount
* @param money
* @return int
*/
@Override
public int transferMoney(Integer originAccount, Integer targetAccount, Double money) {
try {
// 1. 開啓事務
txManager.start();
// 2. 執行操作
// 2.1 查出轉出賬戶
User originUser = userDao.findOne(originAccount);
// 2.2 查出站如賬戶
User targetUser = userDao.findOne(targetAccount);
// 2.3 轉出賬戶減少金額
double originMoney = originUser.getMoney() - money;
originUser.setMoney(originMoney);
// 2.4 轉入賬戶增加金額
double targetMoney = targetUser.getMoney() + money;
targetUser.setMoney(targetMoney);
// 2.5 更新轉出賬戶
userDao.updateOne(originUser);
int num = 1/0;
// 2.6 更新轉入賬戶
userDao.updateOne(targetUser);
// 3. 提交事務
txManager.commit();
// 4. 返回結果
return 1;
} catch (Exception e) {
// 5. 異常回滾
txManager.rollBack();
e.printStackTrace();
} finally {
// 6. 結束事務
txManager.release();
}
return 0;
}
}
- 問:事務控制爲什麼需要加到業務層?
- 答:通常一個業務需要連續多次操作數據庫,如果中間某一次操作數據庫的時候發生了異常,整個業務沒有回滾就提交了部分操作,就會發生異常;比如最常見的轉賬業務,如果轉出方轉出成功,而轉入的時候發生了異常,就會造成轉入方沒有收到轉賬,從而造成金額損失
- beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置service -->
<bean id="userService" class="com.lizza.service.impl.UserServiceImpl">
<!-- 注入Dao -->
<property name="userDao" ref="userDao"></property>
<property name="txManager" ref="txManager"></property>
</bean>
<!-- 配置事務管理器 -->
<bean name="txManager" class="com.lizza.util.TransactionManager">
<property name="jdbcUtil" ref="jdbcUtil"></property>
</bean>
<!-- 配置dao -->
<bean id="userDao" class="com.lizza.dao.impl.UserDaoImpl">
<!-- 注入QueryRunner -->
<property name="queryRunner" ref="queryRunner"></property>
<property name="jdbcUtil" ref="jdbcUtil"></property>
</bean>
<!-- 配置JdbcUtil -->
<bean id="jdbcUtil" class="com.lizza.util.JdbcUtil">
<!-- 注入數據源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置QueryRunner;QueryRunner需要配置成多例模式,避免發生線程安全問題 -->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置數據源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3360/spring"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
1.2 存在問題
- 代碼大量重複:每一個涉及到事務的控制都需要增加事務控制的代碼
- 代碼重複導致耦合度增大,開發維護成本升高:新的人接手代碼時,需要更多的時間去了解與業務無關的事務控制的代碼
1.3 解決方案
靜態代理,動態代理(本案例選擇動態代理)
二、動態代理
2.1 概述
動態的在運行期創建某個類或者接口的代理類
2.2 分類
- JDK動態代理
- cglib動態代理
類型 | 特點 | 使用方式 |
---|---|---|
JDK動態代理 | 基於接口的動態代理 | 使用Proxy的newProxyInstance()方法創建代理對象 |
cglib動態代理 | 基於類的動態代理 | 使用Enhancer類中的create()方法創建代理對象 |
2.3 JDK動態代理示例
public class Client {
/**
* JDK動態代理
* 定義:可以在運行期動態創建某個interface的實例
* 作用:在不修改源碼的基礎上實現對方法的增強
* 特點:需要接口,並且實現類的實例
* 分類:
* 1. 基於接口的動態代理
* 2. 基於子類的動態代理
* 基於接口的動態代理
* 涉及的類:Proxy
* 提供者:JDK官方
* 創建動態代理的要求
* 被代理的類最少實現一個接口,如果沒有則不能使用
* newProxyInstance方法的參數
* ClassLoader:類加載器
* 用於加載代理對象的字節碼;與被代理對象使用相同的類加載器;固定寫法
* Class<?>[]:字節碼數組
*
* InvocationHandler:用於提供增強的代碼
* 用於實現增強的代碼;通常情況下是匿名內部類;非必需
*/
public static void main(String[] args){
Producer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:執行被代理對象的任何接口方法時都會執行該方法
* @author: [email protected]
* @date: 2020/3/23 9:19 下午
* @param proxy 代理對象的引用
* @param method 當前執行的方法
* @param args 當前執行方法所需的參數
* @return 和被代理對象具有相同的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 獲取傳入的參數
if("sale".equals(method.getName())) {
return method.invoke(producer, (float) args[0] * 0.8f);
}
return method.invoke(producer, args);
}
}
);
proxyProducer.sale(10000);
}
}
2.4 cglib動態代理示例
public class Client {
/**
* cglib動態代理
* 定義:可以在運行期動態創建某個class的實例
* 作用:在不修改源碼的基礎上實現對方法的增強
* 特點:對指定類進行代理
* 分類:
* 1. 基於接口的動態代理
* 2. 基於子類的動態代理
* 基於接口的動態代理
* 涉及的類:Proxy
* 提供者:第三方cglib庫
* 創建動態代理的要求
* 被代理的類不能用final修飾
* 如何創建代理對象
* 使用Enhancer類中的create方法
* create方法的參數
* ClassLoader:類加載器
* 用於加載代理對象的字節碼;與被代理對象使用相同的類加載器;固定寫法
* Class<?>[]:字節碼數組
*
* Callback:用於提供增強的代碼
* 用於實現增強的代碼;通常情況下是匿名內部類;非必需
* 一般創建MethodInterceptor
*/
public static void main(String[] args){
Producer producer = new Producer();
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), producer.getClass().getInterfaces(), new MethodInterceptor() {
/**
* 執行被代理對象的任何方法都會經過該方法
* @author: [email protected]
* @date: 2020/3/25 8:21 下午
* @param proxy
* @param method
* @param args
* @param methodProxy
* @return java.lang.Object
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 1. 獲取傳入的參數
if("sale".equals(method.getName())) {
return method.invoke(producer, (float) args[0] * 0.8f);
}
return method.invoke(producer, args);
}
});
cglibProducer.sale(12000);
}
}
三、用動態代理解決問題
3.1 代碼示例
- 創建動態代理對象並增加事務控制
public class BeanFactory {
private UserService userService;
private TransactionManager txManager;
public UserService getUserService() {
return (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
txManager.start();
result = method.invoke(userService, args);
txManager.commit();
} catch (Exception e) {
txManager.rollBack();
e.printStackTrace();
} finally {
txManager.release();
}
return result;
}
});
}
public void setUserService(UserService userService) {
this.userService = userService;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
}
- 配置beans.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置帶事務控制的service -->
<bean id="beanFactory" class="com.lizza.factory.BeanFactory">
<property name="txManager" ref="txManager"></property>
<property name="userService" ref="userService"></property>
</bean>
<bean id="proxyUserService" factory-bean="beanFactory" factory-method="getUserService"></bean>
<!-- 配置service -->
<bean id="userService" class="com.lizza.service.impl.UserServiceImpl">
<!-- 注入Dao -->
<property name="userDao" ref="userDao"></property>
</bean>
<!-- 配置事務管理器 -->
<bean name="txManager" class="com.lizza.util.TransactionManager">
<property name="jdbcUtil" ref="jdbcUtil"></property>
</bean>
<!-- 配置dao -->
<bean id="userDao" class="com.lizza.dao.impl.UserDaoImpl">
<!-- 注入QueryRunner -->
<property name="queryRunner" ref="queryRunner"></property>
<property name="jdbcUtil" ref="jdbcUtil"></property>
</bean>
<!-- 配置JdbcUtil -->
<bean id="jdbcUtil" class="com.lizza.util.JdbcUtil">
<!-- 注入數據源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置QueryRunner;QueryRunner需要配置成多例模式,避免發生線程安全問題 -->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置數據源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
- 進行測試
public class UserServiceTest {
@Test
public void addOne() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
User user = new User();
user.setId(4);
user.setName("test");
user.setAge(19);
Assert.assertEquals(1, userService.addOne(user));
}
@Test
public void deleteOne() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
Assert.assertEquals(1, userService.deleteOne(4));
}
@Test
public void updateOne() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
User user = new User();
user.setId(4);
user.setName("test");
user.setAge(0);
Assert.assertEquals(0, userService.updateOne(user));
}
@Test
public void findOne() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
User user = userService.findOne(1);
Assert.assertEquals(1, user.getId());
}
@Test
public void findAll() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
List<User> list = userService.findAll();
System.out.println(list);
Assert.assertNotNull(list);
}
@Test
public void transferMoney() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 獲取代理對象
UserService userService = context.getBean("proxyUserService", UserService.class);
userService.transferMoney(1, 2, 100.0);
List<User> list = userService.findAll();
System.out.println(list);
}
}
3.2 總結
- 可以看到動態代理在不改變源碼的基礎上實現了事務的控制
- 事務的控制類獨立於業務代碼,降低了耦合性