Author: Pan Pan

Cover: Luo Xiaoxi

“Cover art”

In fact, for many Java programmers, a beginner to hands-on level of programming is probably enough to get most of the work done. Unless you do it yourself, or your job forces you to do it, there seems to be no reason to really understand Java programming, or to delve into how the JDK works, or to write a design pattern for a module in the real world, or to obsess over a thread-safety issue.

I feel that there is no need to understand, because a lot of knowledge content, my technical reserve is only a point so far, can be competent to work, why in-depth? Indeed, I also like some friends, most of the time since 8 years programming career there is this kind of thought, until one day suddenly have the opportunity to come, you will be responsible for a system of architecture planning and design, you will be solely to ensure the safety of the whole enterprise informatization, you will be under the management of research and development of dozens of hundreds of brothers, but you know you’re good at the if/else, You know you wrote an order method that had 9000 lines of code, and you just got a call from an operations colleague saying that your CPU went 100% after a CERTAIN SQL execution…

For, this is you and me now or in the future, will face are unavoidable problems at the same time, so I think thinking or to an article on “read” series, we construct, with you a piece of research learning, from Java foundation to the core, from single frame to micro service cluster, from the database to the server, we all share together, grow simultaneously, On the road to Java, we should stay true to our original aspiration and lifelong learning.

Hence our first word, “Java dynamic proxy.”

If you have any suggestions or opinions on the content of this article, or hope to exchange and learn about Internet development, or simply love life, please feel free to wechat me: Panshenlian

The introduction

The earliest agency mode, we can roughly associate with The Three Kingdoms period, Lord Meng De coerced the emperor to order the princes to be the agency mode, is the power agency; Nowadays, real estate agents and ticketing agents are agents and business agents. Browsing the web is a proxy mode, VPN proxy; Back in our programming world, remote method calls (RMI) you used to use were proxies, enterprise JavaBeans (EJBs) were proxies, and many of the popular RPC frameworks such as Dubbo were proxies, including our Java dynamic proxies, which are object proxies.

Java dynamic proxy

The Java dynamic proxy mechanism enables Java developers to obtain proxy classes dynamically by simply specifying a set of interfaces and delegate class objects instead of writing proxy classes manually. The proxy class is responsible for dispatching all method calls to the delegate object for reflection execution. During dispatch execution, the developer can also adjust the delegate object and its functionality as needed. This is a very flexible and elastic proxy framework.

Proxy is a common design pattern whose purpose is to provide a proxy class for the caller to control access to the called. The proxy class is responsible for preprocessing messages for the delegate class, filtering and forwarding messages, and for subsequent processing of messages after they are executed by the delegate class.

As you can see, the proxy class and the delegate class implement the same interface, so there is no difference for client requests, which is a characteristic of Java interface oriented programming. The proxy mode uses proxy objects (proxy classes) to complete user requests, effectively shielding users from access to real objects (proxy classes), and also can well hide and protect proxy classes. At the same time, there is room to add different control strategies, thus gaining more flexibility in design. The Java dynamic proxy mechanism practices the design philosophy of the proxy pattern almost perfectly in a clever way.

Just as an agent in the real world is authorized to perform some of the client’s business without the client’s presence, from the perspective of a third party it seems that the client does not exist because he only communicates with the agent. In fact, the agent is to have the authorization of the parties, and in the core issues also need to ask the parties, and the agent in addition to forwarding the parties to complete the authorization, but also before and after the implementation of the authorization can add some additional affairs.

Proxy object = target object + enhanced transaction

Related classes and interfaces

To understand the mechanics of Java dynamic proxies, you first need to understand two related classes or interfaces:

  • Java.lang.reflect.proxy: This is the main class of Java’s dynamic Proxy mechanism, which provides a set of static methods to dynamically generate Proxy classes and their objects for a set of interfaces.
