从零开始写javaweb框架笔记23-使框架具备AOP特性-实现事务控制特性

时间:2022-05-17 13:12:45

     之前实现过一个Service注解,用于定义服务类,而在服务类种会包括若干方法,有些方法具备事务性,比如创建,修改,删除。如果来保证这类方法具有事务性呢?我们可以利用这个Proxy框架来实现一个简单的事务控制特性。值需要开发者使用Transaction注解,将其定义在需要事务控制的方法上即可。

一:定义事务注解

      注解代码如下:

package org.smart4j.framework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Created by jack on 2017/7/18.
* 定义注解,应用在需要事务的方法上
*/
//Transaction注解上使用了@Target(ElementType.METHOD),说明该注解只能使用在方法级别,也就是说,我们需要将该注解使用在
//每个具有事务性的方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transaction {
}

二:提供事务相关操作

       JDBC提供了事务常用的操作,比如开启事务,提交事务,回滚事务等,我们可以将这些操作统一封装在DatabaseHelper种,详细代码参考:http://blog.csdn.net/j903829182/article/details/50190337如下:

从零开始写javaweb框架笔记23-使框架具备AOP特性-实现事务控制特性


从零开始写javaweb框架笔记23-使框架具备AOP特性-实现事务控制特性




从零开始写javaweb框架笔记23-使框架具备AOP特性-实现事务控制特性


      需要注意的式,默认式提交事务的,所以需要将自带提交属性设置为false。在开启事务完毕后,需要将Connection对象放入本地线程变量中。当事务提交或回滚后,需要移除本地线程中的Connection对象。

     DatabaseHelper的代码如下:

package org.smart4j.framework.helper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smart4j.framework.util.PropsUtil;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

/**
* Created by jack on 2017/7/18.
* 数据库操作助手类
*/
public class DatabaseHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseHelper.class);
private static final String DRIVER;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;
private static final ThreadLocal<Connection> CONNECTION_HOLDER = new ThreadLocal<Connection>();

static {
Properties conf = PropsUtil.loadProps("config.properties");
DRIVER = conf.getProperty("jdbc.driver");
URL = conf.getProperty("jdbc.url");
USERNAME = conf.getProperty("jdbc.username");
PASSWORD = conf.getProperty("jdbc.password");
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
LOGGER.error("can not load jdbc driver", e);
}
}


/**
* 获取数据库连接
*/
public static Connection getConnection() {
Connection conn = CONNECTION_HOLDER.get();//1
if (conn == null) {
try {
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
} catch (SQLException e) {
LOGGER.error("get connection failure", e);
} finally {
CONNECTION_HOLDER.set(conn);
}
}
return conn;
}

/**
* 关闭数据库连接
*/
public static void closeConnection() {
Connection conn = CONNECTION_HOLDER.get();//1
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
LOGGER.error("close connection failure", e);
throw new RuntimeException(e);
} finally {
CONNECTION_HOLDER.remove();//3
}
}
}

/**
* 开启事务
*/
public static void beginTransaction() {
Connection connection = getConnection();
if (connection != null) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
LOGGER.error("begin transaction failure", e);
throw new RuntimeException(e);
} finally {
CONNECTION_HOLDER.set(connection);
}
}
}

/**
* 提交事务
*/
public static void commitTransaction() {
Connection connection = getConnection();
if (connection != null) {
try {
connection.commit();
connection.close();
} catch (SQLException e) {
LOGGER.error("commit transaction failure", e);
throw new RuntimeException(e);
} finally {
CONNECTION_HOLDER.remove();
}

}
}

/**
* 回滚事务
*/
public static void rollbackTransaction() {
Connection connection = getConnection();
if (connection != null) {
try {
connection.rollback();
connection.close();
} catch (SQLException e) {
LOGGER.error("rollback transaction failure", e);
throw new RuntimeException(e);
} finally {
CONNECTION_HOLDER.remove();
}

}
}

}


三:编写事务代理切面类

       我们需要编写一个名为TransactionProxy的类,让它实现Proxy接口,在doProxy方法中完成事务控制的相关逻辑,代码如下:

package org.smart4j.framework.proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smart4j.framework.annotation.Transaction;
import org.smart4j.framework.helper.DatabaseHelper;

import java.lang.reflect.Method;

/**
* Created by jack on 2017/7/18.
* 事务代理类
*/
public class TransactionProxy implements Proxy{
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionProxy.class);
private static final ThreadLocal<Boolean> FLAG_HOLDER = new ThreadLocal<Boolean>(){
@Override
protected Boolean initialValue() {
return false;
}
};
public Object doProxy(ProxyChain proxyChain) throws Throwable {
Object result;
boolean flag = FLAG_HOLDER.get();
Method method = proxyChain.getTargetMethod();
if (!flag && method.isAnnotationPresent(Transaction.class)) {
FLAG_HOLDER.set(true);
try {
DatabaseHelper.beginTransaction();
LOGGER.debug("begin transaction");
result = proxyChain.doProxyChain();
DatabaseHelper.commitTransaction();
LOGGER.debug("commit transaction");
} catch (Exception e) {
DatabaseHelper.rollbackTransaction();
LOGGER.debug("rollback transaction");
throw e;
} finally {
FLAG_HOLDER.remove();
}

} else {
result = proxyChain.doProxyChain();
}
return result;
}
}

       这里定义了一个名为FLAG_HOLDER的本地线程变量,它是一个标志,可以保证同一线程中事务控制逻辑只会执行一次。通过ProxyChain对象可获取目标方法,进而判断该方法是否带有Transaction注解。首先调用DatabaseHelper.beginTransaction方法开启事务,然后调用ProxyChain对象的doProxyChain方法执行目标方法,接着调用DatabaseHelper.commintTransaction提交事务,或者在一次处理中调用DatabaseHelper.rollbackTransaction方法回滚事务,最后别忘记移除FLAG_HOLDER本地线程变量中的标志。


 四:在框架中添加事务代理机制

       只需对AopHelper类的createProxyMap方法稍作调整,即可将事务代理机制添加到框架中。由于之前添加到AOP框架中的只是普通切面代理,现在需要将事务代理也添加进去。我们定义两个私有方法,一个用于添加普通切面代理,另一个用于添加事务代理,就像下面这样:

