08 Proxy Pattern and Common Implementations #
The dynamic proxy mechanism is widely used in Java, for example, Spring AOP, MyBatis, Hibernate, and other commonly used open-source frameworks all use dynamic proxy mechanism. Of course, Dubbo also uses dynamic proxy, and when we develop a simplified version of the RPC framework later, we will refer to the dynamic proxy mechanism used by Dubbo to shield the underlying network transmission and service discovery implementations.
In this lesson, we will start with the basics and first introduce the basic concept of the proxy pattern. We will then focus on the usage and underlying implementation of JDK dynamic proxy, and also explain some limitations of JDK dynamic proxy. Finally, we will introduce dynamic proxy based on bytecode generation.
Proxy Pattern #
The proxy pattern is one of the 23 object-oriented design patterns. Its class diagram is shown below:
In the diagram, Subject
is the business logic interface in the program, RealSubject
is the actual business class that implements the Subject
interface, and Proxy
is the proxy class that implements the Subject
interface and encapsulates a reference to a RealSubject
. We do not directly call methods of the RealSubject
object in the program, but instead use the Proxy
object to achieve the relevant functionality.
The implementation of the Proxy.operation()
method will invoke the operation()
method of the encapsulated RealSubject
object to execute the actual business logic. The role of the proxy is not only to complete the business logic as usual, but also to add some proxy logic before and after the business logic. In other words, the Proxy.operation()
method will perform some pre-processing and post-processing before and after the invocation of RealSubject.operation()
method. This is what we commonly refer to as the “proxy pattern”.
By using the proxy pattern, we can control the program’s access to the RealSubject
object. If any abnormal access is detected, we can directly limit or return, and we can also perform relevant pre-processing and post-processing before and after the execution of the business logic to help the upper-level caller shield the underlying details. For example, in an RPC framework, the proxy can complete a series of operations such as serialization, network I/O, load balancing, fault recovery, and service discovery, while the upper-level caller only perceives a local invocation.
The proxy pattern can also be used to implement lazy loading. We know that querying the database is a time-consuming operation, and sometimes the queried data is not actually used by the program. The lazy loading feature can effectively avoid this waste. When the system accesses the database, it can first obtain a proxy object, at which point no database query operation is performed, and naturally there is no actual data in the proxy object; when the system really needs to use the data, it can then call the proxy object to perform the database query and return the data. The principle of lazy loading in popular ORM frameworks, such as MyBatis and Hibernate, is roughly the same.
In addition, the proxy object can coordinate the relationship between the actual RealSubject
object and the caller, achieving a certain degree of decoupling.
JDK Dynamic Proxy #
The implementation of the proxy pattern mentioned above is also called the “static proxy pattern” because a Proxy
class has to be created for each RealSubject
class at compile time. When there are many classes to be proxied, a large number of Proxy
classes will appear.
In this scenario, we can use JDK dynamic proxy to solve this problem. The core of JDK dynamic proxy is the InvocationHandler
interface. Here is a demo implementation of InvocationHandler
:
public class DemoInvokerHandler implements InvocationHandler {
private Object target; // The actual business object, i.e., the RealSubject object
public DemoInvokerHandler(Object target) { // Constructor
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// ... Pre-processing before executing the business method ...
Object result = method.invoke(target, args);
// ... Post-processing after executing the business method ...
return result;
}
public Object getProxy() {
// Create the proxy object
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(), this);
}
}
Next, we can create a main()
method to simulate the upper-level caller, create and use dynamic proxy:
public class Main {
public static void main(String[] args) {
Subject subject = new RealSubject();
DemoInvokerHandler invokerHandler = new DemoInvokerHandler(subject);
Subject proxy = (Subject) invokerHandler.getProxy();
proxy.operation();
}
}
In the above code, we create a RealSubject
object as the actual business object, create a DemoInvokerHandler
object with the RealSubject
object as a parameter, and then call the getProxy()
method of the DemoInvokerHandler
to get the proxy object. Finally, we call the operation()
method of the proxy object, which will invoke the operation()
method of the RealSubject
object through the invoke()
method of the InvocationHandler
.
private Class<?> apply(ClassLoader loader,
Class<?>[] interfaces) {
synchronized (proxies) {
// Step 1: 检查接口数量是否超出限制
checkProxyClassLimit(interfaces.length);
// Step 2: 构造代理类的名称
String proxyPkg = null; // 包名,略
String proxyName = proxyPkg + proxyClassNamePrefix +
nextUniqueNumber.getAndIncrement();
// 创建代理类的完整类名,例如:com.sun.proxy.$Proxy1
String proxyClassName = proxyName;
// Step 3: 写入代理类的字节码文件
Class<?> cl = null;
try {
// 生成代理类的字节码内容
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyClassName, interfaces, accessFlags);
// 写入字节码文件
cl = defineClass0(loader, proxyClassName,
proxyClassFile, 0, proxyClassFile.length);
} catch (IOException e) {
// 异常处理,略
}
// Step 4: 加载代理类
resolveClass(cl);
return cl;
}
}
public Class apply(ClassLoader loader, Class[] interfaces) {
// ... A series of checks on the `interfaces` collection
// ... Select the package name of the proxy class (omitted)
// The name of the proxy class consists of the package name, the prefix of the proxy class name, and the number
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// Generate the proxy class and write it to a file
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
// Load the proxy class and return the Class object
return defineClass0(loader, proxyName, proxyClassFile, 0,
proxyClassFile.length);
}
The ProxyGenerator.generateProxyClass()
method generates the bytecode of the proxy class based on the specified name and interface collection, and decides whether to save it to the disk based on certain conditions. The code of this method is as follows:
public static byte[] generateProxyClass(final String name,
Class[] interfaces) {
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
// Generate the bytecode of the proxy class dynamically. The specific generation process is not discussed in detail here. Interested readers can continue to analyze.
final byte[] classFile = gen.generateClassFile();
// If `saveGeneratedFiles` is `true`, the bytecode of the generated proxy class will be saved to a file.
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Void run() {
// Omitted try/catch block
FileOutputStream file = new FileOutputStream(
dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
}
}
);
}
return classFile; //Return the bytecode of the generated proxy class
}
Finally, to see the actual definition of the dynamically generated proxy class by the JDK, we need to decompile the generated bytecode of the proxy class. The generated proxy class for RealSubject
in the example above is decompiled into the following code:
public final class $Proxy37
extends Proxy implements Subject { // Implementing the `Subject` interface
// Related methods and properties inherited from the `Object` class are omitted here
private static Method m3;
static {
// Omitted try/catch block
// Record the `Method` object corresponding to the `operation()` method
m3 = Class.forName("com.xxx.Subject")
.getMethod("operation", new Class[0]);
}
// The parameter of the constructor is the `DemoInvokerHandler` object we used in the example
public $Proxy11(InvocationHandler var1) throws {
super(var1);
}
public final void operation() throws {
// Omitted try/catch block
// Call the `invoke()` method of the `DemoInvokerHandler` object and ultimately call the corresponding method of the `RealSubject` object
super.h.invoke(this, m3, (Object[]) null);
}
}
To summarize, the basic usage and core principles of JDK dynamic proxy have been introduced. In summary, the implementation principle of JDK dynamic proxy is to dynamically create a proxy class and load it by the specified class loader. When creating a proxy object, the InvocationHandler
object is passed as a parameter to the constructor. When calling the proxy object, the InvocationHandler.invoke()
method is called, which executes the proxy logic and ultimately calls the corresponding method of the actual business object.
CGLib #
JDK dynamic proxy is natively supported by Java and does not require any external dependencies. However, as analyzed above, it can only be used for proxying based on interfaces, and it is not applicable for classes that do not implement any interfaces.
To proxy classes that do not implement any interfaces, CGLib can be used.
CGLib (Code Generation Library) is a bytecode generation library based on ASM. It allows us to modify and generate bytecode at runtime. CGLib uses bytecode technology to implement dynamic proxy functionality. The underlying principle is to generate a subclass for the target class through bytecode technology, and in this subclass, intercept all method calls of the parent class using method interception, thus achieving the purpose of proxy.
Since CGLib uses the approach of generating subclasses to implement dynamic proxy, it cannot proxy methods that are marked as final
(as final methods cannot be overridden). Therefore, CGLib and JDK dynamic proxy can complement each other: when the target class implements interfaces, JDK dynamic proxy can be used to create the proxy object; when the target class does not implement any interfaces, CGLib can be used to implement the dynamic proxy functionality. In many open-source frameworks such as Spring and MyBatis, combined usage of JDK dynamic proxy and CGLib can be seen.
CGLib has two important components in its implementation.
- Enhancer: It specifies the target object to be proxied and the object that actually handles the proxy logic. The proxy object is obtained by invoking the
create()
method, and calls to all non-final
methods of this object will be forwarded to theMethodInterceptor
for processing. - MethodInterceptor: All method calls to the dynamic proxy object are intercepted and processed in the
intercept
method.
The usage of these two components is similar to that of Proxy
and InvocationHandler
in JDK dynamic proxy.
Let’s explore the usage of CGLib through an example. When creating a dynamic proxy class with CGLib, we need to define an implementation of the Callback
interface. CGLib also provides several sub-interfaces of the Callback
interface, as shown in the following diagram:
Taking the MethodInterceptor
interface as an example, first we need to include the Maven dependency for CGLib:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
Here is the specific code for the CglibProxy
class, which implements the MethodInterceptor
interface:
public class CglibProxy implements MethodInterceptor {
// Initialize the Enhancer object
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz); // Specify the superclass of the generated proxy class
enhancer.setCallback(this); // Set the Callback object
return enhancer.create(); // Dynamically create a subclass instance through ASM bytecode technology
}
// Implement the `intercept()` method of the MethodInterceptor interface
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("Preprocessing");
Object result = proxy.invokeSuper(obj, args); // Call the method in the parent class
System.out.println("Postprocessing");
return result;
}
}
Next, we write another target class to be proxied and a main
method for testing, as follows:
public class CGLibTest { // Target class
public String method(String str) { // Target method
System.out.println(str);
return "CGLibTest.method():" + str;
}
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
// Generate a proxy object of CBLibTest
CGLibTest proxyImp = (CGLibTest)
proxy.getProxy(CGLibTest.class);
// Call the `method()` method of the proxy object
String result = proxyImp.method("test");
System.out.println(result);
// ----------------
// The output is as follows:
// Preprocessing
// test
// Postprocessing
// CGLibTest.method():test
}
}
That concludes the introduction to the basics of using CGLib. We will continue to introduce related CGLib content when discussing Dubbo source code in the future.
Javassist #
Javassist is an open-source library for generating Java bytecodes. Its main advantages are simplicity and speed. It allows dynamic modification of class structures or dynamic generation of classes by directly using the Java APIs provided by Javassist.
Using Javassist is relatively simple. First, let’s see how to dynamically create classes using the Java API provided by Javassist. The example code is as follows:
public class JavassistMain {
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault(); // Create a ClassPool
// The name of the class to be generated is com.test.JavassistDemo
CtClass clazz = cp.makeClass("com.test.JavassistDemo");
StringBuffer body = null;
// Create a field, specifying the field type, field name, and the class to which the field belongs
CtField field = new CtField(cp.get("java.lang.String"),
"prop", clazz);
// Specify private modifier for this field
field.setModifiers(Modifier.PRIVATE);
// Set the getter/setter methods of the `prop` field
clazz.addMethod(CtNewMethod.setter("getProp", field));
clazz.addMethod(CtNewMethod.getter("setProp", field));
// Set the initial value of the `prop` field and add the field to clazz
clazz.addField(field, CtField.Initializer.constant("MyName"));
// Create a constructor, specifying the parameter types and the class to which the constructor belongs
CtConstructor ctConstructor = new CtConstructor(
new CtClass[]{}, clazz);
// Set the method body
body = new StringBuffer();
body.append("{\n prop=\"MyName\";\n}");
ctConstructor.setBody(body.toString());
clazz.addConstructor(ctConstructor); // Add the constructor to clazz
// Create the `execute()` method, specifying the return type, method name, method parameter list, and
// the class to which the method belongs
CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute",
new CtClass[]{}, clazz);
// Specify the public modifier for this method
ctMethod.setModifiers(Modifier.PUBLIC);
// Set the method body
body = new StringBuffer();
body.append("{\n System.out.println(\"execute():\" " +
"+ this.prop);");
body.append("\n}");
ctMethod.setBody(body.toString());
clazz.addMethod(ctMethod); // Add the `execute()` method to clazz
// Save the JavassistDemo class defined above to the specified directory
clazz.writeFile("/Users/xxx/");
// Load the clazz class and create an object
Class<?> c = clazz.toClass();
Object o = c.newInstance();
// Call the execute() method
Method method = o.getClass().getMethod("execute",
new Class[]{});
method.invoke(o, new Object[]{});
}
}
After executing the above code, you can find the generated JavassistDemo.class
file in the specified directory. Decompile it to get the code for JavassistDemo
as follows:
public class JavassistDemo {
private String prop = "MyName";
public JavassistDemo() {
prop = "MyName";
}
public void setProp(String paramString) {
this.prop = paramString;
}
public String getProp() {
return this.prop;
}
public void execute() {
System.out.println("execute():" + this.prop);
}
}
Javassist can also be used to implement dynamic proxy functionality. The underlying principle is also based on creating a subclass of the target class. Here, we use Javassist to create a proxy object for the generated JavassitDemo
. The specific implementation is as follows:
public class JavassitMain2 {
public static void main(String[] args) throws Exception {
ProxyFactory factory = new ProxyFactory();
// Specify the superclass. ProxyFactory will dynamically generate a subclass that inherits from this superclass
factory.setSuperclass(JavassistDemo.class);
// Set the filter to determine which method calls need to be intercepted
factory.setFilter(new MethodFilter() {
public boolean isHandled(Method m) {
if (m.getName().equals("execute")) {
return true;
}
return false;
}
});
// Set the interception handling
factory.setHandler(new MethodHandler() {
@Override
public Object invoke(Object self, Method thisMethod,
Method proceed, Object[] args) throws Throwable {
System.out.println("Preprocessing");
Object result = proceed.invoke(self, args);
System.out.println("Execution result:" + result);
System.out.println("Postprocessing");
return result;
}
});
// Create a proxy class for JavassistDemo and create a proxy object
Class<?> c = factory.createClass();
JavassistDemo JavassistDemo = (JavassistDemo) c.newInstance();
JavassistDemo.execute(); // The execute() method will be intercepted
System.out.println(JavassistDemo.getProp());
}
}
That concludes the introduction to the basics of Javassist. Javassist allows the generation of classes directly from Java language strings, making it quite useful. Javassist also has good performance and is the default proxy generation method in Dubbo.
Summary #
In this lesson, we first introduced the core concepts and uses of the proxy pattern, giving you a preliminary understanding of the proxy pattern. Then we introduced the use of JDK dynamic proxy and deeply analyzed the implementation principle of JDK dynamic proxy and its limitations. Finally, we introduced the basic usage of CGLib and Javassist, two code generation tools, and briefly described the principles of generating proxies using these two tools.
Do you know any other ways to implement dynamic proxy? Feel free to discuss in the comments.