Java dynamic proxy: Java dynamic proxy

Dynamic proxies are widely used in Java, such as Spring AOP, Hibernate data queries, back-end mocks of test frameworks, RPC remote calls, Java annotation object acquisition, logging, user authentication, global exception handling, performance monitoring, and even transaction processing.

This article focuses on two common dynamic proxy methods in Java: JDK native dynamic proxy and CGLIB dynamic proxy.

Since Java dynamic proxy is closely related to Java reflection, please make sure you are familiar with Java reflection. Please refer to the previous article “Java Reflection in Detail”.

The proxy pattern

The Java dynamic proxy that this article introduces relates to the proxy pattern in design patterns. What is a proxy pattern?

Proxy mode: An object is provided with a proxy that controls access to the real object. The proxy pattern is a structural design pattern.

There are three types of roles in proxy mode:

Subject (Abstract topic role) : Defines the public external method of the proxy class and the real topic, and is also the method of the proxy class to proxy the real topic;

RealSubject (RealSubject role) : classes that actually implement business logic;

Proxy (Proxy topic role) : Used to Proxy and encapsulate real topics;

The structure of proxy mode is relatively simple, and its core is proxy class. In order to make clients treat real objects and proxy objects consistently, an abstraction layer is introduced in proxy mode

Agent modes are classified according to responsibilities (application scenarios) and can be divided into at least the following types: 1. Remote agent. 2. Virtual proxy. 3. Copy-on-write proxy. 4. Protect or Access agents. Cache proxy. 6. Firewall agent. 7. Synchronization agents. 8. Smart Reference agents, etc.

According to the bytecode creation time, it can be classified into static proxies and dynamic proxies:

  • By static, we mean bytecode files with proxy classes that exist before the program runs, and the relationship between proxy classes and real subject roles is determined before the program runs.
  • The source code of the dynamic proxy is dynamically generated by the JVM during the program running based on reflection and other mechanisms, so there is no bytecode file of the proxy class before running

Static agent

Let’s learn about static proxies by example, then understand the disadvantages of static proxies, and then learn about the main character of this article: dynamic proxies

Write an interface, UserService, and an implementation class for that interface, UserServiceImpl

public interface UserService {
    public void select(a);   
    public void update(a);
}

public class UserServiceImpl implements UserService {  
    public void select(a) {  
        System.out.println("Query selectById." ");
    }
    public void update(a) {
        System.out.println("Update update"); }}Copy the code

We will enhance UserServiceImpl with a static proxy to log some logs before calling SELECT and UPDATE. Write a proxy class, UserServiceProxy. The proxy class needs to implement UserService

public class UserServiceProxy implements UserService {
    private UserService target; // The proxied object

    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    public void select(a) {
        before();
        target.select();    // This is where the methods of the real subject role are actually called
        after();
    }
    public void update(a) {
        before();
        target.update();    // This is where the methods of the real subject role are actually called
        after();
    }

    private void before(a) {     // execute before executing the method
        System.out.println(String.format("log start time [%s] ".new Date()));
    }
    private void after(a) {      // execute after executing the method
        System.out.println(String.format("log end time [%s] ".newDate())); }}Copy the code

Client testing

public class Client1 {
    public static void main(String[] args) {
        UserService userServiceImpl = new UserServiceImpl();
        UserService proxy = newUserServiceProxy(userServiceImpl); proxy.select(); proxy.update(); }}Copy the code

The output

logStart time [Thu Dec 20 14:13:25 CST 2018] query selectByIdlog end time [Thu Dec 20 14:13:25 CST 2018] 
logStart time [Thu Dec 20 14:13:25 CST 2018log end time [Thu Dec 20 14:13:25 CST 2018] 
Copy the code

With static proxies, we achieved functionality enhancements without intruding into the original code, which is one of the advantages of static proxies.

Disadvantages of static proxies

While static proxies are simple to implement and do not intrude into the original code, their disadvantages can be exposed when the scenario is slightly more complex.

When you need to delegate multiple classes, there are two ways to implement the same interface as the target object:

  • Maintaining only one proxy class, which implements multiple interfaces, results in the proxy class becoming too large
  • Creating multiple proxy classes, one for each target object, can result in too many proxy classes

2. When interfaces need to be added, deleted, or modified, both the target object and the proxy class need to be modified at the same time, which is difficult to maintain.

How to improve?

Of course, the proxy class is dynamically generated, that is, dynamic proxy.

Why can classes be generated dynamically?

This involves the class loading mechanism of the Java VIRTUAL machine. It is recommended to refer to section 7.3 of the Class loading process in Understanding the Java Virtual Machine.

The Java VIRTUAL machine class loading process is divided into five stages: loading, verification, preparation, parsing, and initialization. The loading stage needs to complete the following three things:

  1. Gets the binary byte stream that defines a class by its fully qualified name
  2. Transform the static storage structure represented by this byte stream into the runtime data structure of the method area
  3. Generates an in-memory representation of this classjava.lang.ClassObject that serves as the various data access points for this class

Since the virtual machine specification is not specific about these three points, the actual implementation is quite flexible. Regarding point 1, there are many ways to obtain the binary byte stream (class bytecode) of a class:

  • From the ZIP package, which is the basis for JAR, EAR, WAR, and so on
  • From the network. The typical application is an Applet
  • Runtime compute generation, this scenario is the most used dynamic Proxy technology, in Java. Lang. Reflect. The Proxy class, is to use the ProxyGenerator. GenerateProxyClass to generate form for a specific interface*$ProxyThe binary byte stream of the proxy class
  • Generated by other files, the typical application is JSP, which generates the corresponding Class Class from JSP files
  • Get it from the database and so on

So a dynamic proxy is a way to compute the bytecode of the proxy class, based on the interface or target object, and load it into the JVM for use. But how do you calculate it? How do you generate it? The situation may be more complicated than imagined, and we need to rely on existing plans.

Common bytecode manipulation class libraries

Here are some introductions: java-source.net/open-source…

  • Apache BCEL (Byte Code Engineering Library) is a widely used Framework for Java Classworking that delve into the details of class operations in JVM assembly language.
  • ObjectWeb ASM: is a Java bytecode manipulation framework. It can be used to dynamically generate stub root classes or other proxy classes directly in binary form, or to dynamically modify classes at load time.
  • CGLIB(Code Generation Library) is a powerful, high-performance, and high-quality Code Generation Library that extends JAVA classes and implements interfaces at run time.
  • Javassist: Java’s load-time reflection system, which is a library for editing bytecode in Java; It enables Java programs to define new classes at run time and modify class files before the JVM loads.
  • .

Thinking direction of dynamic proxy implementation

In order to keep the generated proxy class consistent with the target object (real subject roles), the following two most common ways will be introduced from now on:

  1. By implementing the interface -> JDK dynamic proxy
  2. By inheriting classes -> CGLIB dynamic proxy

Note: Using ASM can be quite demanding on the user, and using Javassist can be a bit of a hassle

JDK dynamic proxy

The JDK dynamic Proxy involves two main categories: Java. Lang. Reflect. The Proxy and Java lang. Reflect. InvocationHandler, we are still learning through the case

Write a call logic processor LogHandler class, provide log enhancement, and implement the InvocationHandler interface; Maintain a target object in LogHandler, which is the propped object (real subject role); Write the logical handling of the method invocation in the Invoke method

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

public class LogHandler implements InvocationHandler {
    Object target;  // The proxied object, the 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 target's method method
        after();
        return result;  // Returns the execution result of the method
    }
    // Execute before invoking the invoke method
    private void before(a) {
        System.out.println(String.format("log start time [%s] ".new Date()));
    }
    // Execute after invoking the invoke method
    private void after(a) {
        System.out.println(String.format("log end time [%s] ".newDate())); }}Copy the code

Write a client to obtain the dynamically generated Proxy class object must use the Proxy class newProxyInstance method, specific steps visible code and annotations

import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client2 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        // Set variables can hold dynamic proxy classes. The default name is $Proxy0 format
        // System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        Create the proxied object, the implementation class of the UserService interface
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        // 2. Obtain the corresponding ClassLoader
        ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
        UserServiceImpl implements only one interface, UserService.
        Class[] interfaces = userServiceImpl.getClass().getInterfaces();
        // 4. Create a call request handler that will be passed to the proxy class to handle method calls on all proxy objects
        // Create a custom log handler and pass in the actual execution object, userServiceImpl
        InvocationHandler logHandler = new LogHandler(userServiceImpl);
        In this process, A.dk dynamically creates bytecode B in memory equivalent to the.class file based on the passed parameter information. C. Then call newInstance() to create a proxy instance */
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
        // Call the proxy's method
        proxy.select();
        proxy.update();
        
