Java beauty [from cainiao to expert drill]: Implementation and principle of JDK dynamic proxy, javajdk

Source: Internet
Author: User

Java beauty [from cainiao to expert drill]: Implementation and principle of JDK dynamic proxy, javajdk

Implementation and principle of JDK dynamic proxy

Author: erqing

Mailbox: xtfggef@gmail.com Weibo: http://weibo.com/xtfggef

Dynamic proxy, which sounds very high, is widely used in Java, especially in Hibernate and Spring frameworks. In AOP, permission control, transaction Management and other aspects have the Implementation of Dynamic proxy. JDK has the dynamic proxy technology, but it has some limitations. That is, the class to be proxy must implement an interface. Otherwise, the built-in dynamic proxy of JDK cannot be used. Therefore, if the conditions are not met, you can only use another dynamic proxy technology, CGLIB, which is more flexible and powerful. Spring will automatically switch between the JDK proxy and CGLIB, and we can also force Spring to use CGLIB. Next we will introduce the knowledge points of dynamic Proxy from the beginning to the end.

Let's take an example:

Create an interface named UserService. java. There is only one method to add ().

package com.adam.java.basic;public interface UserService {public abstract void add();}

Create an implementation class UserServiceImpl. java for this interface

package com.adam.java.basic;public class UserServiceImpl implements UserService {@Overridepublic void add() {System.out.println("----- add -----");}}

Create a proxy processing class MyInvocationHandler. java

package com.adam.java.basic;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class MyInvocationHandler implements InvocationHandler {private Object target;public MyInvocationHandler(Object target) {super();this.target = target;}public Object getProxy() {return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {System.out.println("----- before -----");Object result = method.invoke(target, args);System.out.println("----- after -----");return result;}}

Test class

package com.adam.java.basic;public class DynamicProxyTest {public static void main(String[] args) {UserService userService = new UserServiceImpl();MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);UserService proxy = (UserService) invocationHandler.getProxy();proxy.add();}}

Run the test class and get the following output:
----- Before -----
----- Add -----
----- After -----
Here, we should think of some problems:
1. who generated the proxy object?
2. How is the invoke method called?
3. What is the correspondence between invoke and add methods?
4. What does the generated proxy object look like?
With these questions, let's take a look at the source code. First of all, our entrance is the getProxy () method in the test class above. Let's go in and see this method:

public Object getProxy() {return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(),this);}

That is to say, JDK's dynamic Proxy is implemented through a Proxy class. Let's continue with it and look at the newProxyInstance () method of the Proxy class. Let's take a look at the JDK Annotations:

/**     * Returns an instance of a proxy class for the specified interfaces     * that dispatches method invocations to the specified invocation     * handler.     *     * <p>{@code Proxy.newProxyInstance} throws     * {@code IllegalArgumentException} for the same reasons that     * {@code Proxy.getProxyClass} does.     *     * @param   loader the class loader to define the proxy class     * @param   interfaces the list of interfaces for the proxy class     *          to implement     * @param   h the invocation handler to dispatch method invocations to     * @return  a proxy instance with the specified invocation handler of a     *          proxy class that is defined by the specified class loader     *          and that implements the specified interfaces

According to JDK annotations, we know that the newProxyInstance method will eventually return an instance of the class implementing the specified interface. The three parameters are ClassLoader, the specified interface and the InvocationHandler class we have defined. I will extract several key codes to see how the proxy class instance objects are generated.

Class<?> cl = getProxyClass0(loader, intfs);...final Constructor<?> cons = cl.getConstructor(constructorParams);...return cons.newInstance(new Object[]{h});

If you are interested, you can check the JDK source code by yourself. The current version is JDK 1.8.25. The implementation methods of each version may be different, but they are basically the same. Please pay attention to this when studying the source code. The code above indicates that the proxy class is obtained through getProxyClass first, then the constructor () is obtained through c1.getConstructor (), and the last step is through cons. newInstance returns an instance of this new proxy class. Note: When calling newInstance, the input parameter is h, that is, the InvocationHandler class we have defined. Remember this step first, we will know the reason for doing so later.

In fact, the core of these three codes is the getProxyClass method. The other two lines of code are Java reflection applications, which have nothing to do with our current points of interest. So we continue to study this getProxyClass method. The comment for this method is as follows:

/*         * Look up or generate the designated proxy class.         */        Class<?> cl = getProxyClass0(loader, intfs);

This key proxy class is generated. Let's take a look.

