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; }
public Connection getThreadConnection() { try{ Connection conn = tl.get(); if (conn == null) { conn = dataSource.getConnection(); tl.set(conn); } 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
| <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils"> </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 { txManager.beginTransaction(); List<Account> accounts = accountDao.findAllAccount(); txManager.commit(); return accounts; }catch (Exception e){ txManager.rollback(); throw new RuntimeException(e); }finally { 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 {
public void saleProduct(float 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() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object returnValue = null;
Float money = (Float)args[0]; 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 {
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 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() {
@Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object returnValue = null;
Float money = (Float)args[0]; 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
|
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; }
public IAccountService getAccountService() { return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
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 { txManager.beginTransaction(); rtValue = method.invoke(accountService, args); txManager.commit(); return rtValue; } catch (Exception e) { txManager.rollback(); throw new RuntimeException(e); } finally { txManager.release(); } } }); } }
|
并对beans.xml做相应的修改
测试中IAccountService只用Autowird不够了,还需@Qualifier(“proxyAccountService”)
使用动态代理后,消除了重复代码,解除了方法的依赖
但是配置变得繁琐了
更好的方式?——>AOP