This article introduces the use of dynamic proxy technology in Java, starting with the concept of proxy, and further explores how it is implemented. Due to my limited level, it is inevitable that there will be unclear or inaccurate places in the narrative, I hope you can correct them, thank you 🙂
An overview of the
What is agency
We all know that wechat business agent, simply speaking is to replace the manufacturer to sell goods, manufacturers “commission” agent for its sales of goods. About wechat business agent, first of all, when we buy things from them, we usually do not know who is behind the manufacturer, that is to say, “consignor” is invisible to us; Secondly, wechat business agent mainly to the circle of friends as the target customers, which is equivalent to the manufacturer to do a “filter” on the customer group. We further abstract the wechat business agent and the manufacturer, the former can be abstracted into the agent class, the latter can be abstracted into the delegate class (agent class). Through the use of agents, there are usually two advantages, and can be respectively corresponding to the two characteristics of wechat business agents we mentioned:
- Advantage 1: you can hide the implementation of the delegate class;
- Advantage two: Decoupling the client from the delegate class, allowing you to do some extra processing without modifying the delegate code.
Static agent
If the proxy class exists before the program runs, it is called a static proxy. In this case, the proxy class is usually defined in Java code. Typically, the proxy and delegate classes in a static proxy implement the same interface or derive from the same parent class. Vendor class represents the manufacturer and BusinessAgent class represents the wechat BusinessAgent to introduce the simple implementation of static agent. Both the delegate class and the agent class realize the Sell interface. The definition of the Sell interface is as follows:
public interface Sell { void sell(); void ad(); }Copy the code
The Vendor class is defined as follows:
public class Vendor implements Sell {
public void sell() {
System.out.println("In sell method");
}
public void ad() {
System.out.println("ad method")
}
}Copy the code
The BusinessAgent class is defined as follows:
public class BusinessAgent implements Sell { private Vendor mVendor; public BusinessAgent(Vendor vendor) { mVendor = vendor; } public void sell() { mVendor.sell(); } public void ad() { mVendor.ad(); }}Copy the code
As you can see from the definition of the BusinessAgent class, static proxies can be implemented through aggregation, with the proxy class holding a reference to the delegate class. Let’s consider this requirement: add a filter to the Vendor class to only sell to college students. With static agents, we don’t need to modify the code of the Vendor class to do this, just add a judgment to the sell method in the BusinessAgent class as follows:
public class BusinessAgent implements Sell { ... public void sell() { if (isCollegeStudent()) { vendor.sell(); }}... }Copy the code
This corresponds to the second advantage of using a proxy we mentioned above: decoupling the client from the delegate class, allowing you to do some extra processing without modifying the delegate class code. The limitation of static proxy is that the proxy class must be written before running. Here we focus on the dynamic proxy way of generating proxy class at run time.
A dynamic proxy
What is dynamic proxy
The way proxy classes create proxies while the program is running is called dynamic proxies. That is, in this case, the proxy class is not defined in Java code, but is dynamically generated at run time according to our “instructions” in Java code. The advantage of dynamic proxy over static proxy is that it is easy to uniformly handle the functions of the proxy class without modifying the functions of each proxy class. Let’s use an example to illustrate the advantages of dynamic proxies. Now, suppose we want to implement a requirement that prints “before” before executing a method in a delegate class and “after” after. We will introduce the Vendor class as the delegate class and the BusinessAgent class as the agent class in the above example. First let’s use static proxy to implement this requirement. The code is as follows:
public class BusinessAgent implements Sell { private Vendor mVendor; public BusinessAgent(Vendor vendor) { this.mVendor = vendor; } public void sell() { System.out.println("before"); mVendor.sell(); System.out.println("after"); } public void ad() { System.out.println("before"); mVendor.ad(); System.out.println("after"); }}Copy the code
As we can see from the code above, implementing our requirements through static proxies requires us to add logic to each method. There are only two methods, so it is not too much work. What if the Sell interface contains hundreds of methods? There is a lot of redundant code to write with static proxies. By using dynamic proxies, we can make a “unified directive” so that methods of all proxy classes are treated uniformly, rather than changing each method individually. Let’s take a look at how to implement our requirements using dynamic proxies.
Using dynamic Proxies
InvocationHandler interface
When using dynamic proxies, we need to define a mediation class that sits between the proxy class and the delegate class. This mediation class is required to implement the InvocationHandler interface, which is defined as follows:
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}Copy the code
As we know from the name InvocationHandler, the mediation class that implements this interface is used as the “call handler.” When we call a method on a proxy object, this “call” is passed to the Invoke method. The proxy object is passed in as a proxy parameter. The method parameter identifies which method of the proxy class we are calling, and args is the method parameter. In this way, all of our calls to methods in the proxy class become calls to Invoke, which allows us to add uniform processing logic to the Invoke method (or to do different processing for different proxy class methods based on the Method argument). So we simply print “before” in the invoke method implementation of the mediation class, then call the invoke method of the delegate class, and then print “after”. Let’s implement it step by step.
Definition of a delegate class
In dynamic proxy mode, the delegate class is required to implement a certain interface. Here we implement the Sell interface. The Vendor class is defined as follows:
public class Vendor implements Sell {
public void sell() {
System.out.println("In sell method");
}
public void ad() {
System.out.println("ad method")
}
}Copy the code
Intermediate class
As mentioned above, the mediation class must implement the InvocationHandler interface to act as the calling handler to “intercept” calls to the proxy class methods. A mediation class is defined as follows:
public class DynamicProxy implements InvocationHandler { private Object obj; Public DynamicProxy(Object obj) {this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before"); Object result = method.invoke(obj, args); System.out.println("after"); return result; }}Copy the code
As you can see from the above code, the mediation class holds a reference to a delegate object and invokes the corresponding method of the delegate object in the Invoke method. Does this sound familiar? A reference to a delegate object is held in an aggregate manner, and external calls to Invoke are eventually converted to calls to a delegate object. Isn’t this an implementation of the static proxy we introduced above? In fact, the mediation class and the delegate class constitute a static proxy relationship. In this relationship, the mediation class is the proxy class, and the delegate class is the delegate class. The proxy and mediation classes also form a static proxy relationship, in which the mediation class is the delegate class and the proxy class is the proxy class. In other words, dynamic proxy relationships consist of two sets of static proxy relationships, which is the principle of dynamic proxy. Let’s take a look at how to “indicate” to dynamically generate proxy classes.
Dynamically generate proxy classes
The code for dynamically generating proxy classes is as follows:
public class Main { public static void main(String[] args) { DynamicProxy inter = new DynamicProxy(new Vendor()); // Add this line to produce a $proxy0.class file, This file is dynamically generated proxy class file System. The getProperties (), put (" sun. Misc. ProxyGenerator. SaveGeneratedFiles ", "true"); / / get the Proxy class instance sell sell sell = (sell) (Proxy. NewProxyInstance (sell. Class. GetClassLoader (), new class [] {sell. Class}, Intel)); sell.sell(); sell.ad(); }}Copy the code
In the above code, we call the newProxyInstance method of the Proxy class to get an instance of the Proxy class. This proxy class implements the interface we specify and distributes method calls to the specified invocation handler. This method is declared as follows:
public static Object newProxyInstance(ClassLoader loader,
Class[] interfaces, InvocationHandler h) throws IllegalArgumentExceptionCopy the code
The meanings of the three parameters of the method are as follows:
- Loader: a ClassLoder that defines the proxy class;
- Interfaces: List of interfaces implemented by the proxy class
-
H: The calling handler, which is the instance of the class we defined above that implements the InvocationHandler interface
Let’s run it and see if our dynamic proxy works. The output from my run here is:
It shows that our dynamic proxy really works.
We have briefly mentioned the principle of dynamic proxy, here is a brief summary: The newProxyInstance method is first used to obtain the instance of the proxy class, and then we can call the method of the proxy class through this instance. All calls to the method of the proxy class will actually call the invoke method of the mediation class (calling handler), from which we call the corresponding method of the delegate class. And you can add your own processing logic. Let’s take a look at the code for the generated proxy class.
Source code analysis of dynamic proxy classes
By running Main, we get a class file named “$Proxy”. This file is the dynamically generated Proxy class. Let’s decompile the source code of this Proxy class:
package com.sun.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; Public final class $Proxy0 extends Proxy implements Sell {// These methods represent the equals(), toString(), AD (), Sell (), hashCode() methods private static Method m1; private static Method m2; private static Method m4; private static Method m3; private static Method m0; // The constructor takes an InvocationHandler object as an argument, $Proxy0(InvocationHandler VAR1) throws {super (var1); $Proxy0(InvocationHandler var1) throws {super (var1); } // The call to equals is actually converted to the call to super.h.invoke. The h in the parent class is the InvocationHandler object we passed in the constructor, Public final Boolean equals(Object var1) throws {try {return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void ad() throws { try { super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void sell() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue(); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); }} static {try {m1 = class.forname (" java.lang.object ").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m4 = Class.forName("Sell").getMethod("ad", new Class[0]); m3 = Class.forName("Sell").getMethod("sell", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); }}}Copy the code
As you can see, the logic of the above code is very simple, and we explain it in the comments. (The above code involves the use of reflection, for reflection is not very familiar with the small friends can refer to here: 10 minutes to understand Java reflection)
Now, we have understood the use of dynamic proxy and its implementation principle, further we can understand the generation process of dynamic proxy class, just need to read the source code of newProxyInstance method, the logic of this method is not complicated, so we will not expand here. Interested partners can follow it themselves, or you can refer to this article: Dynamic Proxy for common technology points
The resources
- Java Docs
- In-depth Understanding of the Java Virtual Machine
- Dynamic proxy for common technology points
Long press or scan the QR code to follow us, so that you can learn how to write a good app while waiting for the subway every day.