Dynamic Java Proxy: What is it and how to use it?

Original author: Peter Verhas
  • Transfer
Hello!

Well, before the New Year and the start of the tenth Java Developer Stream, it ’s just a bit of a joke. So we have one open lesson left, which we are preparing for publication and today's article, from which you will learn about Java dynamic proxy: what it is, when and how to use it in code.

What is a proxy?

A proxy is a design pattern. We create and use it to add and modify the functionality of existing classes. In this case, the proxy object is used instead of the original one. Usually it uses the same method as the original, and in Java, proxy classes extend the source. A proxy can call the method of the original object, as it has a handle to the original.

Thus, proxy classes conveniently implement many things:

  • start and stop logging;
  • additional argument checking;
  • imitation of the behavior of the original class;
  • implementation of deferred initialization of costly resources;



All this happens without changing the original class code. The full list is not limited to the examples above, they are only a small part of it.

In practice, the proxy class does not directly implement the functionality. Following the principle of sole responsibility, the proxy class directly performs only proxying, and the behavior change is implemented in handlers. When calling a proxy object instead of the original one, the proxy itself decides whether to call the original method or some handlers. The handler can perform both his own task and refer to the original method.

Although the proxy pattern is not only used to create a proxy object and class in a runtime environment, in Java it is a particularly interesting topic. In this article, I focus on such proxies.

This is a complex topic that requires the use of a reflection class, or manipulation of bytecode, or compilation of Java code generated dynamically. And maybe all at once. To prevent the new class from being available as a bytecode at runtime, you will need the generated bytecode and class loader to load the bytecode. To create bytecode, use cglib , bytebuddy or the built-in Java compiler.

The importance of separation of responsibilities, in our case, becomes clear, we need only think about the proxy classes and the handlers they call. The proxy class is generated at runtime, but the handlers it calls can be added to regular source code and compiled with the rest of the program.

How to use it in our code?

The simplest is to use java.lang.reflect.Proxythat is part of the JDK. This class can create a proxy class or directly its instance. Using a proxy embedded in Java is very simple. All you need to do is implement java.lang.InvocationHandlerit so that the proxy object can call it. The interface InvocationHandler is very simple and contains only one method: invoke(). When called, the arguments contain the proxied original object, the method invoked (as a reflection of the object Method), and an array of objects of the original arguments. The code snippet below demonstrates the use of:

package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
publicclassJdkProxyDemo{
    interfaceIf{
        voidoriginalMethod(String s);
    }
    staticclassOriginalimplementsIf{
        publicvoidoriginalMethod(String s){
            System.out.println(s);
        }
    }
    staticclassHandlerimplementsInvocationHandler{
        privatefinal If original;
        publicHandler(If original){
            this.original = original;
        }
        public Object invoke(Object proxy, Method method, Object[] args)throws IllegalAccessException, IllegalArgumentException,
                InvocationTargetException {
            System.out.println("BEFORE");
            method.invoke(original, args);
            System.out.println("AFTER");
            returnnull;
        }
    }
    publicstaticvoidmain(String[] args){
        Original original = new Original();
        Handler handler = new Handler(original);
        If f = (If) Proxy.newProxyInstance(If.class.getClassLoader(),
                new Class[] { If.class },
                handler);
        f.originalMethod("Hallo");
    }
}

To call the original method of the original object, the handler needs access to it. What is not provided by the Java proxy implementation. You will need to pass the argument to the instance instance in the code yourself. (Note the object (usually called proxy) that is passed as an argument to the handler being called. This is a proxy object that generates a reflection of Java dynamically, and not the object that we want to proxy.) So you can use as separate handler objects for each source class, and a common object that knows how to call the original object, if there is any method for this at all.

In a special case, you can create a call handler and proxy interface without the original object. Moreover, the class for implementing the interface in the source code is not required. It is implemented by a dynamically created proxy class.

If the proxied class does not implement the interface, you should consider using some other proxy implementation.

THE END

We are waiting for your comments and questions. As always, or here, or you can go to Vitaly on the open day .

Also popular now: