1.转账案例分析
先看业务层的代码:
public void transfer(int sourceId, int targetId, int money) {
//执行操作
//1.根据转出用户id查询转出用户
Account source = accountDao.findById(sourceId);
//2.根据转入用户id查询转入用户
Account target = accountDao.findById(targetId);
//3.给转出用户减钱
source.setMoney(source.getMoney()-money);
//4.给转入用户加钱
target.setMoney(target.getMoney()+money);
//5.更新转出用户
accountDao.updateAccount(source);
//手动加入异常
int i = 1/ 0;
//6.更新转入用户
accountDao.updateAccount(target);
}
执行结果:转出方money减少,但因为更新转入方数据时,发生了异常,导致其money并没有增加。
why?看一下我们用的Dbutils 的核心对象QueryRunner的配置。
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
我们发现QueryRunner是一个多例,因为实际开发中,对数据库的操作必定是多线程同时进行的,所以QueryRunner要配置成多例,所以,我们每次调用accountDao的方式,都获取了一个新的QueryRunner,每一步操作都是一个独立的事务,此时,spring时默认事务自动提交的,就出现了一致性的问题。
解决方案:手动添加一个事务对整套操作进行管理,期间使用的Connection是同一个,如何保证是同一个呢?使用ThreadLocal对象,这个对象是map存储结构,key即当前执行线程,value是Object,可以实现同一线程数据共享。
2.绑定事务改进
如何对一个功能(方法)绑定一个事务进行管理呢?如何增强一个类的功能呢?
三种方式:继承,装饰者模式,动态代理
当然使用态代理!
动态代理:在不改变源码的基础上对已有的功能进行增强,底层:预编译,反射,特点:字节码随用随创建,随用随加载,大体思想是,通过反射获取到要执行的方法对象,在方法执行(method.invoke(obj,args))前后,对其参数,或者结果集进行增强,这样预编译的程序,在这个对象真正执行方法时,就会生效。
实现:创建一个事务管理类TransactionManager,这个类提供了开启事务,提交,回滚,释放资源(注意,这里的释放资源不仅要关闭连接,还要断开连接)的方法,每个方法具体实现是由Connection对象完成的,所以,我们需要定义一个ConnectionUtils,提供获取连接的方法,从ThreadLocal对象中get()方法获取,如果是第一次获取Connection,获取不到,我们从DataSource中获取一个并通过set(Object object)方法设置到ThreadLocal中,并返回这个Connection。
好的,事务管理类就准备好了,我们可以在accountService类中对每个方法进行改进,为他们绑定事务,但是,这样做的话,会产生代码重复的问题,怎么解决呢?
我们要做的是创建一个工厂类,提供一个获取accountService代理对象的方法,方法内通过动态代理,把TransactionManager内的各个方法,增强到accountService对象的各个方法上;这样accountService的每个方法执行时都会绑定事务。
3.jdk动态代理改进
核心api:Proxy.newInstance(),提供者:JDK官方
要求被代理的类至少实现一个接口,因为jdk动态代理时基于接口的代理,即创建了一个和被代理类同级的类,和它实现同一个接口
看代码,我也不是很理解,现阶段先会用:
@Bean(name = "proxyAccountService")
public IAccountService getAccountService(){
/** newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
*/
return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(), new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
stackoverflow对这个参数的解释是:
1. 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())。
2. 可以将代理对象返回以进行连续调用,这就是proxy存在的目的,因为this并不是代理对象。
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//开启事务
transactionManager.beginTransaction();
//执行操作
Object object = method.invoke(accountService,args);
//提交事务
transactionManager.commitTransaction();
return object;
} catch (Exception e) {
//事务回滚
transactionManager.rollbackTransaction();
throw new RuntimeException(e);
}finally {
//释放资源
transactionManager.closeTransaction();
}
}
});
}
4.cglib动态代理
核心api:Enhance.create(),提供方:第三方cglib库
它是基于子类的动态代理,新生成的代理对象是被代理对象的子类
看代码吧,这个我也不是很理解
create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码。
*
* Callback:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
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 {
try {
//开启事务
transactionManager.beginTransaction();
//执行操作
Object object = method.invoke(accountService, objects);
//提交事务
transactionManager.commitTransaction();
return object;
} catch (Exception e) {
//事务回滚
transactionManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
//释放资源
transactionManager.closeTransaction();
}
}
5.aop入门
aop: 面向切面编程
对已有方法进行 无代码侵入增强,底层是动态代理
学几个专业名词:
连接点:要增强的类中的所有方法
切入点:被增强的方法
通知:增强的功能 ----->前置通知,后置通知,异常通知,最终通知
切面:切入点和通知的结合
啊哈?四个吧,其他感觉没必要记啊,见名知意,
目标对象:被增强的对象
代理对象:增强后产生的代理对象
织入:把通知应用到目标对象中产生代理对象的过程就是织入
引介:不知道 - -,算了算了,补上吧,就是给类中添加字段或者方法
在编写aop我们需要做的事(这个老师总结的太好了):
a. 编写业务代码
b.编写增强代码
c.配置业务代码和增强代码的关系,即切面
maven依赖:aspectjweaver
约束:xmlns:aop
配置切面:这个是一步一步的:
<aop:config>
<!--共性抽取配置切入点表达式-->
<aop:pointcut id="pt" value="execution(* cn.itcast.spring.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="txManager" ref="transactionManager">
<aop:before method="前置通知" pointcut-ref="pt"></aop:before>
<aop:after-returning method="后置通知" pointcut-ref="pt"></aop:after-returning>
<aop:after-throwing method="异常通知" pointcut-ref="pt"></aop:after-throwing>
<aop:after method="最终通知" pointcut-ref="pt"></aop:after>
</aop:aspect>
</aop:config>
6.aop注解开发
a. 开启注解包扫描
<context:component-san base-package="cn.itcast.spring"></conext:component-scan>
b. 开启aop注解支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
c. 在增强类上声明切面注解
@Aspect
d. 在增强类定义切入点表达式
@Pointcunt("excution(* cn.itcast.spring.service.impl..(..))")
public void pt(){}
不要问我为什么这里写了个方法,我猜是因为切入点本身就是个方法,这个抽取出的切入点表达式,也要定义成一个方法
c. 配置通知
@Before("pt()")
@AfterReturning("pt()")
@AfterThrowing("pt()")
@After("pt()")
问题来了,spring的aop注解方式通知的执行顺序是:前置,最终,后置/异常
所以我们使用环绕通知:
@Around("pt()")
环绕通知是将所有的增强代码放到一个方法里,我们自己定义其执行顺序,以方法执行为相对位置,原方法执行用到一个api:ProceedingJoinPoint 中的getArgs()/proceeding()方法。
看不懂把,来段代码:
@Around("gt()")
public Object aroundTransaction(ProceedingJoinPoint proceedingJoinPoint){
try {
//前置通知:开启事务
connectionUtils.getConnection().setAutoCommit(false);
//获取参数,并执行原方法
Object[] args = proceedingJoinPoint.getArgs();
Object object = proceedingJoinPoint.proceed(args);
//后置通知:提交事务
connectionUtils.getConnection().commit();
//返回结果
return object;
} catch (Throwable throwable) {
try {
//异常通知:回滚事务
connectionUtils.getConnection().rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
throwable.printStackTrace();
}finally {
try {
//最终通知:释放资源
connectionUtils.getConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
return null;
}