private static Class<?> getProxyClass0(ClassLoader loader,                                           Class<?>... interfaces) {        if (interfaces.length > 65535) {            throw new IllegalArgumentException("interface limit exceeded");        }        // If the proxy class defined by the given loader implementing        // the given interfaces exists, this will simply return the cached copy;        // otherwise, it will create the proxy class via the ProxyClassFactory        return proxyClassCache.get(loader, interfaces);    }

The cache is used here. First, check from the cache. If yes, return directly. If no, create a new one. In this get method, we see the following code:
Object subKey = Objects. requireNonNull (subKeyFactory. apply (key, parameter ));
The apply () method is used to implement the interface of the Proxy class's internal class ProxyClassFactory. The specific implementation is as follows:

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);            for (Class<?> intf : interfaces) {                /*                 * Verify that the class loader resolves the name of this                 * interface to the same Class object.                 */                Class<?> interfaceClass = null;                try {                    interfaceClass = Class.forName(intf.getName(), false, loader);                } catch (ClassNotFoundException e) {                }                if (interfaceClass != intf) {                    throw new IllegalArgumentException(                        intf + " is not visible from class loader");                }...

When I saw Class. forName (), I thought most people would laugh and finally saw the familiar method. That's right! This is to load the specified interface. Since the class is generated, the corresponding class bytecode is required. Let's continue to look at it:

/* * Generate the specified proxy class. */   byte[] proxyClassFile = ProxyGenerator.generateProxyClass(   proxyName, interfaces, accessFlags);    try {          return defineClass0(loader, proxyName,          proxyClassFile, 0, proxyClassFile.length);

This Code uses ProxyGenerator to generate the bytecode file of the final proxy class for us, that is, the final return value of the getProxyClass0 () method. So let's review the first four questions:

1. who generated the proxy object?

2. How is the invoke method called?

3. What is the correspondence between invoke and add methods?

4. What does the generated proxy object look like?

For the first question, I think the answer is clear. I will try again and again: the Proxy class is generated by the getProxyClass0 () method of the Proxy class, and then the constructor of the class is obtained, finally, the newInstance method is reflected to generate the Instance Object of the proxy class.

Next, let's take a look at the other three methods. I 'd like to start with Step 4 first, because with the above Code for generating bytecode, We can imitate this step, I generated the final proxy class using the following code.

package com.adam.java.basic;import java.io.FileOutputStream;import java.io.IOException;import sun.misc.ProxyGenerator;public class DynamicProxyTest {public static void main(String[] args) {UserService userService = new UserServiceImpl();MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);UserService proxy = (UserService) invocationHandler.getProxy();proxy.add();String path = "C:/$Proxy0.class";byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",UserServiceImpl.class.getInterfaces());FileOutputStream out = null;try {out = new FileOutputStream(path);out.write(classFile);out.flush();} catch (Exception e) {e.printStackTrace();} finally {try {out.close();} catch (IOException e) {e.printStackTrace();}}}}

Proxy in the above test method. add (), the add () method here is no longer the add () method in the original UserService, but the add () method of the newly generated proxy class, we open the generated $ Proxy0.class file with jd-gui. I removed some code. The add () method is as follows:

public final void add()    throws   {    try    {      this.h.invoke(this, m3, null);      return;    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }

The core is this. h. invoke (this. m3, null). What is h here? Let's take a look at the class name of this class:

Public final class $ Proxy0 extends Proxy implements UserService

It is not hard to find that the newly generated class inherits the Proxy class to implement the UserService method, and this UserService is the interface we specify. Therefore, we can conclude that, JDK dynamic Proxy, the new Proxy class generated is the class that inherits the Proxy Base class and implements the passed interface. So what is h? Let's take a look at this new proxy class and look at the constructor:

public $Proxy0(InvocationHandler paramInvocationHandler)    throws   {    super(paramInvocationHandler);  }

A InvocationHandler type parameter is input in the constructor. Here, we should think of the previous line of code:

Return cons. newInstance (new Object [] {h });

This is the last sentence of the newInstance method. The input h is the h used here, that is, the MyInvocationHandler class instance we initially defined. Therefore, we found that the add () method actually called is the invoke () method of MyInvocationHandler. Let's take a look at this method, find out the meaning of m3, and continue to look at the source code of the proxy class:

static  {    try    {      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);      m3 = Class.forName("com.adam.java.basic.UserService").getMethod("add", new Class[0]);      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);      return;    }

I was pleasantly surprised to find that the m3 is the add () method of the original interface. What else do I understand here? I think I should solve all the problems 2, 3, and 4? Let's continue and look at the invoke () method in the original MyInvocationHandler:

@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {System.out.println("----- before -----");Object result = method.invoke(target, args);System.out.println("----- after -----");return result;}

M3 is the method to be passed in. So, why do we output before first and then after? Is it completely clear here? This is the entire process of JDK dynamic proxy, isn't it difficult?

Finally, I will summarize the operations of JDK dynamic Proxy:

1. Define an interface with the methods to be implemented and write the actual implementation classes.

2. Define an InvocationHandler class, implement the InvocationHandler interface, override the invoke () method, and add the getProxy () method.

Summarize the dynamic proxy implementation process:

1. Use getProxyClass0 () to generate a proxy class.

2. Use Proxy. newProxyInstance () to generate an Instance Object of the Proxy class. When creating an object, input an instance of the InvocationHandler type.

3. Call the method of the new instance, that is, add () in this example, that is, the invoke () method in the original InvocationHandler class.

Well, after writing so much, it's time to end. Thanks to a Rejoy article for your reference. You are also welcome to ask questions and discuss them. If you have any questions, please leave a message and I will take the time to reply. The Code has been uploaded to the Baidu online storage ,.

Contact info:

Email: xtfggef@gmail.com

Weibo: http://weibo.com/xtfggef


  • References: http://rejoy.iteye.com/blog/1627405

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.