[Java] Proxy mode

proxy pattern

Simply put: instead of accessing the real object, the proxy object can be used to provide additional functional operations to extend the function of the target object without modifying the original target object.

The main purpose of the proxy mode is to extend the capabilities of the target object, for example, you can add some custom actions before and after a method of the target object

There are two main ways to implement the proxy model:

  1. Static Proxy
  2. Dynamic proxy (application of reflection)

Static Proxy

Implementation Steps
  1. Create an interface and its implementation class
  2. Create a proxy class to implement this interface
  3. By injecting the target object into the proxy class, and then calling the corresponding method in the target class, we can block access to the target object through the proxy class. And you can do something you want to do before and after the goal approach is implemented

Static proxy enhancements to each method of each target object are done manually, with obvious drawbacks

shortcoming

  1. Very inflexible, if new methods are added to the interface, then the target and proxy objects will also be modified
  2. Implement a proxy class for each target class
Implementation Instances

1. Define the interface for sending text messages

public interface SmsService {
    String send(String message);
}

2. Implement the interface for sending SMS

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3. Create a proxy class and also implement the interface for sending SMS

public class SmsProxy implements SmsService {

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
        //We can add our own actions before calling the method
        System.out.println("before method send()");
        smsService.send(message);
        //After invoking the method, we can also add our own actions
        System.out.println("after method send()");
        return null;
    }
}

4. Actual use

public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

After running the above code, the console prints out:

before method send()
send message:java
after method send()

As you can see from the output, we have added the send() method of SmsServiceImpl.

Dynamic Proxy

Dynamic agents are more flexible than dynamic agents

There is no need to create a proxy class for each target class, and there is no need for us to implement an interface. We can proxy the implementation class directly (CGLIB dynamic proxy mechanism).

When it comes to dynamic proxies, Spring AOP and RPC frameworks should be mentioned as two obligatory frameworks whose implementation depends on dynamic proxies.

Dynamic proxy is a relatively small technology used in our daily development, but it is almost a necessary technology in the framework. After learning dynamic proxy, it is also very helpful for us to understand and learn the principles of various frameworks.

For Java, there are many ways to implement dynamic proxy, such as JDK dynamic proxy, CGLIB dynamic proxy, and so on.

guide-rpc-framework Using JDK dynamic proxy, let's first look at the use of JDK dynamic proxy.

In addition, although guide-rpc-framework CGLIB dynamic proxy is not used, so here is a brief description of its use and comparison with JDK dynamic proxy.

1. JDK Dynamic Proxy Technology

The InvocationHandler interface and Proxy class are the core of the Java dynamic proxy mechanism

The most frequently used method in the Proxy class is newProxyInstance(), which is used primarily to generate a proxy object.

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ......
    }

This method has three parameters:

  1. Loader: A class loader used to load proxy objects.
  2. Interfaces: Some interfaces implemented by the proxy class;
  3. h: An object that implements the InvocationHandler interface;

To implement dynamic proxies, you must also implement InvocationHandler to customize the processing logic. When our dynamic proxy object calls a method, the call to that method is forwarded to the invoke method that implements the InvocationHandler interface class.

