手寫簡易版動態代理

基礎代碼

先在此處聲明接口與實現類,後續會用到。
Dao接口:

package dao;

/**
 * @author Livingdd
 * 2020/4/30 10:47
 **/
public interface Dao {
    void queryDataBase();
    String queryDataBase(String param);
}

Dao實現類:

package dao;

/**
 * @author Livingdd
 * 2020/4/30 11:04
 **/
public class RoleDaoImpl implements Dao{
    public void queryDataBase() {
        System.out.println("Query Role Database No Param");
    }

    public String queryDataBase(String param) {
        System.out.println("Query Role Database Param : "+param);
        return param;
    }
}

靜態代理

靜態代理分爲兩種,聚合以及繼承。

繼承

顧名思義,繼承要代理的目標對象,對目標對象中的方法進行代理。
假如變態的老闆讓你用靜態代理的方式對Dao層實現類加入日誌功能。你的代碼可能會這樣:

package staticProxy.inherit;

import dao.RoleDaoImpl;

/**
 * @author Livingdd
 * 2020/4/30 11:06
 **/
public class RoleRecordLog extends RoleDaoImpl {
    @Override
    public void queryDataBase() {
        System.out.println("Record Role Log");
        super.queryDataBase();
    }

    @Override
    public String queryDataBase(String param) {
        System.out.println("Record Role Log");
        return super.queryDataBase(param);
    }
}


主方法調用:

 Dao dao = new RoleRecordLog();
 dao.queryDataBase();
 System.out.println("===============");
 System.out.println("Param Return :"+dao.queryDataBase("test"));

假如你的變態老闆心情不好,和你說Dao層實現類不需要日誌了,要加上事務,你的代碼可能會這樣:

package staticProxy.inherit;

import dao.RoleDaoImpl;

/**
 * @author Livingdd
 * 2020/4/30 11:06
 **/
public class RoleTransaction extends RoleDaoImpl {
    @Override
    public void queryDataBase() {
        System.out.println("Make Role Transaction");
        super.queryDataBase();
    }

    @Override
    public String queryDataBase(String param) {
        System.out.println("Make Role Transaction");
        return super.queryDataBase(param);
    }
}

主方法調用:

 Dao dao = new RoleTransaction();
 dao.queryDataBase();
 System.out.println("===============");
 System.out.println("Param Return :"+dao.queryDataBase("test"));

假如你的變態老闆更加變態了,他可能會讓你先做事務再加日誌,那麼你的代碼可能會這樣:

package staticProxy.inherit;

/**
 * @author Livingdd
 * 2020/4/30 11:10
 **/
public class TransactionAndRoleRecordLog extends RoleRecordLog{

    @Override
    public void queryDataBase() {
        System.out.println("Make Role Transaction");
        super.queryDataBase();
    }

    @Override
    public String queryDataBase(String param) {
        System.out.println("Make Role Transaction");
        return super.queryDataBase(param);
    }
}

主方法調用:

 Dao dao = new TransactionAndRoleRecordLog();
 dao.queryDataBase();
 System.out.println("===============");
 System.out.println("Param Return :" + dao.queryDataBase("test"));

但是如果你的變態老闆讓你先寫日誌再做事務呢,難道要再添加一個代理類去繼承寫日誌的代理類嗎?
所以此方法的缺點爲會產生類爆炸。

聚合

目標對象與代理對象實現同一個接口,並且傳入代理對象作爲私有屬性。
同一個例子,假如變態的老闆讓你對Dao層實現類加入日誌功能。你的代碼可能會這樣:

package staticProxy.aggregation;

import dao.Dao;

/**
 * @author Livingdd
 * 2020/4/30 11:19
 **/
public class RoleRecordLog implements Dao {
    private Dao dao;
    public RoleRecordLog(Dao dao){
        this.dao  =dao;
    }
    @Override
    public void queryDataBase() {
        System.out.println("Record Role Log");
        this.dao.queryDataBase();
    }

    @Override
    public String queryDataBase(String param) {
        System.out.println("Record Role Log");
        return this.dao.queryDataBase(param);
    }
}

主方法調用:

 RoleDaoImpl roleDao = new RoleDaoImpl();
 Dao dao = new RoleRecordLog(roleDao);
 dao.queryDataBase();
 System.out.println("===============");
 System.out.println("Param Return :" + dao.queryDataBase("test"));

要是讓你加入事務呢?

package staticProxy.aggregation;

import dao.Dao;

/**
 * @author Livingdd
 * 2020/4/30 11:19
 **/
public class RoleTransaction implements Dao {
    private Dao dao;
    public RoleTransaction(Dao dao){
        this.dao  =dao;
    }
    @Override
    public void queryDataBase() {
        System.out.println("Make Role Transaction");
        this.dao.queryDataBase();
    }

    @Override
    public String queryDataBase(String param) {
        System.out.println("Make Role Transaction");
        return this.dao.queryDataBase(param);
    }
}