Listing 1. Static methods for Proxy
  
  // Method 1: This method is used to get the calling handler associated with the specified proxy object
  static InvocationHandler 
      getInvocationHandler(Object proxy)

  // Method 2: This method is used to get the class object associated with a dynamic proxy class that specifies a class loader and a set of interfaces
  static Class getProxyClass( ClassLoader loader, Class[] interfaces)

  // Method 3: This method is used to determine whether the specified class object is a dynamic proxy class
  static boolean isProxyClass(Class cl)

  // Method 4: This method is used to generate dynamic proxy class instances for the specified class loader, a set of interfaces, and the calling handler
  static Object newProxyInstance( ClassLoader loader, Class[] interfaces, InvocationHandler h)
      
Copy the code
  • Java. Lang. Reflect InvocationHandler: this is called the processor interface, since it defines an invoke method, used to deal with the focus on the dynamic proxy class object method calls, usually in the method of the class of agents.
Listing 2. The core method of InvocationHandler
  
  // This method is responsible for centrally handling all method calls on a dynamic proxy class.
  // The first argument is both a proxy class instance,
  // The second argument is the method object being called
  // The third method is to call parameters.
  // Call the handler with these three arguments
  // Preprocess or dispatch to the delegate class instance for reflection execution
  Object invoke(Object proxy, Method method, Object[] args)
      
Copy the code

Each time a dynamic Proxy class object is generated, you need to specify a call handler object that implements this interface (see third parameter of Proxy static method 4).

  • Java.lang. ClassLoader: This is the ClassLoader class that loads the class’s bytecode into the Java Virtual Machine (JVM) and defines class objects for it before the class can be used. The Proxy static method generates dynamic Proxy classes that also need to be loaded by the class loader to be used. The only difference is that the bytecode is generated dynamically by the JVM at runtime rather than preexisting in any.class file.

You need to specify a classloader object each time you generate a dynamic Proxy class object (see the first parameter of Proxy static method 4)

Agency mechanism and characteristics

Let’s first look at how to use Java dynamic proxies. There are four steps as follows:

  1. Create your own call handler by implementing the InvocationHandler interface;
  2. Create a dynamic Proxy class by specifying a ClassLoader object and a set of interfaces for the Proxy class;
  3. The constructor of the dynamic proxy class, whose only parameter type is the calling processor interface type, is obtained through reflection.
  4. A dynamic proxy class instance is created through a constructor that calls the processor object passed in as a parameter.
Listing 3. The core method of InvocationHandler

// InvocationHandlerImpl implements the InvocationHandler interface,
// It can dispatch and forward method calls from the proxy class to the delegate class
// It usually contains a reference to the delegate class instance,
// Used to actually execute the method call that dispatches forward
InvocationHandler handler = newInvocationHandlerImpl(..) ;// Use Proxy to represent a group of interfaces including the Interface Interface
// Dynamically create a class object for the proxy class
Class clazz = Proxy.getProxyClass(
    classLoader, 
    new Class[] { Interface.class, ... });

// Get the constructor object from the generated class object by reflection
Constructor constructor = clazz.getConstructor(
    new Class[] { InvocationHandler.class });

// Create a dynamic proxy class instance with the constructor object
Interface Proxy = (Interface)constructor.newInstance(
    new Object[] { handler });

Copy the code

The actual process is simpler, because the static method newProxyInstance of Proxy has encapsulated the process from Step 2 to Step 4 for us, so the simplified process is as follows

Listing 4. Simplified dynamic proxy object creation process (three in one)

// InvocationHandlerImpl implements the InvocationHandler interface,
// It can dispatch and forward method calls from the proxy class to the delegate class
InvocationHandler handler = newInvocationHandlerImpl(..) ;// Create a dynamic Proxy class instance directly through Proxy
Interface proxy = (Interface)Proxy.newProxyInstance( 
     classLoader,
     new Class[] { Interface.class },
     handler );

Copy the code

Let’s take a look at some of the features of Java’s dynamic proxy mechanism.

