动态代理

需要知道一个东西是怎么来的,你做一个就清楚了,所以我着手模拟了下

首先定义一个接口
public interface Action {
    public void move();
    public void speak();
}
实现这个接口
/**
 * @Author: YangZaining
 * @Date: Created in 19:40$ 2018/8/11$
 */
public class ActionImpl implements Action{
    @Override
    public void move() {
        System.out.println("移动了。。。。");
    }

    @Override
    public void speak() {
        System.out.println("sxl nb");//别在意这句话,我好朋友而已
    }
}

现在我们有这样一个要求,对move方法进行测试计算它的执行时间,则我们会想到继承或组合这个类,或者直接在main方法里调用测试,以下只是实现它的一种方式
如下:

/**
 * @Author: YangZaining
 * @Date: Created in 19:59$ 2018/8/11$
 */
public class Test implements Action {

    Action action;

    public Test(Action action) {//传入实现的子类即可
        this.action = action;
    }

    @Override
    public void move() {
        long st = System.currentTimeMillis();
        action.move();
        long ed = System.currentTimeMillis();
        System.out.println("运行了" + (ed - st) + "ms");
    }

    @Override
    public void speak() {
        action.speak();
    }
    public static void main(String[] args) {
        Action action = new ActionImpl();
        new Test(action).move();
    }
}
output:
移动了。。。。
运行了199840us

现在我又有个要求,在speak方法前后加入一句话,这时候你又会想到简单,再写这样一个类就可以了,但是每次需求的变更你都要写这样一个类,不是很难受吗?而且我们需要测试的时候才记录这些内容,上线了,我们要撤销这些内容,这样做的话不是很麻烦吗?
所以我们需要一个代理的代理,去减轻工作量,帮我们生成这样的代理对象,所以我们要着手动态的去创建这个类

import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * @Author: YangZaining
 * @Date: Created in 19:32$ 2018/8/11$
 */
public class Proxy {
    public static Object newProxynewInstance() throws Exception {
        String s = "package cn.learn;\n" +
                "\npublic class Test implements Action {\n" +
                "\n" +
                "    Action action;\n" +
                "\n" +
                "    public Test(Action action) {\n" +
                "        this.action = action;\n" +
                "    }\n" +
                "\n" +
                "    @Override\n" +
                "    public void move() {\n" +
                "        long st = System.nanoTime();\n" +
                "        action.move();\n" +
                "        long ed = System.nanoTime();\n" +
                "        System.out.println(\"运行了\" + (ed - st) + \"us\");\n" +
                "    }\n" +
                "\n" +
                "    @Override\n" +
                "    public void speak() {\n" +
                "        action.speak();\n" +
                "    }\n" +
                "\n" +
                "}\n";
        String filePath =  "D:\\src\\cn\\learn\\Test.java";//本地建一个目录,以免和编译期编译的文件混淆
        File fs = new File(filePath);
        FileWriter fw = new FileWriter(fs);//写入文件里
        fw.write(s);
        fw.flush();
        fw.close();
        //编译文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        System.out.println("CompilerName:" + compiler);//编译器名字
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable it = fileManager.getJavaFileObjects(filePath);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null,it);
        t.call();
        fileManager.close();
        //加载到内存并且生成新对象
        URL[] urls = new URL[]{new URL("file:/"+"d:/src/")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class c = urlClassLoader.loadClass("cn.learn.Test");
        Constructor constructor = c.getConstructor(Action.class);
        Object object =  constructor.newInstance(new ActionImpl());
        return object;
    }
}

然后主函数里调用这个方法既可以获取到代理对象,对这个类继续加工

import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * @Author: YangZaining
 * @Date: Created in 19:32$ 2018/8/11$
 */
public class Proxy {
    public static Object newProxynewInstance(Class inf) throws Exception {
        Method[] methods = inf.getMethods();
        String s = "package cn.learn;\n" +
                "\npublic class Test implements " + inf.getName() + " {\n" +
                "\n" +
                "    " + inf.getName() + " action;\n" +
                "\n" +
                "    public Test(" + inf.getName() + " action) {\n" +
                "        this.action = action;\n" +
                "    }\n";
        String ln = "\r\n";
        for (Method m : methods) {
            s += "    @Override\n";
            if ("move".equals(m.getName())) {
                s += "public " + m.getGenericReturnType() + " " + m.getName() + "(){" +
                        "        long st = System.nanoTime();\n" +
                        "        action."+m.getName()+"();\n" +
                        "        long ed = System.nanoTime();\n" +
                        "        System.out.println(\"运行了\" + (ed - st) + \"us\");\n" +
                        "}";
            }
            else  {
                s += "public " + m.getGenericReturnType() + " " + m.getName() + "(){" +
                        "        action."+m.getName()+"();\n" +
                        "    }\n" +
                        "\n";
            }
        }
        s+="}\n";
        String filePath =  "D:\\src\\cn\\learn\\Test.java";
        File fs = new File(filePath);
        FileWriter fw = new FileWriter(fs);//写入文件里
        fw.write(s);
        fw.flush();
        fw.close();
        //编译文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        System.out.println("CompilerName:" + compiler);//编译器名字
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable it = fileManager.getJavaFileObjects(filePath);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, it);
        t.call();
        fileManager.close();
        //加载到内存并且生成新对象
        URL[] urls = new URL[]{new URL("file:/"+"d:/src/")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class c = urlClassLoader.loadClass("cn.learn.Test");
        Constructor constructor = c.getConstructor(inf);
        Object object = constructor.newInstance(new ActionImpl());
        return object;
    }
}

进一步加工,我们会发现这个只能实现测试时间的一个代理,而且还只是在move方法上
所以我们要定义一个专门处理事件的接口

import java.lang.reflect.Method;

public interface InvocationHandler {
    public void invoke(Object o,Method m);//需要执行的方法对象和方法本身(我们定义的函数没有参数,暂且先不定义)
}

现在实现这个接口

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author: YangZaining
 * @Date: Created in 23:26$ 2018/8/11$
 */
public class TalkHandler implements InvocationHandler{
    @Override
    public void invoke(Object o,Method m) {
        System.out.println("我前进了一步");
        try {
            m.invoke(o,new Object[]{});
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println("我后退了一步");
    }
}

为实现方便,就不特定判断哪个方法是move,哪个方法是speak了,下面是改进后的代码,这一块做了几个变动:1)生成代理对象构造函数的参数改变;2)在生成代码块里增加了Method方法传值;3)以及下面构造器传值;
为什么要怎么做呢?因为我们需要让我们生成的代理对象按照我们所规定的方法进行执行,所以我们需要把我们的事件(InvocationHandler )进行传递,而我们构造的事件执行需要被代理对象(Action)的方法,所以我们生成的代理对象(Test,名字无所谓只是对应本例是这样的)中反射进行传值,这一块算是比较饶的地方,这里代码语义容易混淆,应对照生成的代理对象理解,实在不明白就自己尝试着修改。

import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * @Author: YangZaining
 * @Date: Created in 19:32$ 2018/8/11$
 */
public class Proxy {
    public static Object newProxynewInstance(Class inf, InvocationHandler in) throws Exception {
        Method[] methods = inf.getMethods();
        String ln = "\r\n";
        String s = "package cn.learn;" + ln +
                "public class Test implements " + inf.getName() + " {" + ln +
                ln +
                "    InvocationHandler in;" + ln +
                "    public Test(InvocationHandler in) {" + ln +//事件传入
                "        this.in = in;" + ln +
                "    }\n";
        for (Method m : methods) {
            s += "    @Override" + ln +
                    "public " + m.getGenericReturnType() + " " + m.getName() + "() {" + ln +
                    "try{"+ln+
                    " java.lang.reflect.Method ms = " + inf.getName() + ".class.getMethod(\"" + m.getName() + "\");" + ln +
                    "in.invoke(this,ms);" + ln +//事件方法的执行
                    "}catch(Exception e){"+ln+
                    "e.printStackTrace();"+ln+
                    "}"+ln+
                    "    }" + ln + ln;
        }
        s += "}" + ln;
        String filePath = "D:\\src\\cn\\learn\\Test.java";
        File fs = new File(filePath);
        FileWriter fw = new FileWriter(fs);//写入文件里
        fw.write(s);
        fw.flush();
        fw.close();
        //编译文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        System.out.println("CompilerName:" + compiler);//编译器名字
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable it = fileManager.getJavaFileObjects(filePath);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, it);
        t.call();
        fileManager.close();
        //加载到内存并且生成新对象
        URL[] urls = new URL[]{new URL("file:/" + "d:/src/")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class c = urlClassLoader.loadClass("cn.learn.Test");
        System.out.println(c);
        Constructor constructor = c.getConstructor(InvocationHandler.class);//发生改变,因为生成的代理对象传值改变了
        Object object = constructor.newInstance(in);//给生成的代理对象传值
        return object;
    }
}

生成的代理对象文件内容

package cn.learn;

public class Test implements cn.learn.Action {

    InvocationHandler in;

    public Test(InvocationHandler in) {
        this.in = in;
    }

    @Override
    public void speak() {
        try {
            java.lang.reflect.Method ms = cn.learn.Action.class.getMethod("speak");
            in.invoke(this, ms);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void move() {
        try {
            java.lang.reflect.Method ms = cn.learn.Action.class.getMethod("move");
            in.invoke(this, ms);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

这样还不算完,这时我们执行会陷入死循环



这里原因很简单,因为生成的代理对象我们把它自己传给了InvocationHandler 执行了其中的invoke方法,然后执行的对象是生成的代理对象,然后又自己调用了自己的方法。
以move方法为例
首先
Test in.invoke(this,ms) --> TalkHandler void invoke(Object o,Method m)
由于this和Object o 是同一对象所以当执行TalkHandler m.invoke(o,new Object[]{});时相当于调用了 Test中的public void speak() 方法,然后speak方法里又重新来一次,就陷入了死循环。
所以做出如下修改

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author: YangZaining
 * @Date: Created in 23:26$ 2018/8/11$
 */
public class TalkHandler implements InvocationHandler {
    private Object obj;

    public TalkHandler(Object obj) {//传入所需要代理的对象
        this.obj = obj;
    }

    public Object getObj() {
        return obj;
    }

    public void setObj(Object obj) {
        this.obj = obj;
    }

    @Override
    public void invoke(Object o, Method m) {
        System.out.println("我前进了一步");
        try {
            m.invoke(obj, new Object[]{});//调用obj
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println("我后退了一步");
    }
}

然后进行测试

/**
 * @Author: YangZaining
 * @Date: Created in 20:29$ 2018/8/11$
 */
public class Main {
    public static void main(String[] args) {
        try {
         Action action = (Action) Proxy.newProxynewInstance(Action.class,new TalkHandler(new ActionImpl()));
         action.move();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
output:
CompilerName:com.sun.tools.javac.api.JavacTool@85ede7b
class cn.learn.Test
我前进了一步
移动了。。。。
我后退了一步

JAVA API中的Proxy和InvocationHandler实现了更多细节,还需要阅读文档


我们能发现一个问题既然InvocationHandler中的Object o 没有用到,是不是可以去掉呢,答案是显然的,只是当我们要取生成代理类的某些方法或属性时,需要用到而已

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容