package org.smart4j.framework.helper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smart4j.framework.annotation.Aspect;
import org.smart4j.framework.annotation.Service;
import org.smart4j.framework.proxy.AspectProxy;
import org.smart4j.framework.proxy.Proxy;
import org.smart4j.framework.proxy.ProxyManager;
import org.smart4j.framework.proxy.TransactionProxy;

import java.lang.annotation.Annotation;
import java.util.*;

/**
* Created by jack on 2017/7/17.
*/
public final class AopHelper {

private static final Logger LOGGER = LoggerFactory.getLogger(AopHelper.class);

static {
try {
Map<Class<?>, Set<Class<?>>> proxyMap = createProxyMap();
Map<Class<?>, List<Proxy>> targetMap = createTargetMap(proxyMap);
for (Map.Entry<Class<?>, List<Proxy>> targetEntry : targetMap.entrySet()) {
Class<?> targetClass = targetEntry.getKey();
List<Proxy> proxyList = targetEntry.getValue();
Object proxy = ProxyManager.createProxy(targetClass, proxyList);
BeanHelper.setBean(targetClass, proxy);
}
} catch (Exception e) {
LOGGER.error("load aop failure", e);
}
}


private static Set<Class<?>> createTargetClassSet(Aspect aspect) throws Exception {
Set<Class<?>> targetClassSet = new HashSet<Class<?>>();
Class<? extends Annotation> annotation = aspect.value();
if (annotation != null && annotation.equals(Aspect.class)) {
targetClassSet.addAll(ClassHelper.getClassSetByAnnotation(annotation));
}
return targetClassSet;
}

private static Map<Class<?>, Set<Class<?>>> createProxyMap() throws Exception {
Map<Class<?>, Set<Class<?>>> proxyMap = new HashMap<Class<?>, Set<Class<?>>>();
/* Set<Class<?>> proxyClassSet = ClassHelper.getClassSetBySupper(AspectProxy.class);
for (Class<?> proxyClass: proxyClassSet) {
if (proxyClass.isAssignableFrom(Aspect.class)){
Aspect aspect = proxyClass.getAnnotation(Aspect.class);
Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
proxyMap.put(proxyClass,targetClassSet);
}
}*/
addAspectProxy(proxyMap);
addTransactionProxy(proxyMap);
return proxyMap;
}

private static Map<Class<?>, List<Proxy>> createTargetMap(Map<Class<?>, Set<Class<?>>> proxyMap) throws Exception {
Map<Class<?>, List<Proxy>> targetMap = new HashMap<Class<?>, List<Proxy>>();
for (Map.Entry<Class<?>, Set<Class<?>>> proxyEntry : proxyMap.entrySet()) {
Class<?> proxyClass = proxyEntry.getKey();
Set<Class<?>> targetClassSet = proxyEntry.getValue();
for (Class<?> targetClass : targetClassSet) {
Proxy proxy = (Proxy) targetClass.newInstance();
if (targetMap.containsKey(targetClass)) {
targetMap.get(targetClass).add(proxy);
} else {
List<Proxy> proxyList = new ArrayList<Proxy>();
proxyList.add(proxy);
targetMap.put(targetClass, proxyList);
}
}
}
return targetMap;
}

private static void addAspectProxy(Map<Class<?>, Set<Class<?>>> proxyMap) throws Exception {
Set<Class<?>> proxyClassSet = ClassHelper.getClassSetBySupper(AspectProxy.class);
for (Class<?> proxyClass : proxyClassSet) {
if (proxyClass.isAnnotationPresent(Aspect.class)) {
Aspect aspect = proxyClass.getAnnotation(Aspect.class);
Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
proxyMap.put(proxyClass, targetClassSet);
}
}
}

private static void addTransactionProxy(Map<Class<?>, Set<Class<?>>> proxyMap) throws Exception {
Set<Class<?>> serviceClassSet = ClassHelper.getClassSetByAnnotation(Service.class);
proxyMap.put(TransactionProxy.class, serviceClassSet);
}

}

         当运行应用出现后,就可以看到事务代理切面中输出的日志信息了。到这里一个简单的事务控制框架就开发完毕了。


      总结:

      首先通过开发一个Proxy框架,然后通过该框架实现了AOP特性,通过模板方法模式提供了一个抽象切面类AspectProxy,底层实际上是用CGLib开发的一个动态代理框架。我们只需根据业务需求定义自己的切面类,拓展AspectProxy抽象类并定义Aspect注解,然后完成特定的钩子方法,即可将横切业务逻辑与业务逻辑相分离,这就是AOP要做的事情。最后,使用Proxy框架实现了一个简单的事务代理框架,可在Service类的方法中使用Transaction注解来定义需要进行事务控制的方法。