欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深入理解代理模式

程序员文章站 2022-06-28 22:30:12
一、目的 在代理模式(一)里面我们了解了静态代理、动态代理、CGLIB代理的基本使用。 这篇主要来将JDK动态代理底层的原理,以及有关$Proxy0、InvocationHandler相关的原理。 二、模拟Java底层动态代理实现 1、模拟TimeTankProxy但是封装在MyProxy中 只是将 ......

一、目的

在代理模式(一)里面我们了解了静态代理、动态代理、cglib代理的基本使用。

这篇主要来将jdk动态代理底层的原理,以及有关$proxy0、invocationhandler相关的原理。

二、模拟java底层动态代理实现

1、模拟timetankproxy但是封装在myproxy

只是将tanktimeproxy封装在了proxy内部,我们用动态编译(jdk1.6 complier)在程序运行的时候动态生成tanktimeproxy类。

缺点: 只能对tanktimeproxy进行代理

下面我们初步模拟jdk内部动态代理,因为动态代理就是不能看到代理类,所以我们将代理类写到myproxy内部,在程序运行的时候动态生成。

public interface movable {
    void move();
}
public class tank implements movable {
    @override
    public void move() {
        // 坦克移动
        system.out.println("tank moving......");
        try {
            thread.sleep(new random().nextint(5000)); // 随机产生 1~5秒, 模拟坦克在移动 
        } catch (interruptedexception e) {
            e.printstacktrace();
        }
    }
}

 

下面看我们很重要的myproxy类,它有一个静态方法newproxyinstance()可以用来生成代理对象:

注意这里 package proxy.advance.one是我要动态编译生成的那个代理类tanktimeproxy最后生成所在的包。

public class myproxy {

    // 用来产生代理类
    public static object newproxyinstance() throws exception{
        string rt = "\n\r";

        //动态代理文件的源码 : 需要动态编译代码
        string src = "package proxy.advance.one;" + rt +
                "public class tanktimeproxy implements movable {" + rt +
                "      private movable tank;" + rt +
                "      public tanktimeproxy(movable tank) {" + rt +
                "             this.tank = tank;" + rt +
                "      }" + rt +
                "     @override" + rt +
                "     public void move() {" + rt +
                "          long start = system.currenttimemillis();" + rt +
                "          system.out.println(\"start time : \" + start);" + rt +
                "          tank.move();" + rt +
                "          long end = system.currenttimemillis();" + rt +
                "          system.out.println(\"end time : \" + end);" + rt +
                "          system.out.println(\"spend all time : \" + (end - start)/1000 + \"s.\");" + rt +
                "      }" + rt +
                "}";

        //把源码写到java文件里
        file file = new file("/home/zxzxin/java_maven/designpatterns/src/main/java/proxy/advance/one/tanktimeproxy.java");
        filewriter fw = new filewriter(file);
        fw.write(src);  fw.flush(); fw.close();

        //下面的代理,就是动态编译
        //编译源码,生成class,注意编译环境要换成jdk才有compiler,单纯的jre没有compiler,会空指针错误
        javacompiler jc = toolprovider.getsystemjavacompiler();
        standardjavafilemanager filemgr = jc.getstandardfilemanager(null, null, null);//文件管事器
        iterable units = filemgr.getjavafileobjects(file); //编译单元
        javacompiler.compilationtask t = jc.gettask(null, filemgr, null, null, null, units);//编译任务
        t.call();
        filemgr.close();

        //把类load到内存里 并 生成新对象       !!!!!注意:下面的home前面不要加 /
        url[] urls = new url[]{new url("file:/" + "home/zxzxin/java_maven/designpatterns/src/main/java/")};
        urlclassloader ul = new urlclassloader(urls);
        class c = ul.loadclass("proxy.advance.one.tanktimeproxy");

        //生成实例return c.newinstance();   //c.newinstance()会调用无参数的constructor,若类没有无参的constructor时会出错
        constructor ctr = c.getconstructor(movable.class);   // 可以得到带有参数的构造方法()
        return ctr.newinstance(new tank());
    }
}

 

测试:

