Java Dynamic proxy mechanism and how Spring is using it

Spac Valentin
5 min readJan 14, 2020

--

In this post, we will talk about Proxy pattern and how you can implement it using the JDK-provided-mechanism, Dynamic Proxies. It is one of the ways Spring Framework is handling cross-cutting concerns, like transactions (@Transactional), caches (@Cacheable) and so on.

1. Introduction

Proxy pattern, as described in Gang of Four, “provides a placeholder interface to an underlying object to control access, reduce cost or reduce complexity”. In simpler terms, it acts as a surrogate.

There are multiple ways of creating proxies:

  1. Using plain java objects, if you know the interface beforehand (a good example can be found here). These are static proxies because you know at compile time the types of all involved objects.
  2. Using a byte-code generation/manipulation library (e.g. cglib, javassist). Used primarily when the target (class to be proxied) does not implement any interface. One of its limitations is that the proxy target must not be a final class, as this approach uses inheritance to generate the proxy.
  3. Using JDK Dynamic Proxies which creates implementations of Java interfaces at runtime using Reflection.

The latter two are dynamic proxies because they work without knowing at compile time the types to be proxied but they leverage reflection to determine it at runtime.

The following sections will explain how to implement a proxy using JDK Dynamic proxy mechanism. Full code can be found on Github.

2. Create a dynamic proxy

Let’s assume that we want to implement an app that plays audio files, records how many times each file was played and stores this info in a database. We will create a proxy that takes care of the transactional behaviour we need in the interaction with the database.

2.1 Define the interface

First, we will define the interfaces we need for building our app. Remember that JDK dynamic proxies mechanism creates implementations based on interfaces so these are a must.

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/service/Playable.java

Nothing out of the ordinary here, just a plain Java interface. An audio file can be played from the beginning, from a specific point, or only a portion of the file. When playing, you can also jump to a specified location (seek audio file). Let’s create an interface for that, too.

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/service/Seekable.java

Now that we have our interfaces ready, we will start creating the service which will contain our business logic.

2.2 Creating the service

For this example, we will create a simple service whose methods will return just the description of the invoked action.

To be more Spring-like, let’s create some annotations which will help to add transactional behaviour.

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/framework/MyCustomTransaction.java

Nothing new here, just a simple annotation, which we will apply to our methods. It’s a simpler version of Spring’s @Transactional. We’ll also create a @TransactionalService annotation (similar to Spring's@Service).

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/framework/TransactionalService.java

Applying the above annotations to our service, we end up with:

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/service/PlayerService.java

2.3 Creating a proxy

To create a proxy is easy, you just invoke the newProxyInstance static method of java.lang.reflect.Proxy class with 3 arguments:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

For example:

// object to be proxied
PlayerService service = new PlayerService()
// Create the proxy
Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(),
service.getClass().getInterfaces(),
new MyInvocationHandler(service));

The InvocationHandler#invoke method will be executed when any of the methods defined in the interfaces returned by service.getClass().getInterfaces() will be invoked. Below is a simple example of a handler which prints the execution time of each method.

2.4 Creating a proxy factory

For a better display of how is Spring leveraging this mechanism, we will create a proxy factory which, using reflection, will scan a specified package for classes annotated with @TransactionalService. For those classes, we are going to create proxies and keep them in a bean registry.

For scanning the classpath, we are going to employ an easy to use library called Reflections:

Reflections reflections = new Reflections(packageToLookup);        Set<Class<?>> transactionalServiceClasses = reflections.getTypesAnnotatedWith(TransactionalService.class);

We need an instance of each of those classes to be our proxy targets and we are going to use the no-arg constructor to do create them:

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/framework/ProxyFactory.java

Now that we have our proxy targets, we can create the actual proxies:

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/framework/ProxyFactory.java

MyTransactionInvocationHandler implements InvocationHandler and takes care of opening and closing transactions for the annotated methods. To do so, we must check if the invoked method is annotated with @MyCustomTransaction. The tricky part is that the method passed to InvocationHandler#invoke is the one defined in the interface, but the annotation is on the actual class that implements it.

We need to get the actual method from our target:

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/MyTransactionInvocationHandler.java

Now, if the returned method is annotated with @MyCustomTransaction, we can trigger the custom transactional logic. Otherwise, we just invoke the same method on the target (pass-through). The handler looks like this

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/MyTransactionInvocationHandler.java

Going back to our createProxy method from ProxyFactory (this one), we notice that we are returning an instance of MyCustomProxy . This is just a container-class which includes the proxy itself (returned by Proxy#newProxyInstance) and the interfaces this proxy can handle.

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/framework/MyCustomProxy.java

This container-class helps us to return the correct proxy for a specified interface (in a type-safe manner).

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/framework/ProxyFactory.java

To recap, our ProxyFactory will scan the classpath for a specific package, get all classes annotated with @TransactionalService , instantiate them using no-arg constructor and create a proxy for each one. These proxies will be stored in a bean registry (List<MyCustomProxy> beanRegistry) and using getBean(Class<T> clazz) , we will get the proxy for a specified type.

https://github.com/spac-valentin/jdk-dynamic-proxy/blob/master/src/main/java/dev/valentinspac/proxy/framework/ProxyFactory.java

To see how it works, we are going to create amain method and invoke the methods defined in our interfaces:

In the output we can see that for the methods annotated with @MyCustomTransaction, our handler gets invoked and we have transactional behaviour (opening and committing transaction). For seekTo there is no transactional behaviour as it is not annotated.

business.play(file.getPath())
Opening transaction [default MyTransaction] with params [SongPath]
Committing transaction default MyTransaction...
Playing song SongPath
business.play(file)
Opening transaction [file] with params [SongPath]
Committing transaction file...
Playing song SongPath
business.play(file, 10)
Opening transaction [starting at] with params [SongPath, 10]
Committing transaction starting at...
Playing song SongPath starting at 10
business.play(file, 10, 15)
Opening transaction [interval] with params [SongPath, 10, 15]
Committing transaction interval...
Playing song SongPath starting at 10 till 15
seekablePlayer.seekTo(20)
Seek to 20

3. Conclusion

In this post, we discussed a bit about Proxy Pattern, ways to create a proxy in Java and showed an example of how JDK Dynamic Proxy works and how it is used in Spring Framework. It is a very powerful way to handle cross-cutting concerns. Full code can be found on Github.

--

--