Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 44

Dynamic Proxies: Runtime Interface Implementation

Recommended resources

Introduction

In our journey through Java's Reflection API, we've learned to inspect classes, create objects, invoke methods, and read metadata via annotations. Each step has taken us deeper into the dynamic capabilities of the JVM. Now, we arrive at one of the most powerful and practical applications of reflection: dynamic proxies. This technique is the secret sauce behind many advanced frameworks, including Spring AOP and mocking libraries like Mockito.

This lesson addresses a key aspect of Java's runtime dynamism: how it can generate and use new types on the fly. We will explore how the java.lang.reflect.Proxy class works, focusing specifically on its mechanism for generating a new class at runtime that implements a set of interfaces, intercepting every method call for custom processing. Understanding this is crucial for grasping how modern backend frameworks achieve cross-cutting concerns like logging, security, and transaction management.


The Proxy Pattern and Its Dynamic Evolution

At its core, a proxy is an object that stands in for another object. In the classic Proxy Design Pattern, you would manually write a proxy class that implements the same interface as a "real" subject. This proxy would hold a reference to the real object and delegate calls to it, perhaps adding some extra logic before or after the delegation.

The limitation of this static approach is its inflexibility. For every class you want to proxy, you must write another class. What if you want to apply the same logic (e.g., logging every method call) to dozens of different service interfaces? This is where dynamic proxies come in. Instead of you writing the proxy class, the Java runtime generates it for you.

Dynamic Proxies in Java | Baeldung

This article from Baeldung provides a concise introduction to dynamic proxies.

Please read the introduction, which explains the core idea of a dynamic proxy as a facade that can pretend to be any interface, routing all method calls to a single handler. Then, read the section on the Invocation Handler. This introduces the central component that defines the proxy's behavior.

As the article explains, the key is that all method invocations on the proxy object are redirected to a single method in an InvocationHandler. This gives you a single point of control to intercept and process calls to any method of any interface you choose to proxy.


The Two Core Components: Proxy and InvocationHandler

Creating a dynamic proxy involves two main parts from the java.lang.reflect package:

  1. InvocationHandler Interface: This is a functional interface that you implement. Its single method, invoke(Object proxy, Method method, Object[] args), is the heart of the proxy's logic. Every time a method is called on the proxy instance, this invoke method is executed instead.

    • proxy: The proxy instance that the method was invoked on.
    • method: The Method object corresponding to the interface method that was called.
    • args: An array of arguments passed to the method.
  2. Proxy Class: This class provides the static factory method, newProxyInstance(), which is used to generate the proxy object.

Let's walk through the creation process.

How to Use Java's Reflection API for Dynamic Proxy Classes

This article from W3Computing gives a clear, step-by-step example of creating a simple dynamic proxy.

Please read section 3, "Creating a Simple Dynamic Proxy". Follow the four steps: defining an interface, creating a real implementation, implementing an InvocationHandler, and finally, creating the proxy instance. Pay close attention to the Proxy.newProxyInstance() call and its three arguments.

The call to Proxy.newProxyInstance(classLoader, interfaces, handler) is the critical step. Let's break down its arguments:

  • ClassLoader classLoader: The class loader that will define the newly generated proxy class. You can usually get this from the target interface's class (Service.class.getClassLoader()).
  • Class<?>[] interfaces: An array of Class objects representing the interfaces that the generated proxy class must implement. This is a fundamental constraint: JDK dynamic proxies can only implement interfaces, not extend classes.
  • InvocationHandler handler: An instance of your invocation handler, which will process all method calls made on the proxy.

When proxyService.perform() is called in the example, the JVM doesn't call RealService.perform() directly. Instead, it calls the invoke method on your ServiceInvocationHandler, passing it the Method object for perform(). Your handler then uses reflection (method.invoke(realService, args)) to delegate the call to the actual object, wrapping it with logging statements.


How It Works: Generating a Class at Runtime

You've now seen what dynamic proxies do, but the core of your learning goal is to understand how they do it. The term "dynamic" is literal: Proxy.newProxyInstance() orchestrates the creation of a brand new Java class on the fly.

This diagram illustrates the internal sequence of operations within `java.lang.reflect.Proxy`. The process begins with a call to `newProxyInstance()` and culminates in the dynamic generation and loading of a proxy class using `ProxyGenerator` and `JavaLangAccess`.

Let's trace the steps shown in this diagram, which reveal the "inner workings" you're interested in:

  1. When you call Proxy.newProxyInstance(), it first calls getProxyClass(). This method checks a cache to see if a proxy class for the requested set of interfaces has already been created.
  2. If not found in the cache, the process continues. A ProxyBuilder and ProxyGenerator are employed to generate the raw bytecode for a new class in memory.
  3. This generated class will have a name like $Proxy0, $Proxy1, etc. It is hardcoded to extend java.lang.reflect.Proxy and will be declared to implement all the interfaces you provided (e.g., public final class $Proxy0 extends Proxy implements com.example.MyInterface).
  4. For each method in the interfaces, the ProxyGenerator creates a corresponding method in the new class. The body of each generated method is essentially boilerplate that forwards the call to the invoke() method of the InvocationHandler associated with that proxy instance.
  5. Once the bytecode (byte[]) is generated, it's passed to defineClass() (a low-level method available through JavaLangAccess to the core JDK, or available on ClassLoader instances). This step formally loads the bytecode into the JVM, turning it into a usable Class object.
  6. This new Class object is then cached for future use.
  7. Finally, newProxyInstance() uses reflection to find the new class's constructor (which takes an InvocationHandler as an argument) and instantiates it to create the final proxy object you get back.

This entire process—bytecode generation, class loading, and instantiation—happens seamlessly at runtime, giving you an object that behaves like an implementation of your interface but with every method call intercepted.


The Limitation of JDK Proxies and the CGLIB Alternative

The mechanism we've just explored has one significant limitation: it is strictly interface-based. The generated class already extends java.lang.reflect.Proxy, and since Java does not support multiple class inheritance, it cannot extend your custom class.

So, what if you need to proxy a class that doesn't implement any interfaces? This is a common scenario, especially with third-party libraries or legacy code. The solution lies in a different proxying technique provided by a library called CGLIB (Code Generation Library). Spring, Hibernate, and many other frameworks use CGLIB as a fallback when JDK proxies aren't sufficient.

This diagram contrasts JDK Proxies, which are interface-based and work by implementing a shared interface, with CGLIB Proxies, which are subclass-based and work by extending the target class.

The key difference is:

  • JDK Proxy: Creates a class that implements one or more interfaces.
  • CGLIB Proxy: Creates a class that extends a target class.

CGLIB's approach is to generate a subclass of your target class at runtime. It then overrides the non-final methods to introduce the proxying logic (the "Extra Logic" in the diagram) before (optionally) calling the original method in the superclass (super.method()). This is why CGLIB cannot proxy final classes or final methods.

The following video from the Spring team provides an excellent demonstration of JDK proxies and then discusses the distinction with CGLIB in a real-world context.

Spring Tips: Proxies

This Spring Tips video explains proxies and clearly distinguishes between the JDK's built-in mechanism and CGLIB.

First, watch the demonstration of a JDK dynamic proxy from this segment. It mirrors the examples we've discussed, using Proxy.newProxyInstance and an InvocationHandler to add transactional behavior. Next, skip to the discussion comparing JDK and CGLIB proxies from this part. This directly addresses the "interface vs. concrete class" limitation and explains why Spring uses both strategies.


Conclusion

In this lesson, you've unlocked the mechanics of one of Java's most powerful runtime features. You've seen that dynamic proxies are not magic, but a clever use of runtime code generation and reflection to create flexible and powerful software architectures.

Key Takeaways:

  • Dynamic proxies generate new classes at runtime that implement specified interfaces. This is done by creating bytecode in memory and loading it into the JVM.
  • All method calls on a proxy instance are routed to the invoke method of a single InvocationHandler, providing a centralized point for adding cross-cutting logic.
  • The factory for this mechanism is Proxy.newProxyInstance(), which requires a class loader, an array of interfaces, and an invocation handler.
  • JDK dynamic proxies are interface-based. They cannot proxy concrete classes that don't implement an interface.
  • For proxying classes, frameworks like Spring use CGLIB, which generates a subclass at runtime instead of implementing an interface.

In our next lesson, we will put this all together. We will leverage the full power of the Reflection API, including dynamic object creation and annotation processing, to implement a basic dependency injection container. This will serve as a capstone project for this module, demonstrating how these low-level JVM features are used to build sophisticated application frameworks.