此時你的老闆又變態了,讓你又加事務,又加日誌,聚合的方式能夠比繼承的方式靈活一些,你可以這樣:

 Dao roleRecordLog = new RoleRecordLog(new RoleDaoImpl());
 Dao dao = new RoleTransaction(roleRecordLog);
 dao.queryDataBase();
 System.out.println("===============");
 System.out.println("Param Return :" + dao.queryDataBase("test"));

可以看出聚合的方式相對於繼承的方式,會少產生一些類,但是還是會產生很多類。

動態代理

動態代理原理爲,用代碼寫出代理類,而後編譯再獲取代理對象,對目標對象進行增強。相比較與動態代理更加方便。。。
生成代理對象代碼:

package dynamicProxy;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * @author Livingdd
 * 2020/4/30 11:28
 **/
public class ProxyUtil {
    private static final String lineFeed = "\n";
    private static final String tab = "\t";
    private static final String noReturn ="void";

    public static Object getJavaFile(Object targetObject) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class clazz= targetObject.getClass().getInterfaces()[0];
        //拼接java文件
        String interfaceName = clazz.getSimpleName();
        String content = "";
        //包名
        String packageContent = "package com.livingdd;" + lineFeed + lineFeed;
        //import
        String importContent = "import dao.Dao;" + lineFeed + lineFeed;
        //類描述
        String classDescription = "public class $Proxy implements " + interfaceName + " {" + lineFeed;
        //成員變量
        String paramContent = tab + "private " + interfaceName + " target;" + lineFeed;
        //構造方法
        String constructContent = tab + "public $Proxy(" + interfaceName + " target){" + lineFeed
                + tab + tab + "this.target = target;" + lineFeed
                + tab + "}" + lineFeed;
        //方法,可能有多個,for循環拼接
        String methodContent = "";
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            String returnTypeName = method.getReturnType().getSimpleName();
            String methodName = method.getName();
            Class<?>[] parameterTypes = method.getParameterTypes();
            methodContent += tab + "public " + returnTypeName + " " + methodName + "(";
            //方法中的參數,可能有多個,for循環拼接
            String argsContent ="";
            //目標對象調用的參數,可能有多個,for循環拼接
            String useArgasContent="";
            int paramCount = 1;
            for (Class parameterType : parameterTypes){
                String parameterTypeSimpleName = parameterType.getSimpleName();
                argsContent += parameterTypeSimpleName+" var"+paramCount+",";
                useArgasContent += "var"+paramCount+",";
            }
            if(argsContent.length()>0){
                argsContent = argsContent.substring(0,argsContent.lastIndexOf(",")-1);
                useArgasContent = useArgasContent.substring(0,useArgasContent.lastIndexOf(",")-1);
            }
            methodContent += argsContent+"){"+lineFeed
                             +tab+tab+"System.out.println(\"Record Log\");"+lineFeed;
            methodContent += tab+tab;
            if(!noReturn.equals(returnTypeName)){
                methodContent += "return ";
            }
            methodContent += "this.target."+methodName+"("+useArgasContent+");"+lineFeed;
            methodContent += tab+"}"+lineFeed;
        }
        content+= packageContent+importContent+classDescription+paramContent+constructContent+methodContent+"}";
        FileWriter fileWriter = null;
        File file = new File("D:\\com\\livingdd\\$Proxy.java");
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            fileWriter = new FileWriter(file);
            fileWriter.write(content);
            fileWriter.flush();
        }finally {
            if(fileWriter!=null){
                fileWriter.close();
            }
        }

        //編譯java文佳
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
        Iterable units = fileMgr.getJavaFileObjects(file);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        t.call();
        fileMgr.close();

        //反射獲取java對象
        //這裏只需要填寫d盤即可,因爲反射出來的類包名爲com.livingdd,並且在D盤下
        URL[] urls = new URL[]{new URL("file:D:\\\\")};
        URLClassLoader classLoader = new URLClassLoader(urls);
        Class<?> loadClass = classLoader.loadClass("com.livingdd.$Proxy");
        Constructor constructor = loadClass.getConstructor(clazz);
        Object o = constructor.newInstance(targetObject);
        return o;
    }
}

主方法調用:

Dao dao = (Dao) ProxyUtil.getJavaFile(new RoleDaoImpl());

附 生成的java文件代碼:

package com.livingdd;

import dao.Dao;

public class $Proxy implements Dao {
    private Dao target;
    public $Proxy(Dao target){
        this.target = target;
    }
    public void queryDataBase(){
        System.out.println("Record Log");
        this.target.queryDataBase();
    }
    public String queryDataBase(String var){
        System.out.println("Record Log");
        return this.target.queryDataBase(var);
    }
}

本文只是寫了簡易版的動態代理,目的在於瞭解大致原理,還有許多待改進的地方比如異常處理、動態傳入增強的邏輯等。

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