Spring笔记09 事务控制的问题&动态代理
coconutnut

https://www.bilibili.com/video/av47952931
p46~55


Account案例中转账方法的事务问题

事务控制应该都在业务层,之前的案例中都在持久层,需要修改
写两个工具类

2个工具类

ConnectionUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {

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

private DataSource dataSource;

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
// 1.先从ThreadLocal上获取
Connection conn = tl.get();
// 2.判断当前线程上是否有连接
if (conn == null) {
// 3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
// 4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}

/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}

TransactionManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
public class TransactionManager {

private ConnectionUtils connectionUtils;

public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}

/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}

/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}

/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}


/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();// 还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}

连接还回连接池中后,还需再把连接和线程解绑,否则下次ConnectionUtils中判断是否有连接是true,但这个连接是已经关闭的错误的连接

注入

1
2
3
4
5
6
7
8
9
10
11
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置事务管理器-->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>

代码改造

修改之后不需要在beans.xml中注入dataSource了

1
2
3
4
5
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<!-- 注入数据源-->
<!-- property name="dataSource" ref="dataSource"></property-->
</bean>

在AccountDaoImpl中加一个ConnectionUtils

1
2
3
4
5
private ConnectionUtils connectionUtils;

public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}

并且runner获取连接改为

1
runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));

此时,AccountServiceImpl中一个完整的事务流程是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public List<Account> findAllAccount() {
try {
// 1.开启事务
txManager.beginTransaction();
// 2.执行操作
List<Account> accounts = accountDao.findAllAccount();
// 3.提交事务
txManager.commit();
// 4.返回结果
return accounts;
}catch (Exception e){
// 5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
}finally {
// 6.释放连接
txManager.release();
}
}

但是每个方法都要这样写,很臃肿
而且方法的依赖很严重(如果TransactionManager中beginTransaction方法名改成beginTransaction1,AccountServiceImpl中每一处用到的都要改)
进一步改造:代理

& 现在的依赖有些乱七八糟,在后面Spring的事务控制中解决

动态代理

描述

特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强

分类:

  • 基于接口的动态代理
  • 基于子类的动态代理

用处如:
连接池close方法关闭时不能真正关闭,还要还回池中。可以使用动态代理对其进行增强,把它还回池里
解决中文乱码,request对象的方法增强,用装饰者模式可以实现,也可以用动态代理实现

基于接口的动态代理

涉及的类:Proxy
提供者:JDK官方
如何创建代理对象:使用Proxy类中的newProxyInstance方法
创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用

newProxyInstance方法的参数:

  • ClassLoader:类加载器
    它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法
  • Class[]:字节码数组
    它是用于让代理对象和被代理对象有相同方法。固定写法
  • InvocationHandler:用于提供增强的代码
    写如何代理。一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 对生产厂家要求的接口
*/
public interface IProducer {

/**
* 销售
* @param money
*/
public void saleProduct(float money);

/**
* 售后
* @param money
*/
public void afterService(float money);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 一个生产者
*/
public class Producer implements IProducer{

public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}

public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 模拟一个消费者
*/
public class Client {

public static void main(String[] args) {
final Producer producer = new Producer();

IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法(即有拦截功能)
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 接收返回值
Object returnValue = null;

// 1.获取方法执行的参数
Float money = (Float)args[0];
// 2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});

proxyProducer.saleProduct(10000f);
}
}

使用代理后,消费者付10000,代理提成20%,生产者拿到8000

并没有对生产者的代码做任何修改,但是实现了增强
此处即为基于接口的动态代理

但是有一个问题
如果生产者没有实现接口,就不能这样用了,会报代理异常

基于子类的动态代理

要求有第三方jar包的支持

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>

涉及的类:Enhancer
提供者:第三方cglib库

如何创建代理对象:使用Enhancer类中的create方法
创建代理对象的要求:被代理类不能是最终类

create方法的参数:

  • Class:字节码
    用于指定被代理对象的字节码
  • Callback:用于提供增强的代码
    写如何代理。一般是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
    此接口的实现类都是谁用谁写
    一般写的都是该接口的子接口实现类:MethodInterceptor

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 一个生产者
*/
public class Producer {

/**
* 销售
* @param money
*/
public void saleProduct(float money){
System.out.println("销售产品,并拿到钱:"+money);
}

/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.itheima.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* 模拟一个消费者
*/
public class Client {

public static void main(String[] args) {
final Producer producer = new Producer();

Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object returnValue = null;

// 1.获取方法执行的参数
Float money = (Float)args[0];
// 2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}

使用动态代理实现事务控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 用于创建Service的代理对象的工厂
*/
public class BeanFactory {

private IAccountService accountService;

private TransactionManager txManager;

public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}

public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}

/**
* 获取Service代理对象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("test".equals(method.getName())){
return method.invoke(accountService,args);
}

Object rtValue = null;
try {
// 1.开启事务
txManager.beginTransaction();
// 2.执行操作
rtValue = method.invoke(accountService, args);
// 3.提交事务
txManager.commit();
// 4.返回结果
return rtValue;
} catch (Exception e) {
// 5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
// 6.释放连接
txManager.release();
}
}
});
}
}

并对beans.xml做相应的修改
测试中IAccountService只用Autowird不够了,还需@Qualifier(“proxyAccountService”)

使用动态代理后,消除了重复代码,解除了方法的依赖
但是配置变得繁琐了
更好的方式?——>AOP