First, there are some characteristics of the dynamically generated proxy classes themselves.

  • Package: If the propped interface is all public, it will be defined in the top-level package (i.e., the package path is empty). If the propped interface has non-public interfaces (since interfaces cannot be defined as protect or private, So the default package access level except for public), then it will be defined in the package where the interface resides (assuming some non-public interface A in the com.panshenlian.proxy package is proxied, So the package where the newly generated proxy class resides is com.panshenlian.proxy. The purpose of this design is to ensure that the dynamic proxy class will not be successfully defined and accessed due to package management problems.
  • Class modifiers: This proxy class has final and public modifiers, meaning that it can be accessed by all classes, but cannot be reinherited;
  • The name of the class: The format is “$ProxyN”, where N is an incrementing Arabic number representing the NTH dynamic Proxy class generated by Proxy. Note that N does not increase with each call to Proxy’s static method to create a dynamic Proxy class. Reason is that if the same set of interfaces (including interface are arranged in the order of the same) tries to create the dynamic proxy classes, it will return to a previously created wisely good proxy class class object, rather than to try to create a new proxy classes, that can save unnecessary code duplication, improve the efficiency of the proxy class to create.
  • Class inheritance relationship: The inheritance relationship of this class is shown as follows:

As you can see from the figure, Proxy class is its parent, and this rule applies to all dynamic Proxy classes created by Proxy. This class also implements the set of interfaces it proides, which is the root reason why it can be safely typed to one of the interfaces it proides.

Let’s look at some of the characteristics of proxy class instances. Each instance is associated with a call handler object, which can be obtained through the static method getInvocationHandler provided by Proxy. When a proxy class instance calls a method declared in its proxy interface, these methods are eventually executed by the invoking processor’s Invoke method. In addition, it is worth noting that three methods in the root java.lang.Object class of the proxy class are also dispatched to the invoking processor’s Invoke method. They are hashCode, equals, and toString for possible reasons:

  • First, because these methods are public and non-final, they can be overridden by proxy classes.
  • Secondly, these methods usually present some characteristic attributes of a class and have certain degree of differentiation. Therefore, in order to ensure the external consistency of the proxy class and the delegate class, these three methods should also be assigned to the delegate class for execution. When a group of interface agent has repeatedly promised method and this method is invoked, the proxy class always derive from the top interface method object is assigned to call processor, and no matter whether the proxy class instances in the interface (or a child of the inheritance in the interface interface) is in the form of external references, because inside the proxy class cannot distinguish between its current reference types.
Next, look at the characteristics of a proxied set of interfaces.

First, be careful not to have duplicate interfaces to avoid compilation errors when generating dynamic proxy class code. Second, these interfaces must be visible to the class loader, otherwise the class loader will not be able to link them and the class definition will fail. Again, all non-public interfaces to be proxied must be in the same package, otherwise proxy class generation will also fail. Finally, the number of interfaces cannot exceed 65535, which is the limit set by the JVM.

    
    /** * Proxy.java * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. */
    private staticClass<? > 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);
    }

Copy the code

Finally, take a look at the characteristics of exception handling. You can see from the method that calls the processor interface declaration that it can theoretically throw any type of exception, because all exceptions inherit from the Throwable interface, but is that true? The answer is no, because we must observe an inheritance principle: when a subclass overrides a parent class or implements a parent interface method, the exception thrown must be in the list of exceptions supported by the original method. So while calling the handler is theoretically possible, in practice it is often limited unless methods in the parent interface support Throwable exceptions. So what if an Invoke method does raise an exception that is not supported in the interface method declaration? Rest assured, Java dynamic proxy classes have designed a solution for us: it will be thrown UndeclaredThrowableException anomalies. This exception is of type RuntimeException, so it does not cause a compilation error. With the exception’s getCause method, you can also get the original unsupported exception object for error diagnosis.

Code demo

Now that the mechanism and features have been introduced, let’s take a look at the source code to see how Proxy is implemented.

First, remember a few important static variables for Proxy:

Listing 5. Important static variables for Proxy

// Map table: used to maintain class loader objects to their corresponding proxy class cache
private static Map loaderToCache = new WeakHashMap();

// flag: Used to indicate that a dynamic proxy class is being created
private static Object pendingGenerationMarker = new Object();

// Synchronous table: records the types of dynamic proxy classes that have been created, mainly determined by isProxyClass
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());

// The associated call handler reference
protected InvocationHandler h;

Copy the code

Then, take a look at the Proxy constructor:

Listing 6. Proxy constructor

// Since the Proxy never calls the constructor directly internally,
// So the private type means that no calls are allowed
private Proxy(a) {}

// Since the Proxy never calls the constructor directly internally,
// So protected means that only subclasses can be called
protected Proxy(InvocationHandler h) {this.h = h; }Copy the code

Next, take a quick look at the newProxyInstance method, because it’s fairly simple:

Listing 7. Proxy static method newProxyInstance

/** * dispatches the method call to the specified invocation handler * and returns the proxy class instance of the specified interface */
public static Object newProxyInstance( ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
            throws IllegalArgumentException {

    // Check that h is not null, otherwise an exception is thrown
    if (h == null) {
        throw new NullPointerException();
    }

    // Get the proxy class type object associated with specifying the class loader and a set of interfaces
    Class cl = getProxyClass(loader, interfaces);

    // Get the constructor object by reflection and generate the proxy class instance
    try {
        
        Constructor cons = 
            cl.getConstructor(constructorParams);
        
        return (Object) cons.newInstance(new Object[]{h});
    } catch (NoSuchMethodException e) { 
        throw new InternalError(e.toString());
    } catch (IllegalAccessException e) { 
        throw new InternalError(e.toString());
    } catch (InstantiationException e) { 
        throw new InternalError(e.toString());
    } catch (InvocationTargetException e) { 
        throw newInternalError(e.toString()); }}Copy the code

Thus, the real key to dynamic proxies is in the getProxyClass method, which is responsible for dynamically generating proxy class type objects for a set of interfaces. Inside the method, you’ll see all the heroes (static variables) in the Proxy come into play. A little impatient? Let’s go into the most mysterious palace of Proxy to appreciate it. This method can be divided into four steps:

Step 1: Perform some level of security checks on this set of interfaces, including checking that the interface class objects are visible to the class loader and are identical to the interface class objects recognized by the class loader, and that they are of type interface rather than class. This step is done through a loop that returns an array of strings containing all the interfaceNames, called String[] interfaceNames. Overall, this part of the implementation is fairly straightforward, so leave out most of the code, leaving only the relevant code on how to determine whether a class or interface is visible to a particular class loader.

Listing 8. Determine the visibility of the interface through the class.forname method

   try {
       // Specify the interface name, class loader object,
       // Make initializeBoolean as well
       // False means that class initialization is not required
       // 
       // If the method returns to normal, it is visible,
       // Otherwise ClassNotFoundException will be thrown
       // Exception means not visible
       interfaceClass = 
           Class.forName(interfaceName, false, loader);
   } catch (ClassNotFoundException e) {
   }

Copy the code

In step 2, the cache table corresponding to the classloader object keyword is retrieved from the loaderToCache mapping table, and if it does not exist, a new cache table is created and updated to loaderToCache. The cache table is an instance of a HashMap that normally holds key-value pairs (a list of interface names, class object references to dynamically generated proxy classes). It is temporarily saved while the proxy class is being created (list of interface names, pendingGenerationMarker). The flag pendingGenerationMarke notifies subsequent requests of the same kind (with the same array of interfaces and the same order of interfaces in the group) that the proxy class is being created and that you should wait until the creation is complete.

Listing 9. Use of cached tables

   do {
       // Use the interface name list as the key to get the corresponding cache value
       Object value = cache.get(key);
       if (value instanceof Reference) {
           proxyClass = 
               (Class) ((Reference) value).get();
       }
       if(proxyClass ! =null) {
           // If already created, return directly
           return proxyClass;
       } else if (value == pendingGenerationMarker) {
           // The proxy class is being created, wait
           try {
               cache.wait();
           } catch (InterruptedException e) {
           }
           // Waiting to be awakened,
           // Loop again,
           // To ensure that the creation is complete,
           // Otherwise, wait again
           continue;
       } else {
           // The tag proxy class is being created
           cache.put(key, pendingGenerationMarker);
           // break breaks out of the loop and enters the creation process
           break;
   } while (true);
       

Copy the code

Step 3, dynamically create a class object for the proxy class. The first step is to determine the package in which the proxy class resides. As mentioned earlier, if both are public interfaces, the package name is an empty string indicating the top-level package. If all non-public interfaces are in the same package, the package name is the same as the package name of those interfaces. If there are multiple non-public interfaces and different packages, an exception is thrown to terminate the generation of the proxy class. Once the package is identified, the class name of the proxy class is generated, again in format “$ProxyN” as described earlier. With the class name settled, the magic happens: dynamically generating the proxy class:

Listing 10. Dynamically generating proxy classes

   // Dynamically generate the bytecode array of the proxy class
   byte[] proxyClassFile = 
       ProxyGenerator.generateProxyClass( 
       		proxyName, 
       		interfaces);
   try {
       
       // Dynamically define the newly generated proxy class
       proxyClass = 
           defineClass0(
           		loader, 
           		proxyName, 
           		proxyClassFile, 0,
           		proxyClassFile.length);
       
   } catch (ClassFormatError e) {
       throw new IllegalArgumentException(e.toString());
   }

   // Add the generated proxy class to the class object
   // log into the proxyClasses table
   proxyClasses.put(proxyClass, null);


Copy the code

Thus, all the work of code generation is done by the mysterious ProxyGenerator. When you try to explore this class, all you get is that it is in an undisclosed sun.misc package, and there are constants, variables, and methods to complete the magic code generation process. But Sun doesn’t provide the source code to peruse. The definition of dynamic classes is performed by the Proxy’s native static method defineClass0.

In step 4, the code generation process enters the end part, and the cache table is updated according to the result. If successful, the class object reference of the proxy class is updated into the cache table, otherwise the corresponding key value in the cache table is cleared, and finally all possible waiting threads are woken up.

Now that all the details of proxy class generation have been covered, the rest of the static methods, such as getInvocationHandler and isProxyClass, are so straightforward that they can be done simply by querying the relevant variables, so the code analysis is omitted.

The proxy class implements the deduction

Analyzing the source code for the Proxy class will leave you with a clearer understanding of Java’s dynamic Proxy mechanism, but all the mystery comes to a halt at the sun.misc.ProxyGenerator class. Many readers will have a similar question about the ProxyGenerator class: What does it do? How does it generate the code for the dynamic proxy class? Admittedly, there is no definitive answer here. Let us begin our journey of discovery with these doubts.

Things are not as complicated as they seem. What we need is to be able to simplify things so that we may have more opportunities to see the light. What would your first reaction be if we were to implement a proxy class in the simplest way possible, with the only requirement being to dispatch and forward the method in combination with the calling handler?” That doesn’t sound very complicated. Indeed, the work involved in pinching involves little more than a few reflection calls and the boxing or unboxing of raw type data, and the rest seems to have taken care of itself. Very good. Let’s get our thoughts together and work through the whole process.

Listing 11. Dispatch forward deduction implementation of a method call in a proxy class

// Suppose you want the proxy interface Simulator
public interface Simulator {
    short simulate(int arg1, long arg2, String arg3) 
        throws ExceptionA, ExceptionB;
}

// Assuming the proxy class is SimulatorProxy, its class declaration would be as follows
final public class SimulatorProxy implements Simulator {

    // Invoke a reference to a processor object
    protected InvocationHandler handler;

    // A constructor that takes the call handler
    public SimulatorProxy(InvocationHandler handler){
        this.handler = handler;
    }

    // Implement the interface method simulate
    public short simulate(int arg1, long arg2, String arg3)
        throws ExceptionA, ExceptionB {

        // The first step is to get the Method object for simulate
        java.lang.reflect.Method method = null;
        try{
            method = Simulator.class.getMethod(
                "simulate".new Class[] {int.class, long.class, String.class} );
        } catch(Exception e) {
            // Exception handling 1 (omitted)
        }

        // The second step is to call handler's invoke method to dispatch the forward method call
        Object r = null;
        try {
            r = handler.invoke(this,
                method,
                // Boxing is required for primitive type parameters
                new Object[] {new Integer(arg1), new Long(arg2), arg3});
        }catch(Throwable e) {
            // Exception handling 2 (omitted)
        }
        // The third step is to return the result (return type is primitive, need to unpack operation)
        return((Short)r).shortValue(); }}Copy the code

Simulation deductions focus more on normal flow and less on error handling in order to highlight general logic, but error handling is also important in practice. From the above deduction, we can derive a very general structured process:

  • The first step is to get the called method object from the proxy interface.
  • The second step dispatches the method to the calling handler;
  • The third step returns the result.

All of this information is already known, such as the interface name, method name, parameter type, return type, and required boxing and unboxing operations, so why not believe that ProxyGenerator will not implement something similar if we write it manually? That, at least, is the more likely scenario.

Let’s return to the previously downplayed error handling. In exception processing 1, because we have reason to ensure that all the information such as interface name, method name and parameter type are accurate, the probability of this part of exception occurrence is basically zero, so it can be basically ignored. We need to think a little bit more about exception handling 2. Recall that an interface method may declare support for a list of exceptions, and that invoking the handler invoke method may throw an exception that is not supported by the interface method. Recall that Java dynamic proxy features exception handling that are not supported. Must throw UndeclaredThrowableException runtime exception. So by deducting again, we can get a clearer case of exception handling 2:

Listing 12. Verbose exception handling 2

Object r = null;

try {
    r = handler.invoke(
            this,
            method,
            new Object[] {
                new Integer(arg1), 
                new Long(arg2), 
                arg3
            }
        );

} catch( ExceptionA e) {

    // The interface method supports ExceptionA, which can be thrown
    throw e;

} catch( ExceptionB e ) {
    // The interface method supports ExceptionB, which can be thrown
    throw e;

} catch(Throwable e) {
    // Other unsupported exceptions,
    / / all UndeclaredThrowableException
    throw new UndeclaredThrowableException(e);
}

Copy the code

This completes the deductive implementation of the dynamic proxy class. Deductive implementations follow a relatively fixed pattern that can be applied to any interface that is defined, and the information required for code generation is known, so it is reasonable to believe that even machine-written code could continue in this style, or at least be guaranteed to do so.

a fly in the ointment

Yes, Proxy has been beautifully designed, but there is a small regret that it has never been able to escape the shackles of interface Proxy only because it was designed to do so. Recall the inheritance diagram for dynamically generated Proxy classes that are destined to have a common parent class called Proxy. Java’s inheritance mechanism prevents these dynamic proxy classes from implementing dynamic proxies to classes because multiple inheritance is inherently unworkable in Java.

There are many reasons why one can deny the need for a class proxy, but there are also some reasons why supporting a class dynamic proxy would be better. The division of interfaces and classes was not obvious until Java became so detailed. If you look at methods only in terms of how they are declared and whether they are defined, there is a hybrid called abstract class. Implementing dynamic proxies for abstract classes also has inherent value. In addition, there are legacy classes that will never be used for dynamic proxies because they do not implement any interfaces. Such a variety, have to say is a small regret.

However, imperfect is not the same as not great, greatness is an essence, Java dynamic proxy is an example.

— Wang Zhongping, He Ping from IBM Developer

conclusion

The agent model is the experience crystallization of the forefather for a kind of specific problem, and can be applied flexibly in various fields. Especially in the field of programming, design specifications and features of different languages according to their own mastery, eventually to be able to implement the specific solutions, such as Spring AOP enhancements in performance matters to ascend, or the interceptor implementation in log and permission level control filter and so on, can do the original interface transaction without intrusion, as well as flexible control, Large-scale implementation, to meet our expectations, this is the agency model, clever.

Thanks for references and tool support

[1] Java dynamic proxy mechanism analysis and extension: developer.ibm.com/zh/articles…

[2] proxy mode principle and the instance to explain in detail: developer.ibm.com/zh/articles…

[3]Dynamic Proxy Classes: java.sun.com/j2se/1.4.2/…

[4] Dynamic proxy mechanism: www.ibm.com/developerwo…

[5] Image source: www.pexels.com/

[6] Flow chart design source: www.processon.com/

BIU ~ the article continues to update, wechat search “Pan Pan and his friends” the first time to read, there are surprises at any time. This article will be included on GitHub github.com/JavaWorld, hot technology, framework, surface, solution, we will be the most beautiful posture in the first time, welcome Star.