public interface InvocationHandler {
    /**
     * This method is actually called when you call it with a proxy object
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

The invoke() method has the following three parameters:

  1. Proxy: dynamically generated proxy class
  2. Method: corresponds to the method invoked by the proxy class object
  3. args: Parameters of the current method

That is, the proxy object you create with the newProxyInstance() of the Proxy class actually calls the invoke() method of the class that implements the InvocationHandler interface when calling the method. You can customize the processing logic in the invoke() method, such as what is done before and after the method is executed.

Implementation Steps
  1. Define an interface and its implementation class;
  2. Customize InvocationHandler and override invoke methods, in which we call native methods (methods of proxied classes) and customize some processing logic;
  3. Through Proxy. The newProxyInstance (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) method creates a proxy object;
Implementation examples

This may be a bit empty and difficult to understand. Let's take a look at my last example.

1. Define the interface for sending text messages

public interface SmsService {
    String send(String message);
}

2. Implement the interface for sending SMS

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3. Define a JDK dynamic proxy class

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author shuang.kou
 * @createTime 2020 11:23:00 May 11, 2005
 */
public class DebugInvocationHandler implements InvocationHandler {
    /**
     * Real objects in proxy classes
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        //We can add our own actions before calling the method
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        //After invoking the method, we can also add our own actions
        System.out.println("after method " + method.getName());
        return result;
    }
}

invoke() method: When our dynamic proxy object calls the native method, it actually calls the invoke() method, and then the invoke() method calls the native method of the proxy object instead of us.

4. Get the factory class of the proxy object

public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // Class Loading of Target Class
                target.getClass().getInterfaces(),  // Agents need to implement interfaces that can specify multiple
                new DebugInvocationHandler(target)   // Custom InvocationHandler corresponding to proxy object
        );
    }
}

getProxy(): Mainly through Proxy.newProxyInstance() method gets a proxy object for a class

5. Actual use

SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");

After running the above code, the console prints out:

before method send
send message:java
after method send
2. CGLIB dynamic proxy mechanism

One of the most fatal problems with JDK dynamic proxies is that they can only proxy classes that implement interfaces.

To solve this problem, we can use CGLIB dynamic proxy mechanism to avoid it.

CGLIB (Code Generation Library) is based on ASM Byte Code Generation Library, which allows us to modify and dynamically generate byte codes at runtime. CGLIB implements proxy through inheritance. Many well-known open source frameworks use CGLIB For example, in the AOP module in Spring, if the target object implements an interface, the JDK dynamic proxy is used by default, otherwise the CGLIB dynamic proxy is used.

The MethodInterceptor interface and Enhancer class are the core of the CGLIB dynamic proxy mechanism.

You need to customize the MethodInterceptor and override the intercept method, which is used to intercept methods that enhance the proxy class.

public interface MethodInterceptor
extends Callback{
    // Intercept methods in proxy classes
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
}
  1. obj: Proxyed object (object requiring enhancement)
  2. Method: Intercepted method (requires enhanced method)
  3. args: method entry
  4. methodProxy: Used to call the original method

You can dynamically get the proxy class by using the Enhancer class. When a proxy class calls a method, it actually calls the intercept method in the MethodInterceptor.

Implementation Steps
  1. Define a class;
  2. Customize the MethodInterceptor and override the intercept method, which intercepts methods that enhance the proxy class, similar to invoke methods in JDK dynamic proxies;
  3. Create a proxy class through create() of the Enhancer class;
Implementation examples

Unlike JDK dynamic proxies, no additional dependency is required. CGLIB The Code Generation Library is actually an open source project and you need to manually add dependencies if you want to use it.

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

1. Implement a class for sending SMS using Ali Cloud

package github.javaguide.dynamicProxy.cglibDynamicProxy;

public class AliSmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

2. Customize MethodInterceptor (Method Interceptor)

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Customize MethodInterceptor
 */
public class DebugMethodInterceptor implements MethodInterceptor {
    /**
     * @param o           Proxyed objects (objects that need to be enhanced)
     * @param method      Intercepted methods (methods that need to be enhanced)
     * @param args        Method Input
     * @param methodProxy Used to call the original method
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //We can add our own actions before calling the method
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        //After invoking the method, we can also add our own actions
        System.out.println("after method " + method.getName());
        return object;
    }

}

3. Get proxy classes

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // Create Dynamic Proxy Enhancement Class
        Enhancer enhancer = new Enhancer();
        // Set Class Loader
        enhancer.setClassLoader(clazz.getClassLoader());
        // Set Proxied Class
        enhancer.setSuperclass(clazz);
        // Set Method Interceptor
        enhancer.setCallback(new DebugMethodInterceptor());
        // Create Proxy Class
        return enhancer.create();
    }
}

4. Actual use

AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

After running the above code, the console prints out:

before method send
send message:java
after method send
3. Summary
Difference

Differences between CGLIB and JDK dynamic proxy technologies:

  1. JDK is implemented by the static method newProxyInstance () of the Proxy class and by implementing the InvocationHandler interface, overriding the invoke method
  2. CGLIB is implemented by creating an enhanced class Enhancer, then setting properties and implementing the MethodInterceptor interface, overriding the intercept method
  3. JDK can only proxy classes that implement interfaces. For those classes that do not, if you want to proxy, you can do so through CGLIB.
  4. CGLIB dynamic proxy intercepts method calls from proxied classes by generating a subclass of the proxied class, so it is not possible to proxy classes and methods declared final.
  5. As far as their efficiency is concerned, JDK dynamic proxy is better in most cases, and this advantage becomes more obvious with the upgrade of JDK version.

Comparison of static proxy and dynamic proxy

  1. Flexibility: Dynamic proxies are more flexible, do not need to implement interfaces (CGLIB), can directly proxy implementation classes, and do not need to create a proxy class for each target class. In addition, in a static proxy, once a new method is added to the interface, both the target proxy and the proxy object need to be modified, which is very cumbersome.
  2. JVM level: Static proxies make interfaces, implementation classes, and proxy classes an actual class file at compile time. Dynamic proxy generates class byte codes dynamically at runtime and loads them into the JVM.

Keywords: Java Design Pattern Dynamic Proxy

Added by harvillo on Mon, 07 Mar 2022 19:23:26 +0200