JavaRush /Java Blog /Random EN /Dynamic Proxies in Java

Dynamic Proxies in Java

Published in the Random EN group
Hello! Today we will look at a rather important and interesting topic - creating dynamic proxy classes in Java. It's not too simple, so let's try to figure it out with examples :) So, the most important question: what are dynamic proxies and what are they for? A proxy class is a kind of “superstructure” over the original class, which allows us to change its behavior if necessary. What does it mean to change behavior and how does it work? Let's look at a simple example. Let's say we have an interface Personand a simple class Manthat implements this interface
public interface Person {

   public void introduce(String name);

   public void sayAge(int age);

   public void sayFrom(String city, String country);
}

public class Man implements Person {

   private String name;
   private int age;
   private String city;
   private String country;

   public Man(String name, int age, String city, String country) {
       this.name = name;
       this.age = age;
       this.city = city;
       this.country = country;
   }

   @Override
   public void introduce(String name) {

       System.out.println("Меня зовут " + this.name);
   }

   @Override
   public void sayAge(int age) {
       System.out.println("Мне " + this.age + " years");
   }

   @Override
   public void sayFrom(String city, String country) {

       System.out.println("Я из города " + this.city + ", " + this.country);
   }

   //..геттеры, сеттеры, и т.д.
}
Our class Manhas 3 methods: introduce yourself, say your age, and say where you're from. Let's imagine that we received this class as part of a ready-made JAR library and cannot simply take and rewrite its code. However, we need to change his behavior. For example, we don’t know which method will be called on our object, and therefore we want the person to first say “Hello!” when calling any of them. (nobody likes someone who is impolite). Dynamic proxies - 1What should we do in such a situation? We will need a few things:
  1. InvocationHandler

What it is? It can be literally translated as “call interceptor”. This describes its purpose quite accurately. InvocationHandleris a special interface that allows us to intercept any method calls to our object and add the additional behavior we need. We need to make our own interceptor - that is, create a class and implement this interface. It's pretty simple:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PersonInvocationHandler implements InvocationHandler {

private Person person;

public PersonInvocationHandler(Person person) {
   this.person = person;
}

 @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

       System.out.println("Hello!");
       return null;
   }
}
We need to implement only one interface method - invoke(). It, in fact, does what we need - it intercepts all method calls to our object and adds the necessary behavior (here we invoke()print “Hello!” to the console inside the method).
  1. The original object and its proxy.
Let's create an original object Manand a “superstructure” (proxy) for it:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       //Создаем оригинальный an object
       Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");

       //Получаем загрузчик класса у оригинального an object
       ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

       //Получаем все интерфейсы, которые реализует оригинальный an object
       Class[] interfaces = vasia.getClass().getInterfaces();

       //Создаем прокси нашего an object vasia
       Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

       //Вызываем у прокси an object один из методов нашего оригинального an object
       proxyVasia.introduce(vasia.getName());

   }
}
Doesn't look very simple! I specifically wrote a comment for each line of code: let’s take a closer look at what’s happening there.

In the first line we simply make the original object for which we will create a proxy. The following two lines may cause you confusion:
//Получаем загрузчик класса у оригинального an object
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
But there's really nothing special going on here :) To create a proxy, we need ClassLoaderthe (class loader) of the original object and a list of all the interfaces that our original class (i.e. Man) implements. If you don’t know what it is ClassLoader, you can read this article about loading classes into the JVM or this one on Habré , but don’t bother too much with it yet. Just remember that we are getting a little extra information that we will then need to create the proxy object. On the fourth line we use a special class Proxyand its static method newProxyInstance():
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
This method just creates our proxy object. To the method we pass the information about the original class that we received at the previous step (it ClassLoaderand the list of its interfaces), as well as the object of the interceptor we created earlier - InvocationHandler'a. The main thing is don’t forget to pass our original object to the interceptor vasia, otherwise it will have nothing to “intercept” :) What did we end up with? We now have a proxy object vasiaProxy. It can call any interface methodsPerson . Why? Because we passed it a list of all interfaces - here:
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Now he is "aware" of all interface methods Person. In addition, we passed our proxy an object PersonInvocationHandlerconfigured to work with the object vasia:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Now, if we call any interface method on the proxy object Person, our interceptor will “catch” this call and execute its own method instead invoke(). Let's try to run the method main()! Console output: Hello! Great! We see that instead of the real method, our Person.introduce()method is called : invoke()PersonInvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hello!");
   return null;
}
And the console displayed “Hello!” But this is not exactly the behavior that we wanted to get :/ According to our idea, “Hello!” should first be displayed, and then the method itself that we are calling should work. In other words, this method call:
proxyVasia.introduce(vasia.getName());
should output to the console “Hello! My name is Vasya,” and not just “Hello!” How can we achieve this? Nothing complicated: you just have to tinker a little with our interceptor and method invoke():) Pay attention to what arguments are passed to this method:
public Object invoke(Object proxy, Method method, Object[] args)
A method invoke()has access to the method it is called instead and all of its arguments (Method method, Object[] args). In other words, if we call a method proxyVasia.introduce(vasia.getName()), and instead of a method , introduce()a method is called invoke(), inside that method we have access to both the original method introduce()and its argument! As a result, we can do something like this:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PersonInvocationHandler implements InvocationHandler {

   private Person person;

   public PersonInvocationHandler(Person person) {

       this.person = person;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       System.out.println("Hello!");
       return method.invoke(person, args);
   }
}
Now we have added invoke()a call to the original method to the method. If we now try to run the code from our previous example:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       //Создаем оригинальный an object
       Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");

       //Получаем загрузчик класса у оригинального an object
       ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

       //Получаем все интерфейсы, которые реализует оригинальный an object
       Class[] interfaces = vasia.getClass().getInterfaces();

       //Создаем прокси нашего an object vasia
       Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

       //Вызываем у прокси an object один из методов нашего оригинального an object
       proxyVasia.introduce(vasia.getName());
   }
}
then we will see that now everything works as it should :) Console output: Hello! My name is Vasya. Where might you need it? In fact, many places. The “dynamic proxy” design pattern is actively used in popular technologies... and by the way, I forgot to tell you that Dynamic Proxyit is a pattern ! Congratulations, you've learned another one! :) Dynamic proxies - 2So, it is actively used in popular technologies and frameworks related to security. Imagine that you have 20 methods that can only be executed by logged-in users of your program. Using the techniques you've learned, you can easily add to these 20 methods a check to see if the user has entered a login and password, without duplicating the verification code separately in each method. Or, for example, if you want to create a log where all user actions will be recorded, this is also easy to do using a proxy. You can even now: just add code to the example so that the name of the method is displayed in the console when called invoke(), and you will get a simple log of our program :) At the end of the lecture, pay attention to one important limitation . Creating a proxy object occurs at the interface level, not the class level. A proxy is created for the interface. Take a look at this code:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Here we create a proxy specifically for the interface Person. If we try to create a proxy for the class, that is, we change the type of the link and try to cast to the class Man, nothing will work.
Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

proxyVasia.introduce(vasia.getName());
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to Man The presence of an interface is a mandatory requirement. Proxy works at the interface level. That's all for today :) As additional material on the topic of proxies, I can recommend you an excellent video and also a good article . Well, now it would be nice to solve a few problems! :) See you!
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION