使用注解和動態代理在Service層控制事務
使用注解和動態代理在Service層控制事務
數據庫的事務控制可以從JavaWeb的Filter開始,到Struts2的interceptor,最后到Spring的AOP。按我個人的理解應該是粗粒度、中粒度和細粒度。在沒有學習interceptor和AOP之前,我們自己完全可以使用現有技術做一個簡單的AOP實現。
技術:
聲明一個注解,以便于在攔截時判斷是否要處理事務。
聲明一個代理類。接收被代理對象。
需要說明的:如果使用JDK的動態代理,則注解必須要作用在接口上。如果使用cglib則可以沒有接口類,當前注解也就作用在具體類的方法上了。
1、使用JDK的動態代理-需要接口
代碼清單1-聲明事務注解:
package cn.itcast.tx;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 事務注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.METHOD)
public @interface Tx {}
代碼清單2-聲明代理類:
package cn.itcast.tx;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 事務攔截模擬
*/
public class TxProxy implements InvocationHandler{
private Object obj;
private TxProxy(Object obj){
this.obj=obj;
}
//接收一個被代理的對象
public static Object newProxy(Object o){
Object proxy = Proxy.newProxyInstance(TxProxy.class.getClassLoader(),
o.getClass().getInterfaces(),
new TxProxy(o));
return proxy;
}
//使用泛型,接收被代理類的Class對象
public static <T>T newProxy(Class<T> cls){
Object o = null;
try{
o = cls.newInstance();
}catch(Exception e){
throw new RuntimeException(e.getMessage(),e);
}
Object proxy = Proxy.newProxyInstance(TxProxy.class.getClassLoader(),
o.getClass().getInterfaces(),
new TxProxy(o));
return (T)proxy;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object res = null;
if(method.isAnnotationPresent(Tx.class)){//判斷是否添加了Tx事務注解
try{
System.err.println("開始攔截..從當前線程局部對象中獲取一個連接");
res = method.invoke(this.obj, args);
System.err.println("攔截完成提交");
}catch(Exception e){
System.err.println("回滾");
throw new RuntimeException(e.getMessage(),e);
}finally{
System.err.println("結束。。。放回連接池");
}
}else{
res = method.invoke(this.obj, args);
}
return res;
}
}
測試代碼清單1:
必須要先完成一個接口:
package cn.itcast.tx;
public interface IOne {
@Tx
void save();//添加需要處理事務的注解
void
}
實現接口類:
package cn.itcast.tx;
public class One implements IOne {
public void save() {
System.err.println("保存。。。");
}
public void del() {
System.err.println("刪除。。。。");
}
}
測試:
@Test
public void t1(){
IOne one = TxProxy.newProxy(One.class);
one.save(); //此方法將會處理事務
one.del();
}
測試結果:可見,對于保存方法成功開始了事務:
開始攔截..從當前線程局部對象中獲取一個連接
保存。。。
攔截完成提交
結束。。。放回連接池
刪除。。。。
2、使用cglib的動態代理
不需要接口。但需要導入cglib的兩個jar文件,分為是:
cglib.jar
asm.jar
使用cglib的動態代理,由于不需要接口,當然也可以有接口。所以此時,你的事務注解就必須要注解到實體類的方法上,而不是接口的方法上。
以下是cglib代理的工具類:
仍然使用上面那個Tx注解,所以Tx注解的代碼略,直接上cglib的動態代理類:
package cn.itcast.cglib;
import java.lang.reflect.Method;
import cn.itcast.tx.Tx;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 使用Cglib的代理
*/
public class ProxyUtils implements MethodInterceptor {
private Object src;
private ProxyUtils(Object src){
this.src=src;
}
public static <T>T newProxy(T t){
Enhancer en = new Enhancer();
en.setSuperclass(t.getClass());
en.setCallback(new ProxyUtils(t));
Object o = en.create();
return (T)o;
}
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
Object o = null;
if(method.isAnnotationPresent(Tx.class)){
try{
System.err.println("開始一個事務。。。");
o = method.invoke(src, args);
System.err.println("提交一個事務");
}catch(Exception e){
System.err.println("回滾一個事務");
}finally{
System.err.println("放回連接池");
}
}else{
o = method.invoke(src,args);
}
return o;
}
}