Home >Java >javaTutorial >Java's two dynamic proxies: the proxy types generated by jdk and cglib and how to implement them

Java's two dynamic proxies: the proxy types generated by jdk and cglib and how to implement them

php是最好的语言
php是最好的语言Original
2018-07-28 15:44:443497browse

What are the two types of dynamic proxies in Java? They are jdk proxy and cglib. Today we will discuss the two dynamic proxies in Java. What should the final generated proxy class look like and how to implement the proxy? apache php mysql

Friends who have participated in Java interviews may know that interviewers like to ask questions such as how Spring AOP is implemented, so I sorted out the questions and wrote this article. The concepts of AOP and proxy mode will not be explained in detail here. Let’s just talk about the topic directly, that is, the implementation method of AOP: Dynamic Proxy. Compared with static proxies, dynamic proxies dynamically generate Java proxy classes at runtime, and the proxy classes complete the encapsulation of specific methods and realize the functions of AOP.

These are my personal arrangements and thoughts. The results may not be the same as those produced by real jdk and cglib, but they are basically the same in principle.

At the end of the article, I will also discuss how to implement a simple dynamic proxy myself, and provide a simple version of my own implementation, of course for reference only.

1. JDK Proxy

This is the dynamic proxy method provided by the Java reflection package java.lang.reflect. This proxy method is completely based on the interface. Here is a simple example first.

Define the interface:

interface ifc {
  int add(int, int);
}

Then the implementation class of the interface ifcReal:

class Real implements ifc {
  @Override
  public int add(int x, int y) {
    return x + y;
  }

Real is the class we need to proxy. For example, we want to print some logs before and after calling add. This is actually AOP. We need to eventually generate a proxy class that implements the same interface ifc and performs the function of Real.add, but needs to add a new line of print statements. All this is transparent to the user, and the user only needs to care about the interface call. In order to add additional code around Real.add, dynamic proxies are implemented through something similar to a method interceptor, which is in Java Proxy InvocationHandler.

class Handler implements InvocationHandler {
  private final Real real;