        // Save the proxy class generated by the JDK dynamic proxy as UserServiceProxy
        // ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");}}Copy the code

The results

logStart time [Thu Dec 20 16:55:19 CST 2018] query selectByIdlog end time [Thu Dec 20 16:55:19 CST 2018] 
logStart time [Thu Dec 20 16:55:19 CST 2018log end time [Thu Dec 20 16:55:19 CST 2018] 
Copy the code

The main methods of InvocationHandler and Proxy are described as follows:

java.lang.reflect.InvocationHandler

Object Invoke (Object Proxy, Method Method, Object[] args) defines the actions that a proxy Object wants to perform when calling a Method. It is used to focus Method calls on dynamic proxy objects

java.lang.reflect.Proxy

Static InvocationHandler getInvocationHandler(Object proxy) is used to get the InvocationHandler associated with the specified proxy Object

static Class
getProxyClass(ClassLoader loader, Class
… Interfaces) returns the proxy class for the specified interface

static Object newProxyInstance(ClassLoader loader, Class
[] interfaces, InvocationHandler h) constructs a new instance of the proxy class that implements the specified interface. All methods call the invoke method of the given processor object

static boolean isProxyClass(Class
cl) returns whether CL is a proxy class

The invocation of the proxy class

What does the generated proxy class look like? With the tools of the following classes, the class of agency preserved to know (by setting the environment variables sun. Misc. ProxyGenerator. SaveGeneratedFiles = true can also save the proxy class)

import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;

public class ProxyUtils {
    /** * Saves binary bytecode dynamically generated based on class information to disk, the default is in the clazz directory * params: clazz needs to generate dynamic proxy class * proxyName: for the dynamically generated proxy class name */
    public static void generateClassFile(Class clazz, String proxyName) {
        // Generate bytecode based on the class information and the provided proxy class name
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;
        try {
            // Save to hard disk
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch(IOException e) { e.printStackTrace(); }}}}Copy the code

Then add a line at the end of main of the Client2 test class

// Save the proxy class generated by the JDK dynamic proxy as UserServiceProxy
ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
Copy the code

After IDEA is run again, userServiceProxy. class can be found in target’s classpath

The code for UserServiceProxy looks like this:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.UserService;

public final class UserServiceProxy extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public UserServiceProxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        / / to omit...
    }

    public final String toString(a) throws  {
        / / to omit...
    }

    public final void select(a) throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final int hashCode(a) throws  {
        / / to omit...
    }

    public final void update(a) throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(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("proxy.UserService").getMethod("select");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("proxy.UserService").getMethod("update");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

From the UserServiceProxy code we can see:

  • UserServiceProxy inherits the Proxy class and implements all the proxied interfaces, as well as the equals, hashCode, toString, and other methods
  • Since UserServiceProxy inherits the Proxy class, each Proxy class is associated with an InvocationHandler method call handler
  • Class and all methods are treated bypublic finalModifier, so proxy classes can only be used, not inherited
  • Each Method is described by a Method object, which is created in the static static code block toM + digitalFormat name of
  • When a method is calledsuper.h.invoke(this, m1, (Object[])null);Call, wheresuper.h.invokeIs actually passed to when the proxy is createdProxy.newProxyInstanceThe LogHandler object, which inherits the InvocationHandler class and is responsible for the actual call processing logic

LogHandler’s Invoke method receives parameters such as Method and args, does some processing, and then uses reflection to make the propped object target execute the method

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);       // Call target's method method
        after();
        return result;  // Returns the execution result of the method
    }
Copy the code

The JDK dynamic proxy performs a method call as follows:

Proxy class call process I believe we are clear, and Proxy source code analysis, also please refer to other articles or directly look at the source code

CGLIB dynamic proxy

Maven imports the CGLIB package and writes a UserDao class that has no interface and only two methods, select() and update().