public class client {
    public static void main(string[] args) throws exception {
        movable tank = new tank();
        // 现在就是说删除了tanktimeproxy,还是要能实现动态代理
        movable tankproxy = (movable) myproxy.newproxyinstance(); // 动态代理不需要写出代理类的名字
        tankproxy.move();
    }
}

 

输出: (和我们使用jdk动态代理一样的)

start time : 1551318534681
tank moving......
end time : 1551318536469
spend all time : 1s.

 

我们动态生成编译的类也在当前包下:

 

深入理解代理模式

 

2、可以对任意接口进行代理,且可以自定义代理逻辑

上面虽然实现了对jdk的动态代理的模拟,但是却只能对movable接口进行代理,而且代理的逻辑只能是timeproxy,下面我们来改进myproxy类:

这里我们里面的那个字符串拼接代理类,不叫tanktimeproxy了,暂且叫$myproxy0

  • 首先,要实现对任意接口的任意方法代理:
    • 实现代理任意接口: 我们要把接口类型作为参数传给myproxynewproxyinstance(class inface)
    • 代理接口的所有方法: 用inface.getmethods()取出所有方法,拼接实现方法的字符串(反射);

 

  • 要实现代理的任意逻辑:
    • 要把代理逻辑抽离,应独立出一个策略接口: myinvocationhandler接口,并接收被代理的对象及方法作为参数invoke(object o, method m);
    • 而且需要把本身作为参数传给myproxy的静态方法newproxyinstance(class inface, myinvocationhandler h) ;
    • 而且我们具体的策略(即myinvocationhandler的实现类)本身聚合被代理类target,以便在target的方法前后增加代理逻辑;
    • 而且其中很重要的一步: 我们需要把myinvocationhandler作为成员遍历放到$myproxy0中,而且在每一个代理方法内部,要调用被代理对象的原始方法,具体就是下面两行:

" method md = " + inface.getname() + ".class.getmethod(\"" + m.getname() + "\");" + rt + //这个接口传入了 ,注意一定要写inface.getname " h.invoke(this, md);" + rt + 比如传入movable接口,里面有move()方法,则上面生成的代码是这样: method md = proxy.advance.two.flyable.class.getmethod("fly"); h.invoke(this, md);

讲的差不多了,现在看代码实现:

深入理解代理模式

代码:

/**
 * 能处理任何方法的    调用  只要给我一个method就能对这个方法进行特殊的处理
 * 特殊处理的方式是由子类(实现类)决定
 */
public interface myinvocationhandler {
    void invoke(object o, method m);
}
最重要的myproxy类,传入了两个参数,分别是可以指定任意接口,以及指定任意逻辑。

public class myproxy {

    public static object newproxyinstance(class inface, myinvocationhandler h) throws exception {
        string rt = "\n\r";
        string methodstr = "";
        method[] methods = inface.getmethods(); //获取接口的所有方法 , 为所有这些方法都生成代理
        /*
        原来固定的思路 : 只能对时间代理
        for(method m : methods) {
            methodstr += "@override" + rt +
                         "public void " + m.getname() + "() {" + rt +
                             "   long start = system.currenttimemillis();" + rt +
                            "   system.out.println(\"start time : \" + start);" + rt +
                            "   t." + m.getname() + "();" + rt +
                            "   long end = system.currenttimemillis();" + rt +
                            "   system.out.println("spend all time : " + (end - start)/1000 + "s.");" + rt +
                         "}";
        }
        */
        for (method m : methods) {
            methodstr += "    @override" + rt +
                    "    public void " + m.getname() + "(){" + rt +
                    "       try {" + rt +
                    "           method md = " + inface.getname() + ".class.getmethod(\"" + m.getname() + "\");" + rt +   //这个接口传入了 ,注意一定要写inface.getname
                    "           h.invoke(this, md);" + rt +
                    "       }catch(exception e) {" + rt +
                    "           e.printstacktrace();" + rt +
                    "       }" + rt +
                    "   }";
        }

        string src =
                "package proxy.advance.two;" + rt +
                        "import java.lang.reflect.method;" + rt +
                        "public class my$proxy0 implements " + inface.getname() + "{" + rt +
                        "    proxy.advance.two.myinvocationhandler h;" + rt + //定义成员变量 myinvocationhandler对象
                        "    public my$proxy0(myinvocationhandler h) {" + rt +
                        "        this.h = h;" + rt +
                        "    }" + rt +
                        methodstr + rt +
                        "}";