  public Handler(Real real) {
    this.real = real;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws IllegalAccessException, IllegalArgumentException,
             InvocationTargetException {
    System.out.println("=== BEFORE ===");
    Object re = method.invoke(real, args);
    System.out.println("=== AFTER ===");
    return re;
  }
}

The most critical thing here is the invoke method. In fact, the add method of the proxy class, and other methods (if the interface also defines other method), in the end it just calls the invoke method of this Handler, and it is up to you to specifically define what needs to be done in invoke, usually by calling the real entity class Real method, here is add, and additional AOP behavior (printing BEFORE and AFTER). So it is conceivable that there must be an instance of InvocationHandler in the proxy class, and all interface method calls will be proxied by this handler instance.

So we should be able to roughly describe what this proxy class looks like:

public ProxyClass implements ifc {
  private static Method mAdd;

  private InvocationHandler handler;

  static {
    Class clazz = Class.forName("ifc");
    mAdd = clazz.getMethod("add", int.class, int.class);
  }
  
  @Override
  public int add(int x, int y) {
    return (Integer)handler.invoke(this, mAdd, new Object[] {x, y});
  }
}

This version is very simple, but it is enough to achieve our requirements. Let's observe this class. First of all, there is no doubt that it implements the ifc interface, which is the foundation of the proxy mode. Its add method directly calls the invoke method of the InvocationHandler instance, passing in three parameters. The first is the this pointer of the proxy class itself, and the second is addThe reflection class of the method, the third one is the parameter list. So in the invoke method, users can freely define its behavior to implement AOP. The bridge for all this is InvocationHandler, which completes the interception and proxy of the method.

The proxy mode generally requires that there be an instance of the real class (the proxied class) in the proxy class, which is an instance of Real, so that the proxy class can call Real# The original add method in ##. Where is Real? The answer is also in InvocationHandler. Compared with the standard agency model, this seems to have an extra layer of nesting, but this does not matter. As long as the agency chain can be built, it meets the requirements of the agency model.

Notice the initialization method of the reflection instance

mAdd of the add method here, we use the static block static {...} to complete, Will only be set once and will not cause multi-threading issues. Of course, you can also use lazy loading and other methods, but you have to consider the safety of concurrency.

Finally, let’s take a look at the specific use of

JDK Proxy:

Handler handler = new Handler(new Real());
ifc p = (ifc)Proxy.newProxyInstance(ifc.class.getClassLoader(),
                                    new Class[] {ifc},
                                    handler);
p.add(1, 2);

method

newProxyInstance will dynamically generate a proxy class and return an instance to us. Implements the ifc interface. This method requires three parameters. The first ClassLoader is not important; the second is the interface list, that is, which interfaces this proxy class needs to implement, because JDK's Proxy is completely based on interfaces, and it encapsulates the methods of the interface instead of Entity class; the third parameter is the instance of InvocationHandler, which will be placed in the final proxy class as a bridge between method interception and proxy. Note that handler here contains an instance of Real, which is an inevitable requirement of the proxy mode as mentioned above.

总结一下JDK Proxy的原理,首先它是完全面向接口的,其实这才是符合代理模式的标准定义的。我们有两个类,被代理类Real和需要动态生成的代理类ProxyClass,都实现了接口ifc。类ProxyClass需要拦截接口ifc上所有方法的调用,并且最终转发到实体类Real上,这两者之间的桥梁就是方法拦截器InvocatioHandlerinvoke方法。

上面的例子里我给出类ProxyClass的源代码,当然实际上JDK Proxy是不会去产生源代码的,而是直接生成类的原始数据,它具体是怎么实现我们暂时不讨论,我们目前只需要关心这个类是什么样的,以及它实现代理的原理。

二、cglib实现动态代理

这是Spring使用的方式,与JDK Proxy不同之处在于它不是面向接口的,而是基于类的继承。这似乎是有点违背代理模式的标准格式,不过这没有关系,所谓的代理模式只是一种思想而不是严格的规范。我们直接看它是如何使用的。

现在没有接口,我们直接有实体类:

class Real {
  public int add(int x, int y) {
    return x + y;
  }
}

类似于InvocationHandler,这里cglib直接使用一个叫MethodInterceptor的类,顾名思义。

public class Interceptor implements MethodInterceptor {
  @Override
  public Object intercept(Object obj,
                          Method method,
                          Object[] args,
                          MethodProxy proxy) throws Throwable {
      System.out.println("=== BEFORE ===");
      Object re = proxy.invokeSuper(obj, args);
      System.out.println("=== AFTER ===");
      return re;
  }
}

使用方法:

public static void main(String[] args) {
  Enhancer eh = new Enhancer();
  eh.setSuperclass(Real.class);
  eh.setCallback(new Interceptor());

  Real r = (Real)eh.create();
  int result = r.add(1, 2);
}

如果你仔细和JDK Proxy比较,会发现它们其实是类似的:

  1. 首先JDK Proxy提供interface列表,而cglib提供superclass供代理类继承,本质上都是一样的,就是提供这个代理类的签名,也就是对外表现为什么类型。

