Detailed explanation of JAVA dynamic agent

  • 2021-07-10 19:38:45
  • OfStack

Document update instructions

September 24, 2018 First draft of v1.0

Agents are very common in life, such as matchmaking websites, which are actually agents looking for objects; There are also social security agents and personnel agents; There is also looking for scalpers to grab tickets, which is actually an agent; These agents are also implemented in JAVA.

1. Why do you want to proxy dynamically

The function of dynamic agent is to enhance the existing method without modifying the original code.
Key points:

Do not modify the existing code (meet the requirements of the design pattern)
Enhance the existing method

2. Take a chestnut

Let's use a very simple example to illustrate: Hello Class, with 1 introduction Method.

Now our need is not to modify Hello Class introduction Method, in the introduction Before first sayHello , in introduction Then sayGoodBye

3. Implementation mode

In JAVA, there are two ways to realize dynamic proxy, one is provided by JDK, and the other is provided by CgLib, the third library. The characteristics are as follows:

JDK Dynamic Proxy: The proxy target class needs to implement the interface
CgLib Mode: You can implement dynamic proxy for any class

3.1. JDK Dynamic Proxy

The JDK dynamic proxy needs to implement the interface, and then implements the dynamic proxy by enhancing the interface method

Therefore, to use JDK dynamic proxy, we must first create an interface, and the proxy method should be in this interface

3.1. 1, Create 1 Interface

We create 1 interface as follows:

Personal.java


public interface Personal {

  /**
   *  Proxy method 
   */
  void introduction();

}

3.1. 2, implementing the interface

Create an interface implementation class and complete the introduction Method

introduction0


public class PersonalImpl implements Personal {
  @Override
  public void introduction() {
    System.out.println(" I'm a programmer! ");
  }
}

3.1. 3, Create a proxy class

The key to JDK proxy is this proxy class, which needs to be implemented introduction1

In the proxy class, all method calls are easily distributed to introduction2 Method. We're in introduction2 Method to complete the enhancement of the method

introduction4


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

public class JDKProxyFactory<T> implements InvocationHandler {

  /**
   *  Target object 
   */
  private T target;

  /**
   *  Constructor is passed into the target object 
   *
   * @param target  Target object 
   */
  public JDKProxyFactory(T target) {
    this.target = target;
  }

  /**
   *  Get the proxy object 
   *
   * @return  Get proxy 
   */
  public T getProxy() {
    return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //  Enhance the method 
    System.out.println(" Hello, everyone! ");
    //  Call the original method 
    Object result = method.invoke(target, args);
    //  Method enhancement 
    System.out.println(" See you later! ");
    return result;
  }
}

In this way, the code of JDK dynamic agent is completed, and then write a test code

3.1. 4, Writing Test Code

To facilitate testing, we wrote 1 introduction5 Method

At the same time, in order to view introduction6 File, and also added 1 introduction7 Method, which can change the generated by the dynamic proxy introduction8 Output to file

introduction9


import org.junit.Test;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.io.IOException;

public class ProxyTest {

  @Test
  public void testJdkProxy() {
    //  Generate the target object 
    Personal personal = new PersonalImpl();
    //  Get the proxy object 
    JDKProxyFactory<Personal> proxyFactory = new JDKProxyFactory<>(personal);
    Personal proxy = proxyFactory.getProxy();

    //  Will proxy Adj. class ByteCode output to file 
    generatorClass(proxy);

    //  Call proxy object 
    proxy.introduction();
  }

  /**
   *  Object's class ByteCode output to file 
   *
   * @param proxy  Proxy class 
   */
  private void generatorClass(Object proxy) {
    FileOutputStream out = null;
    try {
      byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), new Class[]{proxy.getClass()});
      out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class");
      out.write(generateProxyClass);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (out != null) {
        try {
          out.close();
        } catch (IOException e) {
          // TODO Auto-generated catch block
        }
      }
    }

  }

}

3.1. 5, View Run Results

As you can see, running introduction5 Method, the console prints as follows:

Hello, everyone!
I'm a programmer!
See you later!

We're in introduction Method before and after the successful addition of functionality, so that the programmer's self-introduction instantly become more polite.

3.1. 6, Exploring the Secrets of Dynamic Agents

There is not much code for dynamic proxy, so Hello2 How does the bottom help us achieve it?

During the test, we output the class bytecode of the dynamically generated proxy class to the file, so we can decompile it.

The result is a little long, so it won't all be posted, but we can see that there is one in it introduction The method is as follows:


/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;

protected Proxy(InvocationHandler h) {
  Objects.requireNonNull(h);
  this.h = h;
}

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

It turns out that the generated proxy object references our introduction1 , and then in the introduction Method calls the introduction1 Adj. introduction , and introduction1 Is a proxy class written by us, where we add sayHello And introduction0 Operation of the original object, and then also calls the introduction Method, thus completing the dynamic proxy.

3.2. CgLib Dynamic Agent

CgLib Dynamic

3.2. 1, Create Proxy Object

Due to CgLib There is no need to implement an interface, so we don't need to create an interface file (of course, you have no problem with an interface)

Directly create the target class and implement the introduction Method

introduction0


public class PersonalImpl {
  public void introduction() {
    System.out.println(" I'm a programmer! ");
  }
}

3.2. 2, Create Proxy Class

Similarly, we also need to create proxy classes and implement enhanced logic here. This time, we are not implementing introduction1 Interface, but implements the CgLib Provided interface MethodInterceptor , they are all similar, MethodInterceptor All method calls are handed to the intercept Handle, we are in intercept Add processing logic.

CgLibProxyFactory.java


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

import java.lang.reflect.Method;

public class CgLibProxyFactory<T> implements MethodInterceptor {

  /**
   *  Get the proxy object 
   *
   * @param tClass  Proxy target object 
   * @return  Proxy object 
   */
  public T getProxyByCgLib(Class<T> tClass) {
    //  Create an enhancer 
    Enhancer enhancer = new Enhancer();

    //  Set the class object of the class to be enhanced 
    enhancer.setSuperclass(tClass);

    //  Set callback function 
    enhancer.setCallback(this);

    //  Get the enhanced proxy object 
    return (T) enhancer.create();
  }

   /**
   *  Proxy class method call callback 
   * 
   * @param obj  This is the proxy object, which is [ Target object ] Subclass of 
   * @param method [ Target object ] Method of 
   * @param args  Parameter 
   * @param proxy  Methods of proxy objects 
   * @return  Returns the result to the caller 
   * @throws Throwable
   */
  @Override
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

    System.out.println(" Hello, everyone! ");

    Object result = proxy.invokeSuper(obj, args);

    System.out.println(" See you later! ");

    return result;
  }
}

3.2. 3, Writing Test Code

In the test method just now, we added 1 cglib Test method of:


@Test
public void testCgLibProxy() {
  //  Generate the proxy target object 
  PersonalImpl personal = new PersonalImpl();
  
  //  Get the proxy class 
  CgLibProxyFactory<PersonalImpl> proxyFactory = new CgLibProxyFactory<>();
  PersonalImpl proxy = proxyFactory.getProxyByCgLib((Class<PersonalImpl>) personal.getClass());

  //  Will proxy Adj. class ByteCode output to file 
  generatorClass(proxy);

  //  Call proxy object 
  proxy.introduction();
}

3.2. 4, View Run Results

Running the test case, you can see the following Hello2 The realization of 1 kind of effect

Hello, everyone!
I'm a programmer!
See you later!

3.2. 5, Exploring the Secrets of Dynamic Agents

Follow Hello2 Test 1, let's also look at the generated introduction6 Documents


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

It can be found that, with Hello2 There is no difference between the dynamic proxy of.

4. How to choose

Since there are two ways to implement it, how should we choose it?

On two principles:

The target class has an interface implementation, Hello2 And CgLib You can choose, just be happy
The target class does not implement any interface, so you can only use the CgLib It's over

5. Postscript

In fact, when I first saw the dynamic proxy, I couldn't understand why we threw the target class to the proxy class when we all put the target class new out. Why not call the method corresponding to the target class directly?

Later, I discovered that I didn't understand the use scenario of dynamic agents. The scenario is very clear, that is:

Do not modify the existing code (meet the requirements of the design pattern)
Enhance the existing method
The key is enhancement. We can add a lot of processing logic in the proxy class to achieve enhancement effect. Just like scalpers grab tickets better than us.


Related articles: