Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

The proxy pattern in Java

1 Proxy Mode

The proxy pattern is a structural design pattern used to assemble classes and objects into larger structures while keeping the structures flexible and efficient

In proxy mode, we substitute proxy objects for requests to real objects to provide additional operations or functionality

Principles of the proxy pattern:

  1. The structure of the actual object is not modified
  2. It is insensitive to the requester
  3. Does not depend on whether the actual object is created

The proxy pattern provides the same interface as the real object, while the decorator pattern provides an enhanced interface

Some application scenarios of proxy mode:

  1. Actual object lifecycle management
  2. Access control
  3. logging

The proxy mode has two implementation modes: static proxy and dynamic proxy

2 Static Proxy

Static proxy implementation steps are as follows:

  1. Define a behavior interface
  2. Create an implementation class for the behavior interface
  3. Create a proxy class that implements the behavior interface and have a property that holds an implementation class object instance of the behavior interface
  4. Change the location of the actual request object to the request proxy object

Take the message sending scenario as an example:

Behavior interface:

public interface IMessageAction {
    
    void send(String message);
    
}
Copy the code

Behavior interface implementation class:

public class MessageService implements IMessageAction {
    
    @Override
    public void send(String message) {
        System.out.printf("Manually send message: %s%n", message); }}Copy the code

The proxy class:

public class MessageProxy implements IMessageAction {
    
    private final MessageService target;
    
    public MessageProxy(MessageService target) {
        this.target = target;
    }
    
    @Override
    public void send(String message) {
        System.out.printf("Proxy log: send message [%s]%n", message); target.send(message); }}Copy the code

Testing:

public class Test {
    public static void main(String[] args) {
        MessageService messageService = new MessageService();
        MessageProxy messageProxy = new MessageProxy(messageService);
        messageService.send("Java");
        messageProxy.send("Java"); }}Copy the code

The output is:

Send message manually: Java proxy Log: Send message [Java] Send message manually: JavaCopy the code

The operation of the static proxy is done manually by the coder

This design creates a serious problem: if the actual object changes, the proxy object needs to be modified synchronously

The actual use of static proxies is rare, and can be tried only when the real object cannot be modified and additional functionality is fixed (is there really a requirement that doesn’t change)

3 Dynamic Proxy

From the JVM’s perspective, dynamic proxies dynamically generate and load class bytecode while the program is running

There is no need to create a separate proxy class, no need to care about changes to the actual object, or even the actual object

Dynamic proxies can be implemented in two ways: JDK dynamic proxy and CGLIB dynamic proxy

3.1 JDK Dynamic Proxy

Here is an example of refactoring a static proxy using the JDK dynamic proxy

Create dynamic proxy class:

public class MessageInvocationHandler implements InvocationHandler {
​
    private final Object target;
​
    public MessageInvocationHandler(Object target) {
        this.target = target;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException,
            InvocationTargetException {
        System.out.printf("Log: send message [%s]%n", args[0]);
        returnmethod.invoke(target, args); }}Copy the code

Create a proxy factory:

public class MessageProxyFactory {
​
    public static IMessageAction getProxy(Object target) {
        return (IMessageAction) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                newMessageInvocationHandler(target)); }}Copy the code

Note here that the agent type returned by the factory should be the interface IMessageAction,

If the implementation class MessageService is used, a type conversion error exception is thrown

From this we can see that the JDK dynamic proxy requirement is to implement the interface

Testing:

public class Test {
​
    public static void main(String[] args) {
        MessageProxyFactory.getProxy(new MessageService()).send("Java"); }}Copy the code

The output is:

Log: Send message [Java] Manually send message: JavaCopy the code

In our example, we decoupled the proxy from the real object by not having the proxy class implement the SEND method manually

3.2 CGLIB dynamic proxy

In JDK dynamic proxies, there is a constraint that you can only proxy classes that implement the interface

CGLIB is based on the ASM bytecode generation library, which helps us implement the proxy for classes with unimplemented interfaces

In Spring’s AOP module, JDK dynamic proxies are used if the target object implements an interface, and CGLIB is used instead

To use CGLIB, you need to add dependencies to Maven:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3. 0</version>
</dependency>
Copy the code

Continue refactoring the previous example

Delete the IMessageAction interface and create the service class directly:

public class MessageService {
    
    public void send(String message) {
        System.out.printf("Manually send message: %s%n", message); }}Copy the code

Create method interceptor:

public class MessageMethodInterceptor implements MethodInterceptor {
​
    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.printf("Log: send message [%s]%n", args[0]);
        returnmethodProxy.invokeSuper(target, args); }}Copy the code

Modify the agent factory:

public class MessageProxyFactory {
​
    public static MessageService getProxy(Class<MessageService> messageServiceClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(messageServiceClass.getClassLoader());
        enhancer.setSuperclass(messageServiceClass);
        enhancer.setCallback(new MessageMethodInterceptor());
        return(MessageService) enhancer.create(); }}Copy the code

Testing:

public class Test {
​
    public static void main(String[] args) {
        MessageProxyFactory.getProxy(MessageService.class).send("Java"); }}Copy the code

The output is:

Log: Send message [Java] Manually send message: JavaCopy the code

CGLIB implements proxies by inheritance, and invokeSuper is called in method interceptors to get a feel for it