  2. 然后是一个方法拦截器,JDK Proxy里是InvocationHandler,而cglib里一般就是MethodInterceptor,所有被代理的方法的调用都是通过它们的invoke方法进行转接的,AOP的逻辑也是在这一层实现。

它们不同之处上面已经说了,就在于cglib生成的动态代理类是直接继承原始类的,所以我们这里也可以大概刻画出这个代理类长什么样子:

public ProxyClass extends Real {
  private static Method mAdd;
  private static MethodProxy mAddProxy;

  private MethodInterceptor interceptor;

  static {
    Class clazz = Class.forName("ifc");
    mAdd = clazz.getMethod("add", int.class, int.class);
    // Some logic to generate mAddProxy.
    // ...
  }
  
  @Override
  public int add(int x, int y) {
    return (Integer)interceptor.invoke(
        this, mAdd, new Object[] {x, y}, mAddProxy);
  }
}

因为直接继承了Real,那自然就包含了Real的所有public方法,都通过interceptor.invoke进行拦截代理。这其实和上面JDK Proxy的原理是类似的,连invoke方法的签名都差不多,第一个参数是this指针代理类本身,第二个参数是方法的反射,第三个参数是方法调用的参数列表。唯一不同的是,这里多出一个MethodProxy,它是做什么用的?

如果你仔细看这里invoke方法内部的写法,当用户想调用原始类(这里是Real)定义的方法时,它必须使用:

Object re = proxy.invokeSuper(obj, args);

这里就用到了那个MethodProxy,那我们为什么不直接写:

Object re = method.invoke(obj, args);

答案当然是不可以,你不妨试一下,程序会进入一个无限递归调用。这里的原因恰恰就是因为代理类是继承了原始类的,obj指向的就是代理类对象的实例,所以如果你对它使用method.invoke,由于多态性,就会又去调用代理类的add方法,继而又进入invoke方法,进入一个无限递归:

obj.add() {
  interceptor.invoke() {
    obj.add() {
      interceptor.invoke() {
        ...
      }
    }
  }
}

那我如何才能在interceptor.invoke()里去调用基类Realadd方法呢?当然通常做法是super.add(),然而这是在MethodInterceptor的方法里,而且这里的method调用必须通过反射完成,你并不能在语法层面上做到这一点。所以cglib封装了一个类叫MethodProxy帮助你,这也是为什么那个方法的名字叫invokeSuper,表明它调用的是原始基类的真正方法。它究竟是怎么办到的呢?你可以简单理解为,动态代理类里会生成这样一个方法:

int super_add(int x, int y) {
  return super.add(x, y);
}

当然你并不知道有这么一个方法,但invokeSuper会最终找到这个方法并调用,这都是在生成代理类时通过一系列反射的机制实现的,这里就不细展开了。

小结

对比JDK Proxycglib动态代理的使用方法和实现上的区别,就会发现,它们本质上都差不多,都是提供两个最重要的东西:

  1. 接口列表或者基类,定义了代理类(当然也包括原始类)的签名。

  2. 一个方法拦截器,完成方法的拦截和代理,是所有调用链的桥梁。

It should be noted that the source code of the proxy classProxyClass I gave above is only the most streamlined version for reference, just to illustrate the principle, notJDK Proxy and cglib look like the actual generated proxy classes. The logic of the real proxy classes is much more complicated, but the principles are basically the same. In addition, as mentioned before, in fact they do not generate source code, but directly generate class bytecode. For example, cglib encapsulates ASM to directly generate Class data. .

How to generate a proxy class

We have learned what a proxy class is, and then we will introduce how to generate a proxy class. I have compiled two plans based on the information:

One method is to dynamically generate the ProxyClass source code, and then dynamically compile it to get the Class. Here you need to use reflection and add a series of string splicing to generate source code. It's not that difficult to do if you fully understand what the proxy class should look like. So how to compile dynamically? You can use JOOR, which is a library that encapsulates javax.tools.JavaCompiler to help you easily dynamically compile Java source code. I tried to write a demo, which was purely experimental. And it has a major problem. I don’t know how to modify the classpath it uses for compilation. By default, it cannot reference any classes you define yourself, because they are not in the compiled classpath, and the compilation will not pass. This is actually This makes this code generator useless. . . I forcibly bypassed this problem by modifying classpath of System.setProperty to add my class path. However, this is obviously not a solution to the fundamental problem.

The second method is more direct, which is to generate the bytecode of the class. This is also the method used by cglib. It encapsulates ASM, which is a library that can be used to directly manipulate Class data. Through it, you can generate or modify the Class you want. Of course, this requires you Only by having a good understanding of the bytecode of the virtual machine can you master this relatively black technology routine. I have also written a Demo here, which is purely an experiment. Interested children can also try it on their own. Writing bytecode is quite refreshing. It is similar to assembly but actually much easier than assembly. It is not like assembly, with registers and memory addresses, heaps and stacks, and various variables and addresses going around. The execution method of bytecode is very clear. Variables are stored in local variable tables, and the stack is only used for function calls, so it is very intuitive.

Related articles:

Detailed explanation of the two dynamic proxies of cglib and jdk

Detailed explanation of the codes of JDK and cglib

Related videos:

Overview of JDK and JRE)-JAVA Basic Introductory Video Tutorial

The above is the detailed content of Java's two dynamic proxies: the proxy types generated by jdk and cglib and how to implement them. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn