在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);”。
    這樣一個簡單的框架也許沒有什麼用,在實際項目應用時有現成的開源框架可以用,但可以學習框架實現的原理,對知識有更加深入的瞭解。
    使用動態代理方法可以獲得代碼上的簡潔,缺陷是犧牲了程序執行的效率,在對效率要求不太高的地方可以使用。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章