在Java中利用动态代理实现数据库连接与事务的自动管理【转】
AOP(Aspect Oriented Programming,面向方面编程)是如今比较火的概念之一,再加上Spring框架的流行,很多程序员更是言必称AOP,如果对AOP不了解就像土老帽似的。AOP中的Aspect其实就是程序员关注的某一方面,如某些方法有没有被访问过、某些方法执行时间有多长、把某些方法的执行置于事务之下等等,具体实现方法就是在某些方法执行前后自动执行一些操作,就像拦截器一样。其实一些框架中的拦截器功能正是通过AOP实现的。
??? Java反射功能中有一项就是动态代理,但代理的对象必须实现了接口,也就是Jdk目前仅支持接口的代理。其原理是首先检索被代理对象的所有接口,然后动态生成一个实现了被代理对象接口的Class(这也是为什么叫动态代理的原因),最后把这个Class的一个实例返回。因为通过Java动态代理之后,您所使用的对象就像狸猫换太子一样被掉包了,执行一个方法时,其实是执行的动态生成实例的方法,里面会有一个地方调用原对象的方法,从而达到在原对象方法执行前后运行特定代码的目的。在Spring中,利用了cglib,如果被代理对象实现了接口,就用Java的动态代理,如果仅仅是一个没有接口的类,则用cglib中继承的方式进行代理。
??? 在Spring中有用Java注解(annotation)的方法实现事务管理的功能,也就是声明式事务,去除了代码中繁琐的事务控制代码。在本文中,利用Java的annotation、dynamic proxy实现一个简单的管理数据库连接与事务的框架,而不使用任何现成的框架。
??? 首先是一个获取数据库连接的工具类,此处是使用的是mysql数据库,运行时需要mysql的jdbc驱动包。代码如下:
?
package demo.dynamicproxy;
import java.sql.Connection;
import java.sql.DriverManager;
public class DbUtil {
??? /**
???? * 获取数据库连接,如果使用其他数据库,修改这里就可以了。
???? * @return
???? */
??? public static Connection getConnection() {
??????? Connection conn = null;
??????? try {
??????????? Class.forName("com.mysql.jdbc.Driver");
??????????? conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/st", "root", "");
??????? }
??????? catch(Exception e) {
??????????? e.printStackTrace();
??????? }
????????
??????? return conn;
??? }
}
??? 对于需要数据库操作的业务类,需要实现IDb接口:
?
package demo.dynamicproxy;
import java.sql.Connection;
public interface IDb {
??? /**
???? * 设置数据库连接
???? * @param conn
???? */
??? void setConnection(Connection conn);
????
??? /**
???? * 获取数据库连接。
???? * 如果没有设置返回null。
???? * @return
???? * @throws Exception
???? */
??? Connection getConnection();
????
??? /**
???? * 释放数据库连接
???? * @param conn
???? * @throws Exception
???? */
??? void closeConnection();
}
??? 为了使用方便,建立一个IDb接口的默认实现类:
package demo.dynamicproxy;
import java.sql.Connection;
import java.sql.Statement;
public class DbImpl implements IDb {
??? private Connection conn;
????
??? @Override
??? public void closeConnection() {
??????? try {
??????????? if (conn != null) conn.close();
??????????? conn = null;
??????????? System.out.println("close conn");
??????? }
??????? catch(Exception e) {
??????????? e.printStackTrace();
??????? }
??? }
??? @Override
??? public Connection getConnection() {
??????? return conn;
??? }
??? @Override
??? public void setConnection(Connection conn) {
??????? this.conn = conn;
??????? System.out.println("set conn");
??? }
??? protected int executeNonQuery(String sql) throws Exception {
??????? System.out.println("executeNonQuery begin:" + sql);
??????? Statement stmt = null;
??????? int result = 0;
??????? try {
??????????? stmt = getConnection().createStatement();
??????????? result = stmt.executeUpdate(sql);
??????? }
??????? catch(Exception e) {
??????????? throw e;
??????? }
??????? finally {
??????????? if (stmt != null) stmt.close();
??????? }
??????? System.out.println("executeNonQuery complete:" + sql);
??????? return result;
??? }
}
??? 对于需要数据库连接和事务的方法,加上一个注解就可以了,这就是那个注解:
package demo.dynamicproxy.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
?* 被注解的方法可以自动获取数据库连接并开启事务。
?*
?*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Transactional {
??? boolean autoCommit() default false;
}
被代理方法执行时的处理器,动态代理的核心就在这里:
package demo.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import demo.dynamicproxy.annotation.Transactional;
/**
?* 自动处理数据库连接、事务的调用处理器
?*/
public class TransactionalInvocationHandler implements InvocationHandler {
??? private Object target;
??? private IDb db;
??? /**
???? * 构造方法
???? * @param target
???? */
??? public TransactionalInvocationHandler(Object target) {
??????? this.target = target;
??????? this.db = (IDb) target;
??? }
????
??? public Object invoke(Object proxy, Method method, Object[] args)?
??????? throws Throwable {
??????? System.out.println("method invoke begin:" + method.getName());
??????? Object result = null;
??????? boolean isNeedTransaction = false;
??????? boolean isAutoCommit = false;
??????? if (method.isAnnotationPresent(Transactional.class)) {
??????????? isNeedTransaction = true;
??????????? isAutoCommit = method.getAnnotation(Transactional.class).autoCommit();
??????? }
??????? boolean isLocalOpen = false;
?????????
??????? boolean rollback = false;
????????
??????? Connection conn = db.getConnection();
????????
??????? System.out.println("isNeedTransaction:" + isNeedTransaction);
??????? System.out.println("isAutoCommit:" + isAutoCommit);
??????? System.out.println("isLocalOpen:" + isLocalOpen);
??????? try {
??????????? if (isNeedTransaction) {
??????????????? if (conn == null) {
??????????????????? isLocalOpen = true;
??????????????????? try {
??????????????????????? conn = DbUtil.getConnection();
??????????????????????? if (conn == null) throw new Exception("数据库连接获取错误");
??????????????????????? conn.setAutoCommit(isAutoCommit);
??????????????????? }
??????????????????? catch (Exception e) {
??????????????????????? throw e;
??????????????????? }
??????????????????? db.setConnection(conn);
??????????????? }
????????????????
??????????? }
??????????? result = method.invoke(this.target, args);
??????? }
??????? catch(Exception e) {
??????????? rollback = true;
??????????? e.printStackTrace();
??????????? throw e;
??????? }
??????? finally {
??????????? if (conn != null && isLocalOpen) {
??????????????? if (!isAutoCommit) {
??????????????????? if (rollback) conn.rollback();
??????????????????? else conn.commit();
??????????????? }
??????????????? db.closeConnection();
??????????? }
??????? }
??????? System.out.println("method invoke complete:" + method.getName());
??????? return result;
??? }
}
??? 获取代理对象的工厂类:
package demo.dynamicproxy;
import java.lang.reflect.Proxy;
public class DefaultProxyFactory {
??? @SuppressWarnings("unchecked")
??? public static <T> T createProxy(T target) throws Exception {
??????? if (!(target instanceof IDb)) throw new Exception("target must be instance of IDb");
??????? ClassLoader classLoader = target.getClass().getClassLoader();
??????? Class<?>[] interfaces = target.getClass().getInterfaces();
??????? TransactionalInvocationHandler handler = new TransactionalInvocationHandler(target);
??????? return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
??? }
}
??? 至此,一个简单的框架就完成了。为了测试是否好用,做一个简单的例子,每个访问数据库的业务类都需要一个接口:
package demo.business;
import demo.dynamicproxy.annotation.Transactional;
public interface IDml {
????
??? void insert() throws Exception;
????
????
??? void update() throws Exception;
????
????
??? void delete() throws Exception;
????
????
??? void select() throws Exception;
????
??? @Transactional(autoCommit=false)
??? void execute() throws Exception;
}
??? 业务类既需要实现业务接口,又需要实现IDb接口:
package demo.business;
import java.sql.ResultSet;
import java.sql.Statement;
import demo.dynamicproxy.DbImpl;
import demo.dynamicproxy.DefaultProxyFactory;
public class Dml extends DbImpl implements IDml {
??? @Override
??? public void delete()? throws Exception{
??????? executeNonQuery("delete from t where id=2");
??????? System.out.println("delete 2");
??? }
??? @Override
??? public void execute()? throws Exception{
??????? System.out.println("begin");
??????? insert();
??????? update();
??????? //if (true) throw new Exception("an error"); //此处测试异常
??????? select();
??????? delete();
??? }
??? @Override
??? public void insert()? throws Exception{
??????? executeNonQuery("insert into t(id) values(1)");
??????? System.out.println("insert 1");
??? }
??? @Override
??? public void select()? throws Exception{
??????? Statement stmt = null;
??????? ResultSet rs = null;
??????? try {
??????????? stmt = getConnection().createStatement();
??????????? rs = stmt.executeQuery("select id from t");
??????????? while (rs.next()) {
??????????????? System.out.println("select :" + rs.getString("ID"));
??????????? }
??????? }
??????? catch(Exception e) {
??????????? throw e;
??????? }
??????? finally {
??????????? if (rs != null) rs.close();
??????????? if (stmt != null) stmt.close();
??????? }
??? }
??? @Override
??? public void update()? throws Exception{
??????? executeNonQuery("update t set id=2 where id=1");
??????? System.out.println("update 2");
??? }
????
??? public static void main(String[] args) {
??????? try {
??????????? Dml dmlImpl = new Dml();
??????????? IDml dml= DefaultProxyFactory.createProxy(dmlImpl);
??????????? dml.execute();
????????????
??????????? Thread.sleep(20000); //延迟程序退出,可以查看数据库连接有没有释放
??????????? System.out.println("over");
??????? }
??????? catch(Exception e) {
??????????? e.printStackTrace();
??????? }
??? }
}
??? main方法中是一个使用业务对象进行业务处理的例子。这样,程序就可以运行了,在测试时可以在mysql数据库中执行“show full processlist;”查看数据库连接情况。在使用时,主要注意在业务对象的接口方法中加上注解“@Transactional”,获取的业务对象需要从工厂中获得“IDml dml= DefaultProxyFactory.createProxy(dmlImpl);”。
??? 这样一个简单的框架也许没有什么用,在实际项目应用时有现成的开源框架可以用,但可以学习框架实现的原理,对知识有更加深入的了解。
??? 使用动态代理方法可以获得代码上的简洁,缺陷是牺牲了程序执行的效率,在对效率要求不太高的地方可以使用。