        //把源码写到java文件里
        file file = new file("/home/zxzxin/java_maven/designpatterns/src/main/java/proxy/advance/two/my$proxy0.java");
        filewriter fw = new filewriter(file);
        fw.write(src);
        fw.flush();
        fw.close();

        //下面的代理,就是动态编译
        //编译源码,生成class,注意编译环境要换成jdk才有compiler,单纯的jre没有compiler,会空指针错误
        javacompiler jc = toolprovider.getsystemjavacompiler();
        standardjavafilemanager filemgr = jc.getstandardfilemanager(null, null, null);//文件管事器
        iterable units = filemgr.getjavafileobjects(file); //编译单元
        javacompiler.compilationtask t = jc.gettask(null, filemgr, null, null, null, units);//编译任务
        t.call();
        filemgr.close();

        //把类load到内存里 并 生成新对象       !!!!!注意:下面的home前面不要加 /
        url[] urls = new url[]{new url("file:/" + "home/zxzxin/java_maven/designpatterns/src/main/java/")};
        urlclassloader ul = new urlclassloader(urls);
        class c = ul.loadclass("proxy.advance.two.my$proxy0");
//        system.out.println("class c : " + c);

        // 这是之前的
        //  生成实例return c.newinstance();   //c.newinstance()会调用无参数的constructor,若类没有无参的constructor时会出错
//        constructor ctr = c.getconstructor(movable.class);   // 可以得到带有参数的构造方法()
//        return ctr.newinstance(new tank());
        constructor ctr = c.getconstructor(myinvocationhandler.class);  // 哪个处理器实现,就创建这个类的实例对象 
        object m = ctr.newinstance(h);
        return m;
    }
}

 

看一个指定时间逻辑的实现类:

public class mytimeinvocationhandler implements myinvocationhandler {

    private object target; //注意是 object,这样可以对任意对象进行时间的代理

    public mytimeinvocationhandler(object target) {
        this.target = target;
    }

    @override
    public void invoke(object proxy, method m) {
        // 在前面做一些事情: 记录开始时间
        long start = system.currenttimemillis();
        system.out.println("start time : " + start);
        system.out.println("proxy : " + proxy.getclass().getname()); // 打印proxy 到底是什么
        system.out.println("target : " + target.getclass().getname()); // 打印 target 到底是什么
        try {
            m.invoke(target); // 调用 target的方法
        } catch (exception e) {
            e.printstacktrace();
        }
        long end = system.currenttimemillis();
        system.out.println("end time : " + end);
        system.out.println("spend all time : " + (end - start) / 1000 + "s.");
    }
}

 

测试: (这里我加了一个flyableplane),测试可以放入任意接口(这两个类在代理模式(一)中也有,代码很简单,就不贴了):

// 可以生成实现了任何接口的代理, 只要把接口传进去就可以了
public class client {
    public static void main(string[] args) throws exception {
        movable tank = new tank();
        myinvocationhandler timehandler = new mytimeinvocationhandler(tank);
        movable tankproxy = (movable) myproxy.newproxyinstance(movable.class, timehandler); // 传入类的.class即可
        tankproxy.move();

        system.out.println("--------------------");

        flyable plane = new plane();
        timehandler = new mytimeinvocationhandler(plane);
        flyable planeproxy = (flyable) myproxy.newproxyinstance(flyable.class, timehandler);
        planeproxy.fly();
    }
}

 

输出:

深入理解代理模式

看我们在包下生成的myproxy0类的内容:

 

深入理解代理模式

 

现在再看这个整体的框架联系图,应该就比较清晰了:

深入理解代理模式

三、总结

在上面的主程序测试类中,当调用tank.move()的时候,就会调用invoke(this, md),而这个md就是具体实现myinvocationhandler接口的mytimeproxyinvocation的方法, 也就是invoke()(在这个方法中我们在前后加了自己的逻辑)方法。

免费java高级资料需要自己领取,涵盖了java、redis、mongodb、mysql、zookeeper、spring cloud、dubbo高并发分布式等教程,一共30g。
传送门: