Java Proxy Mode and Dynamic Proxy Details

Keywords: Java JDK Spring Programming

Java's dynamic proxy has a wide range of usage scenarios in practice, such as Spring AOP for the most scenarios, acquisition of Java annotations, logging, user authentication, and so on.This article takes you through proxy mode, static proxy, and native dynamic proxy based on JDK.

proxy pattern

Whether you are learning static or dynamic proxies, we need to first understand the proxy model.

First look at the definition of Baidu Encyclopedia:

Definition of proxy mode: Provides a proxy for other objects to control access to this object.In some cases, one object is inappropriate or cannot directly reference another object, while a proxy object can act as an intermediary between the client and the target object.

A direct look at the definition may be somewhat difficult to understand, so let's illustrate it with specific examples in life.

We've all been to the supermarket to buy goods, and the supermarket sells them to us after they buy from the manufacturer. We usually don't know where the goods go through how many processes to get to the supermarket.

In this process, it means that the manufacturer "entrusts" the supermarket to sell goods, which is invisible to us (the real object).Supermarket (agent object) acts as the "agent" of the manufacturer to interact with us.

At the same time, supermarkets can also deal with discounts according to specific sales conditions to enrich the functions of the agents.

With the proxy model, we can do two things:

1. Hide the implementation of the delegate class.

2. Decouple the client from the delegate class and add some additional functions (logs, permissions) without changing the delegate class code.

Agent Mode Role Definition

In the process of programming, we can define three types of objects:

  • Subject (Abstract Theme Role): A public external method that defines proxy classes and real themes, as well as a way for proxy classes to proxy real themes.For example: advertising, sales, etc.
  • RealSubject: A class that truly implements business logic.Vendor, for example, implements methods such as advertising and sales.
  • Proxy: Used to proxy and encapsulate real themes.For example, time-outs such as advertising and sales are also implemented.

The class diagrams for the three roles above are as follows:

Static proxy instance

Static proxy means that a proxy class already exists before the program runs, in which case the proxy class is usually defined in Java code.

Let's demonstrate a static proxy with a specific example.

First, define a set of interfaces, Sells, to provide features such as advertising and sales.The Vendor class (manufacturer, proxy object) and Shop (supermarket, proxy class) are then provided, which implement the Sell interface, respectively.

The Sell interface is defined as follows:

/**
 * Both delegate and proxy classes implement the Sell interface
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:30 AM
 **/
public interface Sell {

    /**
     * Sell
     */
    void sell();

    /**
     * Advertisement
     */
    void ad();
}

The Vendor class is defined as follows:

/**
 * Supplier
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:30 AM
 **/
public class Vendor implements Sell{

    @Override
    public void sell() {
        System.out.println("Shop sell goods");
    }

    @Override
    public void ad() {
        System.out.println("Shop advert goods");
    }
}

The Shop class is defined as follows:

/**
 * Supermarket, Agents
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:30 AM
 **/
public class Shop implements Sell{

    private Sell sell;

    public Shop(Sell sell){
        this.sell = sell;
    }

    @Override
    public void sell() {
        System.out.println("proxy class Shop,Handle sell");
        sell.sell();
    }

    @Override
    public void ad() {
        System.out.println("proxy class Shop,Handle ad");
        sell.ad();
    }
}

The proxy class Shop holds a reference to the proxy class Vendor by aggregation and calls the corresponding Vendor method in the corresponding method.We can add some additional processing to the hop class, such as filtering purchase users, logging logs, and so on.

Let's see how proxy classes are used in clients.

/**
 * Static Proxy Class Test Method
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:33 AM
 **/
public class StaticProxy {

    public static void main(String[] args) {

        // Vendor - Agent Class
        Vendor vendor = new Vendor();

        // Create vendor's proxy class Shop
        Sell sell = new Shop(vendor);

        // The client is using the proxy class Shop.
        sell.ad();
        sell.sell();
    }
}

In the code above, what the customer sees is that the Sell interface provides functionality, which is provided by Shop.We can modify or add something to the hop without affecting the proxy class Vendor.

Disadvantages of static proxy

Static proxy implementations are simple and do not intrude into the original code, but when scenarios are complex, static proxy has the following drawbacks:

1. When multiple classes need to be proxied, the proxy object implements an interface that is consistent with the target object.Alternatively, only one proxy class is maintained to implement multiple interfaces, but this can result in proxy classes becoming too large.Alternatively, create more than one new proxy class, but this will result in too many proxy classes.

2. When an interface needs to add, delete or modify methods, both the target object and the proxy class need to be modified at the same time, which is not easy to maintain.

Thus, dynamic proxy comes in handy.

Dynamic Proxy

Dynamic proxy refers to the way proxy classes are created when a program is running.In this case, the proxy class is not defined in Java code, but is generated dynamically at runtime based on instructions in Java code.

The advantage of dynamic proxy over static proxy is that it is easy to unify the functions of proxy classes without modifying the functions of each proxy class.

Implementation of native dynamic proxy based on JDK

There are usually two ways to implement dynamic proxies: JDK native dynamic proxy and CGLIB dynamic proxy.Here, we will take the native dynamic proxy of JDK as an example.

The JDK dynamic proxy mainly involves two classes: java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler.

The InvocationHandler interface defines the following methods:

/**
 * Call Handler
 */
public interface InvocationHandler { 
    Object invoke(Object proxy, Method method, Object[] args); 
}

As the name implies, the mediation class that implements this interface is used as the "call processor".When a method of a proxy class object is invoked, this "call" is forwarded to the invoke method. The proxy class object is passed in as a proxy parameter. The parameter method identifies which method of the proxy class is invoked and args is the parameter of the method.In this way, calls to all methods in the proxy class become invoke calls, which can add uniform processing logic to the invoke method (or different proxy class methods can be handled differently depending on the method parameter).

The Proxy class is used to get the call handler associated with the specified proxy object.

The following demonstrates a dynamic proxy with an example of adding logs.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class LogHandler implements InvocationHandler {
    Object target;  // Agented object, actual method executor

    public LogHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);  // Call the method method method of the target
        after();
        return result;  // Returns the execution result of the method
    }
    // Execute before invoke method is called
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    // Execute after invoke method is called
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

Client writers use dynamic proxy code as follows:

import java.lang.reflect.Proxy;

/**
 * Dynamic proxy testing
 *
 * @author sec
 * @version 1.0
 * @date 2020/3/21 10:40 AM
 **/
public class DynamicProxyMain {

    public static void main(String[] args) {
        // Create Instance of Mediation Class
        LogHandler logHandler = new LogHandler(new Vendor());
        // Setting this variable saves the dynamic proxy class with the default name $Proxy0.class
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        // Get proxy class instance Sell
        Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler));

        // Calling a proxy class method through a proxy class object actually goes to the invoke method call
        sell.sell();
        sell.ad();
    }
}

After execution, print the log as follows:

Calling log processing of method sell
Shop sell goods
 Logging of method sell
 Log processing calling method ad
Shop advert goods
 Logging Processing of Call Method ad

After the above validation, we found that we have successfully added logs for our proxied classes before and after the execution of the method.

To see the code for the generated dynamic proxy class in the above example, we added the following property settings (which need to be removed in a production environment).

// Setting this variable saves the dynamic proxy class with the default name $Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

So, after we execute the main method, we also generate a class file named $Proxy0.class.By decompiling, you can see the following code:

package com.sun.proxy;

import com.choupangxia.proxy.Sell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Sell {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m3;
    private static Method m0;

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

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void ad() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void sell() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.choupangxia.proxy.Sell").getMethod("ad");
            m3 = Class.forName("com.choupangxia.proxy.Sell").getMethod("sell");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

You can see that $Proxy0 (proxy class) inherits the Proxy class and implements all the interfaces that are proxied, as well as methods such as equals, hashCode, toString.

Since dynamic proxy classes inherit the Proxy class, each proxy class is associated with an InvocationHandler method call processor.

Classes and all methods are decorated with public final, so proxy classes can only be used and can no longer be inherited.

Each method has a Method object that is created in a static block of code and named in the format "m+number".

The method is called through super.h.invoke(this,m1,(Object[])null); andThe super.h.invoke is actually a LogHandler object passed to Proxy.newProxyInstance when the proxy is created. It inherits the InvocationHandler class and is responsible for the actual call handling logic.

Summary

So much about proxies and dynamic proxies.Understanding the proxy mode can make our system design more scalable.Dynamic proxy is more widely used, with a variety of frameworks and business scenarios being used.With two foundations, you can learn more about other frameworks.

Let's talk about CGLIB dynamic proxy in the next article.


New horizon of program: great and growing can not be missed

Posted by Majes on Fri, 20 Mar 2020 21:05:53 -0700