public class UserDao {
    public void select(a) {
        System.out.println("UserDao query selectById");
    }
    public void update(a) {
        System.out.println("UserDao update update"); }}Copy the code

Write a LogInterceptor that inherits MethodInterceptor and uses it for method intercepting callbacks

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

public class LogInterceptor implements MethodInterceptor {
    / * * *@paramObject Indicates the object to be enhanced *@paramMethod Method of interception *@paramThe objects array represents a list of arguments. Basic data types are passed in wrapper types such as int-->Integer, long-long, and double--> double *@paramMethodProxy represents a proxy for a method, and the invokeSuper method represents a call to a prosted-object method *@returnExecution result *@throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(object, objects);   InvokeSuper executes the methods of the original class and method.invoke executes the methods of the subclass
        after();
        return result;
    }
    private void before(a) {
        System.out.println(String.format("log start time [%s] ".new Date()));
    }
    private void after(a) {
        System.out.println(String.format("log end time [%s] ".newDate())); }}Copy the code

test

import net.sf.cglib.proxy.Enhancer;

public class CglibTest {
    public static void main(String[] args) {
        DaoProxy daoProxy = new DaoProxy(); 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dao.class);  // Set the superclass. Cglib does this through inheritance
        enhancer.setCallback(daoProxy);

        Dao dao = (Dao)enhancer.create();   // Create the proxy classdao.update(); dao.select(); }}Copy the code

The results

logStart time [Fri Dec 21 00:06:40 CST 2018] UserDao Query selectByIdlog end time [Fri Dec 21 00:06:40 CST 2018] 
logStart time [Fri Dec 21 00:06:40 CST 2018] UserDao Updatelog end time [Fri Dec 21 00:06:40 CST 2018] 
Copy the code

You can further filter multiple MethodInterceptors

public class LogInterceptor2 implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(object, objects);
        after();
        return result;
    }
    private void before(a) {
        System.out.println(String.format("log2 start time [%s] ".new Date()));
    }
    private void after(a) {
        System.out.println(String.format("log2 end time [%s] ".newDate())); }}// Callback filter: CGLib callbacks can be set to perform different callback logic for different methods, or no callback at all.
public class DaoFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if ("select".equals(method.getName())) {
            return 0;   // The first interceptor in the Callback list
        }
        return 1;   // Callback is the second interceptor in the list, return 2 is the third, and so on}}Copy the code

The test again

public class CglibTest2 {
    public static void main(String[] args) {
        LogInterceptor logInterceptor = new LogInterceptor();
        LogInterceptor2 logInterceptor2 = new LogInterceptor2();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserDao.class);   // Set the superclass. Cglib does this through inheritance
        enhancer.setCallbacks(new Callback[]{logInterceptor, logInterceptor2, NoOp.INSTANCE});   // Set multiple interceptors. Noop. INSTANCE is an empty interceptor and does nothing
        enhancer.setCallbackFilter(new DaoFilter());

        UserDao proxy = (UserDao) enhancer.create();   // Create the proxy classproxy.select(); proxy.update(); }}Copy the code

The results

log start time [Fri Dec 21 00:22:39 CST 2018UserDao queries selectById log End time [Fri Dec]21 00:22:39 CST 2018] 
log2 start time [Fri Dec 21 00:22:39 CST 2018] UserDao Update Update log2 End time [Fri Dec21 00:22:39 CST 2018] 
Copy the code

CGLIB creates dynamic proxy classes using the following pattern:

  1. Find all non-final public type method definitions on the target class;
  2. Convert the definitions of these methods into bytecode;
  3. Converts the composed bytecode into the corresponding proxy class object;
  4. Implements the MethodInterceptor interface, which handles requests for all methods on the proxy class

Compare JDK dynamic proxies with CGLIB dynamic proxies

JDK dynamic proxy: Implemented based on Java reflection, business classes that implement the interface are required to generate proxy objects in this way.

Cglib dynamic proxy: Implemented based on ASM mechanism, it generates subclasses of business classes as proxy classes.

Advantages of JDK Proxy:

  • Minimizing dependencies, reducing dependencies means easier development and maintenance, and JDK support itself is probably more reliable than Cglib.
  • Smooth JDK version upgrades, while bytecode libraries often need to be updated to be usable on new versions of Java.
  • The code implementation is simple.

Advantages based on cGLIb-like frameworks:

  • No need to implement the interface, to achieve no intrusion of proxy class
  • We only work on the classes we care about, without having to do extra work for other related classes.
  • A high performance

The interview questions

From the Internet, used to help understand and master, welcome to add

Describe how dynamic proxies can be implemented? Describe your strengths and weaknesses

Proxies can be divided into “static proxy” and “dynamic proxy”, dynamic proxy is divided into “JDK dynamic proxy “and “CGLIB dynamic proxy” implementation.

Static proxy: Both the proxy Object and the Real Object inherit the same interface. The proxy Object points to an instance of the Real Object, exposing the proxy Object while calling the Real Object

  • Advantages: The service logic of real objects can be protected to improve security.
  • Disadvantages: Different interfaces must have different proxy class implementation, can be very redundant

JDK dynamic proxy:

  • In order to solve the static proxy, generate a large number of proxy classes caused by the redundancy;

  • The JDK dynamic proxy simply needs to implement the InvocationHandler interface and override the Invoke method to implement the proxy.

  • JDK proxies use reflection to generate bytecodes for the proxyxx.class proxy class and generate objects

  • JDK dynamic proxies can only Proxy interfaces because the Proxy class itself extends Proxy, and Java does not allow multiple inheritance, but does allow multiple interfaces to be implemented

  • Advantages: Solve the problem of redundant proxy implementation classes in static proxy.

  • Disadvantages: JDK dynamic proxy is based on interface design implementation, if there is no interface, will throw exceptions.

Additional agent:

  • Because JDK dynamic proxy can only be based on interface design, and for the absence of interfaces, JDK approach can not solve the problem;

  • CGLib uses very low-level bytecode technology. Its principle is to create a subclass of a class by bytecode technology, and use method interception technology in the subclass to intercept all the calls of the parent class method, and then weave the crosscutting logic to achieve dynamic proxy.

  • Implement the MethodInterceptor interface, rewrite the intercept method, through the Enhancer class callback method to implement.

  • However, CGLib takes a lot more time to create proxy objects than the JDK, so for singleton objects that don’t need to be created frequently, CGLib is more appropriate than the JDK approach.

  • At the same time, because CGLib is subclassed dynamically, there is no proxy for final methods.

  • Advantages: Dynamic proxy can be realized without interface, and bytecode enhancement technology, performance is also good.

  • Disadvantages: The technical implementation is relatively difficult to understand.

CGlib implements proxies for interfaces?

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import proxy.UserService;
import java.lang.reflect.Method;

/** * Create a factory for the proxy class that implements the MethodInterceptor interface. * (1) Declare the member variables of the target class and create a constructor that takes the object of the target class as an argument. For receiving the target object * (2) defines the proxy generation method for creating the proxy object. Method names are arbitrary. The proxy object, a subclass of the target class * (3), defines callback interface methods. Enhancements to the target class are done here */
public class CGLibFactory implements MethodInterceptor {
    // Declare a member variable of the target class
    private UserService target;

    public CGLibFactory(UserService target) {
        this.target = target;
    }
    // Define proxy generation methods for creating proxy objects
    public UserService myCGLibCreator(a) {
        Enhancer enhancer = new Enhancer();
        // Sets the parent class for the proxy object, specifying the target class
        enhancer.setSuperclass(UserService.class);
        /** * setCallback() specifies the MethodIntecepter interface as a subinterface */
        enhancer.setCallback(this);
        return (UserService) enhancer.create();// create to generate a CGLib proxy object
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("start invoke " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("end invoke " + method.getName());
        returnresult; }}Copy the code

Reference: “the Java core technology” volume 1 “deep understanding of Java virtual machine” 7.3 Java docs: docs.oracle.com/javase/8/do… Java dynamic proxy Mechanism (JDK and Cglib, Javassist, ASM) Static proxy and dynamic proxy

Afterword.

Welcome to comment, forward, share, your support is my biggest motivation

For more, visit my personal blog at laijianfeng.org

Pay attention to the wechat official account of xiao Shuang Feng and receive blog posts timely