"Hi, Amigo."

"Hello, Rishi."

"Today I will explain a new and very interesting topic to you: dynamic proxies".

"Java has several ways to change the functionality of a particular class…"

"The first method is inheritance."

"The easiest way to change a class's behavior is to create a new class that inherits the original (base) class, and override its methods. Then, instead of using the original class, you use the derived class. For example:"

Reader reader = new UserCustomReader();

"The second method is to use a wrapper class."

"BufferedReader is an example of this type of class. First, it inherits Reader. In other words, it can be used instead of Reader. Second, it redirects all calls to the original Reader object, which must be passed to the BufferedReader object's constructor. For example:"

Reader readerOriginal = new UserCustomReader();
Reader reader = new BufferedReader(readerOriginal);

"The third method is to create a dynamic proxy (Proxy)."

"There is a special class in Java (java.lang.reflect.Proxy) that actually lets you construct an object during program execution (dynamically), without creating a separate class for it."

"This is very easy to do:"

Reader reader = (Reader)Proxy.newProxyInstance();

"That's already something new to me!"

"But of course, we don't need an object with no methods. We need the object to have methods, and we need them to do what we want. Java uses a special interface for this called InvocationHandler, which can intercept all method calls associated with the proxy object. A proxy object can only be created using interfaces."

"Invoke – is the standard name for a method or class whose primary task is to simply call some method."

"Handler – is the standard name for a class that handles some event. For example, a class that handles mouse clicks would be called MouseClickHandler, etc."

"The InvocationHandler interface has a single invoke method, to which all calls to the proxy object are directed. For example:"

Code
Reader reader = (Reader)Proxy.newProxyInstance(new CustomInvocationHandler());
reader.close();
class CustomInvocationHandler implements InvocationHandler
{
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
 {
  System.out.println("yes!");
  return null;
 }
}

"When calling the reader.close() method, the invoke method will be called, and the screen will display 'yes!'"

"So, we declared a CustomInvocationHandler, class, and implemented the InvocationHandler interface and its invoke method. When the invoke method is called, it displays 'yes!'. Then we created a CustomInvocationHandler object, and passed it to the newProxyInstance method when creating a proxy object."

"Yes, that's all correct."

"This is a very powerful tool. Usually, these proxies are created to simulate objects in programs that are physically running on another computer. Or to control access."

"You can check the current user's permissions, handle errors, log errors, and much more in this method."

"Here's an example where the invoke method also calls the original object's methods:"

Code
Reader original = new UserCustomReader();

Reader reader = (Reader)Proxy.newProxyInstance(new CustomInvocationHandler(original));
reader.close();
class CustomInvocationHandler implements InvocationHandler
{
 private Reader readerOriginal;

 CustomInvocationHandler(Reader readerOriginal)
 {
  this.readerOriginal = readerOriginal;
 }

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
 {
  if (method.getName().equals("close"))
  {
   System.out.println("Reader closed!");
  }

  // This calls the readerOriginal object's close method.
  // The method's name and a description of its parameters are stored in the method variable.
  return method.invoke(readerOriginal, args);
 }
}

"This example has two special features."

"First, the «original» Reader object is passed to the constructor, and a reference to it is saved inside the CustomInvocationHandler."

"Second, we call this same method again in the invoke method, but on the «original» object this time."

"Ah. In other words, this last line calls the same method, but on the original object:"

return method.invoke(readerOriginal, args);

"Yep."

"I wouldn't say that it's super obvious, but it's still understandable. Or so it seems."

"Great. Then one more thing. In the newProxyInstance method, you need to pass a little more housekeeping information to create a proxy object. But since we aren't creating monstrous proxy objects, this information is easily obtained from the original class itself."

"Here's another example:"

Code
Reader original = new UserCustomReader();

ClassLoader classLoader = original.getClass().getClassLoader();
Class<?>[] interfaces = original.getClass().getInterfaces();
CustomInvocationHandler invocationHandler = new CustomInvocationHandler(original);

Reader reader = (Reader)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
class CustomInvocationHandler implements InvocationHandler
{
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
 {
  return null;
 }
}

"Ah. ClassLoader and list of interfaces. This is something from Reflection, isn't it?"

"Yep."

"I see. Well, I think I can create a primitive, super simple proxy object if I ever need one."

"Then go check in with Diego."