Java proxy objects for Interceptor implementation.
23.02.2016
Maybe you heard something about design patterns before. Design patterns could be useful for your developer life as they shows you some good practices for various situations, which can occur during software development. In our post we will implement Interceptor pattern.
In Java there are some interesting language / API constructs since version 1.5. In our current article, we will be interested especially in annotations and proxy objects. If you work with CDI, you probably know the concept of interceptors there. We will implement it similar way, but without need of CDI. It can be useful for some older JEE projects or other Java projects, where CDI can't be used.
Ok, let's start with some motivation why we should thing about it. Imagine you have a service class, which contains the following method:
public void output(String msg) { System.out.println(msg); // Actual action code. }
This method takes one String argument and print it out. Everything should work ok. But now what if there is a need to perform this action transactionally? So basically we need to perform some action at the beginning and at the end of the method. We have something like this:
public void output(String msg) { System.out.println("--Start Transaction"); // Code for transaction start. System.out.println(msg); // Actual action code. System.out.println("--End Transaction"); // Code for transaction end. }
It is working well now, but it is not ideal. As you can see these actions for transaction
are unrelated to the business logic of the method (business logic is to print out String). But we
need them. Even worse - if we have let's say hundreds of methods in our project containing
transactional behavior done this way and want to change for example
"--Start Transaction"
to "--Begin Transaction"
, you are in trouble. Of course,
you can create methods like beginTransaction()
, endTransaction()
and write
something like this:
public void output(String msg) { beginTransaction(); // Code for transaction start. System.out.println(msg); // Actual action code. endTransaction(); // Code for transaction end. }
But still you have code unrelated to the business logic mixed in. There must be something better...
Proxy objects to the rescue.
Our goal is to have transactional behavior defined somehow declarative. For this declarative approach, annotations help us a lot. The result will look like:
@Transaction public void output(String msg) { System.out.println(msg); // Actual action code. }
Maybe it is good idea to explain, what the proxy object is. Basically it is an object, which mimics class and properties of given regular object. Additionally proxy object allow us to control method execution. We are then able to intercept actual execution and we can perform own actions during execution.
At first, we define the annotation @Transaction
. It will help us to recognize,
which method should be transactional-aware. Methods, which are not annotated will be processed
in normal way.
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Transaction { }
Now we must implement some code, which will do an actual method calling interception. In terms of Java proxy object, it is called invocation handler and this code is called for every single method invocation of defined proxy object.
public class MyInvocationHandler<T> implements InvocationHandler { private final T proxied; public MyInvocationHandler(T proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result; Method m = proxied.getClass().getMethod(method.getName(), method.getParameterTypes()); if (m.isAnnotationPresent(Transaction.class)) { System.out.println("--Start Transaction"); result = method.invoke(proxied, args); System.out.println("--End Transaction"); } else { result = method.invoke(proxied, args); } return result; } }
Let me explain the code a little. As you can see our class is generic, which helps us to
implement it universally without dealing with potentially dangerous type casting. Class must
implement InvocationHandler
interface. Method Object invoke(Object proxy,
Method method, Object[] args)
must be overridden and it contains all necessary code
for interceptor. On if (m.isAnnotationPresent(Transaction.class))
we are
testing, if our annotation is present and if so we perform start transaction, method invocation
and end transaction. Code result = method.invoke(proxied, args);
is actual method
invocation, result of it is stored in the result
variable. If there is no annotation,
we only perform method invocation itself. That's it. Now we only need the way, how the proxy object
with our invocation handler could be instantiated.
Instead of standard instantiation like Object o = new Object();
, we must create proxy
object based on regular object's instance.
MyInvocationHandlerinvocationHandler = new MyInvocationHandler (new ServiceImpl()); Service service = (Service) Proxy.newProxyInstance(ServiceImpl.class.getClassLoader(), new Class[]{Service.class}, invocationHandler);
And now we have in variable service
proxy object, which behaves the same way as
Service
object instance itself. Only difference is, that we can control method
invocations thru the invocation handler.
You can download all source code for this article on Github. It can help you understand the whole concept. It is fully working example available as Maven project. Feel free to modify and reuse the code as you want.