06 What Principle Is Dynamic Proxy Based On

06 What principle is dynamic proxy based on #

Programming languages usually have different ways of classification, and dynamic typing and static typing are one way to classify them. Simply put, it refers to whether the language checks type information at runtime or at compile time.

There is also a similar comparison between strong typing and weak typing, which refers to whether explicit (forced) type conversion is required when assigning different types of variables.

So how do we classify the Java language? It is generally considered a statically typed and strongly typed language, but because it provides mechanisms such as reflection, it also has some of the capabilities of dynamic typing languages.

To get to the point, today I want to ask you a question: Talk about the Java reflection mechanism, and what is the principle behind dynamic proxy?

Typical Answer #

The reflection mechanism is a basic feature provided by the Java language, which gives programs the ability to introspect at runtime. With reflection, we can directly manipulate classes or objects, such as obtaining the class definition of a certain object, getting the attributes and methods declared in the class, invoking methods or constructing objects, and even modifying class definitions at runtime.

Dynamic proxy is a mechanism that facilitates the dynamic construction of proxies and the dynamic handling of proxy method invocations at runtime. Many scenarios rely on similar mechanisms, such as wrapping RPC calls and aspect-oriented programming (AOP).

There are many ways to implement dynamic proxy. For example, the dynamic proxy provided by the JDK itself primarily utilizes the aforementioned reflection mechanism. There are also other implementation methods, such as using bytecode manipulation mechanisms with higher performance, such as ASM, cglib (based on ASM), Javassist, etc.

Analysis of Key Points #

My initial impression of this question is that it may be somewhat leading, as one might instinctively think that dynamic proxy is implemented using the reflection mechanism. While this is not entirely wrong, it is not comprehensive. The focus should be on the functionality, as there are many ways to implement it. In general, this question tests another fundamental mechanism of the Java language: reflection. It is like a kind of magic that introduces runtime introspection capabilities, giving Java language a surprising vitality. By operating on metadata or objects at runtime, Java can flexibly manipulate information that can only be determined at runtime. Dynamic proxy is an widely used technique in product development that extends from this mechanism, and it can elegantly solve many tedious repetitive programming tasks.

From the perspective of examining knowledge points, this question involves a wide range of knowledge, so the interviewer can expand or dig deeper into many aspects, such as:

  • Testing your understanding and mastery of the reflection mechanism.
  • What problems does dynamic proxy solve, and what are the application scenarios in your business system?
  • What are the design and implementation differences between JDK dynamic proxy and other methods like cglib, and how do you choose between them?

It seems that these points cannot be covered in a short article, but I will try to summarize them as much as possible in the knowledge expansion section.

Knowledge Expansion #

  1. Reflection Mechanism and its Evolution

For the reflection mechanism of the Java language itself, if you take a look at the relevant abstractions in the java.lang or java.lang.reflect package, you will have a very intuitive impression. Class, Field, Method, Constructor, etc., are all corresponding to the metadata we manipulate classes and objects. Many articles or books have provided detailed introductions to the programming of various typical use cases of reflection, so I will not go into further detail here. At the very least, you need to master basic scenario programming. Here is the reference documentation provided by the official Java tutorial:

https://docs.oracle.com/javase/tutorial/reflect/index.html.

There is one point I need to mention specifically about reflection, which is the AccessibleObject.setAccessible(boolean flag) provided by reflection. Most of its subclasses have also overridden this method. The so-called accessible here can be understood as modifying the access modifiers of members, such as public, protected, and private. This means that we can modify the member access restrictions at runtime!

The application scenarios of setAccessible are very common and can be found in our daily development, testing, dependency injection, and various frameworks. For example, in O/R Mapping frameworks, we often need to generate the logic for setters and getters of a Java entity object at runtime, which is necessary for loading or persisting data. Frameworks can often leverage reflection to do this, eliminating the need for developers to manually write repetitive code.

Another typical scenario is bypassing API access controls. In our daily development, we may be forced to call internal APIs to do certain things. For example, a custom high-performance NIO framework needs to explicitly release DirectBuffers, and using reflection to bypass the restrictions is a common approach.

However, after Java 9, the use of this method may be controversial because the modular system introduced by the Jigsaw project restricts the reflection access for encapsulation considerations. Jigsaw introduces the concept of “Open”. Only when the module being reflected and the specified package are “Open” to the reflecting caller module can setAccessible be used; otherwise, it will be considered an illegal operation. If our entity class is defined inside a module, we need to declare it explicitly in the module descriptor:

module MyEntities {
    // Open for reflection
    opens com.mycorp to java.persistence;
}

Due to the extensive use of reflection, according to community discussions, Java 9 still retains the Java 8 compatible behavior. However, it is very likely that in future versions, the restrictions mentioned earlier for setAccessible will be fully enabled, that is, only when the module being reflected and the specified package are “Open” to the reflecting caller module can setAccessible be used. We can explicitly set this behavior using the following parameter:

--illegal-access={ permit | warn | deny }
  1. Dynamic Proxy

The previous question asked about dynamic proxy, so let’s take a look at what problem it actually solves.

Firstly, it is a proxy mechanism. If you are familiar with the proxy pattern in design patterns, you would know that a proxy can be seen as a wrapper for the target being called. This means that the call to the target code does not happen directly, but is completed through the proxy. In fact, many dynamic proxy scenarios can also be seen as applications of the decorator pattern, which I will supplement in the upcoming column on design patterns.

Through proxy, callers and implementers can be decoupled. For example, when making RPC calls, the internal framework operations such as addressing, serialization, and deserialization are often not significant to the caller. Through proxy, a more user-friendly interface can be provided.

The development of proxy has gone through the transition from static to dynamic, which originated from the additional work introduced by static proxy. Similar to ancient technologies like early RMI, static stubs and other files needed to be generated using tools like rmic, which added a lot of tedious preparation work unrelated to our business logic. With the dynamic proxy mechanism, the corresponding stub classes, etc., can be generated at runtime, and the corresponding invocation operations are also dynamically completed, greatly improving our productivity. The improved RMI no longer requires manual preparation of these files, although it is still a relatively old and outdated technology, which may be gradually discontinued in the future.

This may not be clear enough, so let’s take a simple example of JDK dynamic proxy. The following code just adds a print statement. In production systems, similar logic can be easily extended for diagnostics, flow control, etc.

public class MyDynamicProxy {
    public static void main(String[] args) {
        HelloImpl hello = new HelloImpl();
        MyInvocationHandler handler = new MyInvocationHandler(hello);
        // Construct the instance of the proxy code
        Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
        // Call the proxy method
        proxyHello.sayHello();
    }
}

interface Hello {
    void sayHello();
}
class HelloImpl implements Hello {
    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
}

class MyInvocationHandler implements InvocationHandler {
    private Object target;
    
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Invoking sayHello");
        Object result = method.invoke(target, args);
        return result;
    }
}

The above JDK Proxy example provides a simple implementation of dynamic proxy construction and proxy operations. First, an InvocationHandler is implemented. Then, using the interface Hello as a link, a proxy object is constructed for the target object to be called. In this way, the application can indirectly invoke the target logic through the proxy object, and the proxy provides a convenient entry point for inserting additional logic (in this case, println).

From the perspective of API design and implementation, this implementation still has limitations because it is interface-centered, which is a restriction on the callee that is not very meaningful. We instantiate the Proxy object, not the real callee type, which may bring inconvenience and degradation of capabilities in practice.

If the callee does not implement an interface, but we still want to use dynamic proxy mechanisms, other methods can be considered. We know that Spring AOP supports two modes of dynamic proxy, JDK Proxy or cglib. If we choose the cglib method, we will find that the dependency on interfaces is overcome.

Cglib dynamic proxy is created by subclassing the target class, and because it is subclassing, we can achieve a similar effect to using the callee itself. In Spring programming, the framework usually handles this situation, but we can also explicitly specify it. I won’t discuss the implementation details of similar solutions in detail.

So how do we choose in development? Let me briefly compare the advantages of the two methods.

Advantages of JDK Proxy:

  • Minimizes dependencies, reducing dependencies means simplifying development and maintenance. The support from JDK itself may be more reliable than cglib.
  • Smoothly upgrade JDK versions, while bytecode libraries usually need to be updated to ensure usability on new versions of Java.
  • Simple implementation.

Advantages of cglib-like frameworks:

  • Sometimes, it may be inconvenient for the callee to implement additional interfaces. From a certain perspective, requiring the callee to implement interfaces is a somewhat intrusive practice, while cglib dynamic proxy does not have this limitation.
  • Only operates on the classes we care about, without adding extra work for other related classes.
  • High performance.

In addition, from a performance perspective, I want to add a few words. I remember someone came to the conclusion that JDK Proxy is tens of times slower than cglib or Javassist. To be honest, without arguing about the specific details of benchmarks, in mainstream JDK versions, JDK Proxy can provide equivalent performance levels in typical scenarios, and there is generally no wide gap in order of magnitude. Moreover, in modern JDK, the performance of reflection mechanism itself has been greatly improved and optimized. At the same time, many JDK features are not solely based on reflection, but also use ASM for bytecode manipulation.

In the selection process, performance may not be the only consideration. Factors such as reliability, maintainability, and programming effort are often more important. After all, standard libraries and the threshold for reflection programming are much lower, and the amount of code is more controllable. If we compare the investment in dynamic proxy development in different open source projects, we can also see this.

Dynamic proxies are widely used. Although they initially came into our sight due to their use in RPC and other scenarios, the use cases for dynamic proxies are much broader. They are a perfect fit for aspect-oriented programming (AOP) such as Spring AOP. In future columns, I will further analyze the purpose and capabilities of AOP. In short, it can be seen as a supplement to OOP because OOP is not expressive enough for handling distributed and tangled logic across different objects or classes, such as performing specific tasks at different stages of different modules, like logging, user authentication, global exception handling, performance monitoring, and even transaction handling. You can refer to the following diagram.

AOP, through (dynamic) proxy mechanism, allows developers to free themselves from these cumbersome tasks, greatly improving the abstraction and reusability of the code. From a logical point of view, similar proxies we use in software design and implementation, such as Facade and Observer, can be elegantly implemented using dynamic proxies.

Today, I briefly reviewed the reflection mechanism, discussed the changes happening in reflection in the evolution of the Java language, and further discussed the dynamic proxy mechanism and related aspect-oriented programming, analyzed the problems it solves, and discussed the considerations in real-world practices.

Practice #

Do you have a clear understanding of the topic we discussed today? I’ll leave you with a question to ponder. In what scenarios have you used dynamic proxy in your work? What implementation technique did you choose? What was your reasoning behind the choice?

Please write your thoughts on this question in the comment section. I will select a well-considered comment and send you a study encouragement reward. Feel free to discuss with me.

Is your friend also preparing for an interview? You can “invite a friend to read” and share today’s question with them. Maybe you can help them out.