In-depth understanding of proxy mode

First, the purpose

In proxy mode (a) which we know the static proxy, dynamic proxies, CGLIB proxy basic uses.

This is mainly the JDKdynamic proxy for the underlying principles and the relevant $Proxy0、InvocationHandlerprinciples related.

Second, the underlying dynamic simulation Java proxy implementation

1, analog TimeTankProxybut encapsulated in MyProxythe

The only TankTimeProxyencapsulated in the interior Proxy, we dynamically compiled (JDK1.6 Complier) dynamically generated program runs TankTimeProxyclasses.

Cons: Only for TankTimeProxyproxy

Here we simulate preliminary JDKinternal dynamic agent, the agent is not seen as a dynamic proxy class, so we will write the proxy class MyProxyinterior, dynamically generated when the program is running.

public interface Movable {
    void move();
}
public class Tank implements Movable {
    @Override
    public void move() {
        // move the tank
        System.out.println("Tank Moving......");
        try {
            Thread.sleep (new Random () nextInt (5000).); // randomly generated 1 to 5 seconds, in simulated moving tanks 
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
    }
}

Here we see a very important MyProxyclass that has a static method newProxyInstance()can be used to generate a proxy object:

Note here  package proxy.advance.onethat I want to compile a dynamic proxy class generated by the TankTimeProxypackage where the last generation.

public class MyProxy {

    // for generating proxy classes
    public static Object newProxyInstance() throws Exception{
        String rt = "\n\r";

        // dynamic proxy source file: dynamically compiled code
        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 +
                "}";

        // The java source code written document
        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();

        // The following agents, is dynamically compiled
        // compile source code, generate class, pay attention to the build environment have to be replaced jdk compiler, simply jre no compiler, it will be a null pointer error
        JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr = jc.getStandardFileManager (null, null, null); // File affairs unit
        Iterable units = fileMgr.getJavaFileObjects (file); // coding unit
        JavaCompiler.CompilationTask t = jc.getTask (null, fileMgr, null, null, null, units); // compiler task
        t.call();
        fileMgr.close();

        // the class load into memory and generate a new object !!!!! Note: Do not add the following in front of 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");

        // create instances return c.newInstance (); //c.newInstance () Constructor calls without parameters, no error occurs if no arguments class Constructor
        Constructor ctr = c.getConstructor (Movable.class); // constructor can be obtained with parameters ()
        return ctr.newInstance(new Tank());
    }
}

test:

public class Client {
    public static void main(String[] args) throws Exception {
        Movable tank = new Tank();
        // Now that is removed TankTimeProxy, or to be able to achieve dynamic proxy
        Movable tankProxy = (Movable) MyProxy.newProxyInstance (); // write the name of the agent does not need dynamic proxy class
        tankProxy.move();
    }
}

Output: (and we use the same JDK dynamic proxies)

start time : 1551318534681
Tank Moving......
end time : 1551318536469
spend all time : 1s.

We compiled classes are dynamically generated under the current package:


v2-b78c3433855fac1fa4e91661745ba8d3_hd.jpg


2, the agent may be any of the interfaces, and can customize the proxy logic

Although the above to achieve the simulation of dynamic proxy of the JDK, but it can only Movableinterface agent, and the agent is only logical TimeProxy, let's improve MyProxycategories:

Here we are inside that string concatenation proxy class, do not call TankTimeProxy, and for the time being called $MyProxy0.

  • First of all, any method to achieve any interface agents:

    • Acting implement any interface: We need to interface type as a parameter to MyProxythe newProxyInstance(Class inface);

    • All methods proxy interfaces: The inface.getMethods()extracted string (reflection) for all approaches, the implementation of the method;


  • 要实现代理的任意逻辑:

    • 要把代理逻辑抽离,应独立出一个策略接口: 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);

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

v2-d46c8a6e386fd376a71c01344dbbd64c_hd.jpg

代码:

/**
 * 能处理任何方法的	调用  只要给我一个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();
    }
}

输出:

v2-6921aee46a8de1531f55160e8cc44fae_hd.jpg

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


v2-460f989ccacbd4ec111a5f3a7fc16593_hd.jpg


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

v2-f587dd7142d587db889dabf82ff44587_hd.jpg

三、总结

In the above main test class, when calling tank.move()time, will call invoke(this, md), and this mdis a concrete realization of MyInvocationHandlerthe interface MyTimeProxyInvocationmethod, that is invoke()(in this method we added their own logic before and after) method.

Free Java needs its own advanced data collection, covering Java, Redis, MongoDB, MySQL, Zookeeper, Spring Cloud, Dubbo distributed high concurrency and other tutorials, a total of 30G.
Portal: mp.weixin.qq.com/s/Jzdd


Guess you like

Origin blog.51cto.com/13672983/2407872