美文网首页
动态代理

动态代理

作者: yzn2015 | 来源:发表于2018-08-12 00:48 被阅读0次

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

首先定义一个接口
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 没有用到,是不是可以去掉呢,答案是显然的,只是当我们要取生成代理类的某些方法或属性时,需要用到而已

相关文章

  • 面试系列~动态代理实现与原理

    动态代理有JDK动态代理, CGLIB动态代理, SpringAOP动态代理 一,JDK动态代理  jdk动态代理...

  • 编程常用的设计模式

    动态代理和静态代理 静态代理 动态代理 静态代理与动态代理的区别 JDK中的动态代理和CGLIB 实现动态代理的方...

  • Spring的AOP原理分析

    一 动态代理 动态代理分为JDK动态代理和CGLIB动态代理 jdk动态代理 被代理类(目标类)和代理类必须实现同...

  • 设计模式之代理模式

    代理分为静态代理和动态代理。 动态代理又包括基于JDK的动态代理、基于CGlib 的动态代理、基于Aspectj实...

  • Java高级主题(五)——动态代理

    代理可以分为静态代理、动态代理,动态代理又可以分为 jvm的动态代理 和 cglib的动态代理。像spring框架...

  • 动态代理

    动态代理分为两类:1、基于接口的动态代理; (JDK动态代理 )2、基于类的动态代理;(cglib动态代理)3、J...

  • 动态代理的两种方式

    静态代理就不说了,基本用到的都是动态代理。 Java中动态代理有JDK动态代理和CGLIB动态代理。 JDK代理的...

  • Java动态代理

    通过以下几种方式介绍动态代理 动态代理涉及到的类 动态代理用法 Proxy类解析 动态代理类解析 动态代理涉及到的...

  • Spring之代理模式

    九、代理模式 目录:静态代理、动态代理AOP的底层机制就是动态代理。代理模式分为静态代理和动态代理。接触aop之前...

  • Java 代理

    静态代理 动态代理 动态代理, 日志切片使用反射获得方法 动态代理, 自定义注解(对注解的方法,使用动态代理添加切...

网友评论

      本文标题:动态代理

      本文链接:https://www.haomeiwen.com/subject/gtzdbftx.html