Java Proxy Pattern Explained
1. Proxy Pattern
The proxy pattern is a relatively well understood design pattern. Simply put we use a proxy object to replace access to the real object (real object), so that we can provide additional functional operations to extend the functionality of the target object without modifying the original target object.
The main purpose of the proxy pattern is to extend the functionality of the target object, for example, you can add some custom operations before and after the execution of a method of the target object.
There are two implementations of the proxy pattern: static proxy and dynamic proxy, so let’s look at the static proxy implementation first.
2. Static Proxy
In static proxy, we enhance each method of the target object is done manually ( later will demonstrate the specific code ), very inflexible ( for example, once a new method is added to the interface, both the target object and the proxy object have to be modified ) and troublesome ( need to write a separate proxy class for each target class ). Practical application scenarios are very, very rare, and everyday development hardly sees scenarios where static proxies are used.
From the JVM level, static proxies turn interfaces, implementation classes, and proxy classes into actual class files at compile time.
Static proxy implementation steps:
- define an interface and its implementation class.
- create a proxy class that also implements the interface
- inject the target object into the proxy class, and then call the corresponding method in the target class in the corresponding method of the proxy class. In this way, we can block access to the target object through the proxy class and do what we want to do before and after the execution of the target method.
The following shows by code!
1. Define the interface for sending SMS messages
public interface SmsService {
String send(String message);
}
2. Implement the interface for sending SMS messages
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3. Create a proxy class and implement the same interface for sending SMS messages
public class SmsProxy implements SmsService {
private final SmsService smsService;
public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}
@Override
public String send(String message) {
//before calling the method, we can add our own actions
System.out.println("before method send()");
smsService.send(message);
//After calling the method, we can also add our own actions
System.out.println("after method send()");
return null;
}
}
4. Actual use
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("java");
}
}
After running the above code, the console prints.
before method send()
send message:java
after method send()
As you can see from the output, we have added the send()
method of SmsServiceImpl
.
3. Dynamic Proxy
Dynamic proxies are more flexible than static proxies. Instead of creating a separate proxy class for each target class, and instead of having to implement interfaces, we can directly proxy the implementing class ( CGLIB dynamic proxy mechanism).
From the JVM perspective, dynamic proxies are dynamically generated at runtime to generate class bytecode and load it into the JVM.
Speaking of dynamic proxies, Spring AOP, RPC framework should be two that have to be mentioned, their implementations rely on dynamic proxies.
Dynamic proxies are used relatively little in our daily development, but they are almost a must-have technology in the framework. After learning dynamic proxies, it is also very helpful for us to understand and learn the principles of various frameworks.
In terms of Java, there are many different ways to implement dynamic proxies, such as JDK dynamic proxy, CGLIB dynamic proxy, etc.
3.1. JDK Dynamic Proxy Mechanism
3.1.1. Introduction
The InvocationHandler
interface and the Proxy
class are the core of the Java dynamic proxy mechanism.
The most frequently used method in the Proxy
class is: newProxyInstance()
, which is mainly used to generate a proxy object.
public static Object newProxyInstance(ClassLoader loader,
Class<? >[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}
This method has a total of 3 parameters.
- loader : class loader for loading the proxy object.
- interfaces : Some interfaces implemented by the proxied class.
- h : An object that implements the
InvocationHandler
interface.
To implement dynamic proxies, you must also implement InvocationHandler
to customize the processing logic. When our dynamic proxy object calls a method, the method call is forwarded to the invoke
method of the class that implements the InvocationHandler
interface to be called.
public interface InvocationHandler {
/**
* This method is actually called when you call a method using a proxy object
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
The invoke()
method has the following three parameters.
- proxy : the dynamically generated proxy class
- method : the method corresponding to the method called by the proxy class object
- args : the parameters of the current method method
In other words:The proxy object you create with newProxyInstance()
of the Proxy
class will actually call the invoke()
method of the class that implements the InvocationHandler
interface when it invokes the method. You can customize the processing logic in the invoke()
method, such as what to do before and after the method execution.
3.1.2. Steps for using JDK dynamic proxy classes
- define an interface and its implementation classes.
- customize
InvocationHandler
and override theinvoke
method, in which we call the native methods (methods of the proxied class) and customize some processing logic. - pass
Proxy.newProxyInstance(ClassLoader loader,Class<? >[] interfaces,InvocationHandler h)
method to create the proxy object.
3.1.3. Code example
This may be a bit empty and difficult to understand, so I’ll give you an example to get a feel for it!
1. Define the interface for sending SMS messages
public interface SmsService {
String send(String message);
}
2. Implement the interface for sending SMS messages
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3. Define a JDK dynamic proxy class
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect;
public class DebugInvocationHandler implements InvocationHandler {
/**
* real object in the proxy class
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//before invoking the method, we can add our own actions
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//After calling the method, we can also add our own actions
System.out.println("after method " + method.getName());
return result;
}
}
invoke()
method: When our dynamic proxy object calls the native method, it actually ends up calling the invoke()
method, and then the invoke()
method calls the native method of the proxy object instead.
4. Get the factory class of the proxy object
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // class load of the target class
target.getClass().getInterfaces(), // interfaces to be implemented by the proxy, multiple interfaces can be specified
new DebugInvocationHandler(target) // custom InvocationHandler corresponding to the proxy object
);
}
}
getProxy()
: mainly through the Proxy.newProxyInstance()
method to get the proxy object of a class
5. Practical use
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");
After running the above code, the console prints out.
before method send
send message:java
after method send
3.2. CGLIB Dynamic Proxy mechanism
3.2.1. Introduction
One of the most fatal problems with JDK dynamic proxies is that they can only proxy classes that implement an interface.
To solve this problem, we can use the CGLIB dynamic proxy mechanism to avoid it.
CGLIBopen in new window(Code Generation Library) is a byte-based ASMopen in new window bytecode generation library, which allows us to modify and dynamically generate bytecode at runtime. CGLIB implements proxies by inheritance. Many well-known open-source frameworks use CGLIBopen in new window, for example, the AOP module in Spring: if the target object implements the interface, the JDK dynamic proxy is used by default, otherwise the CGLIB dynamic proxy is used.
The MethodInterceptor
interface and the Enhancer
class are the core of the CGLIB dynamic proxy mechanism.
You need to customize MethodInterceptor
and override the intercept
method, which is used to intercept methods that enhance the proxied class.
public interface MethodInterceptor
extends Callback{
// Intercept methods in the proxied class
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
- obj : dynamically generated proxy object
- method : intercepted methods (methods that need to be enhanced)
- args : method entry
- proxy : used to call the original method
You can use the Enhancer
class to get the proxied class dynamically. When the proxy class calls the method, the actual call is to the intercept
method in the MethodInterceptor
.
3.2.2. Steps for using CGLIB dynamic proxy classes
- define a class.
- customize
MethodInterceptor
and override theintercept
method,intercept
is used to intercept methods that enhance the proxied class, similar to theinvoke
method in JDK dynamic proxies. - create the proxy class via
create()
of theEnhancer
class.
3.2.3. Code example
Unlike the JDK dynamic proxies do not require additional dependencies. CGLIBopen in new window(Code Generation Library) is actually part of an open source project and you need to add the dependencies manually if you want to use it.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
1. Implement a class that sends SMS messages using Aliyun
public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
2. Custom MethodInterceptor
(method interceptor)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy;
import java.lang.reflect;
/**
* Custom MethodInterceptor
*/
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o Proxy object (object to be enhanced)
* @param method The intercepted method (the method to be enhanced)
* @param args method entry
* @param methodProxy for calling the original method
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// Before calling the method, we can add our own actions
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//After calling the method, we can also add our own actions
System.out.println("after method " + method.getName());
return object;
}
}
3. Get the proxy class
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<? > clazz) {
// Create dynamic proxy enhancer class
Enhancer enhancer = new Enhancer();
// Set the class loader
enhancer.setClassLoader(clazz.getClassLoader());
// set the proxied class
enhancer.setSuperclass(clazz);
// set the method interceptor
enhancer.setCallback(new DebugMethodInterceptor());
// create a proxy class
return enhancer.create();
}
}
4. Actual use
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");
After running the above code, the console prints out.
before method send
send message:java
after method send
3.3. Comparison of JDK Dynamic Proxy and CGLIB Dynamic Proxy
- JDK dynamic proxy can only proxy classes that implement interfaces or proxy interfaces directly, while CGLIB can proxy classes that do not implement any interfaces. In addition, CGLIB dynamic proxy intercepts the method calls of the proxied class by generating a subclass of the proxied class, so it cannot proxy classes and methods declared as final. 2. As far as the efficiency of the two is concerned, the JDK dynamic proxy is superior in most cases, and this advantage becomes more obvious as the JDK version is upgraded.
4. Comparison of Static and Dynamic Proxy
- Flexibility : Dynamic proxy is more flexible, does not need to have to implement the interface, can directly proxy the implementation class, and can not need to create a proxy class for each target class. In addition, static proxies, once the interface is newly added methods, the target object and the proxy object have to be modified, which is very troublesome!
- JVM level: Static proxies turn interfaces, implementation classes, and proxy classes into actual class files at compile time. Dynamic proxies, on the other hand, dynamically generate class bytecode at runtime and load it into the JVM.
5. Summary
This article introduces two implementations of the proxy pattern: static proxies and dynamic proxies. The article covers the difference between static proxy and dynamic proxy, the difference between static proxy and dynamic proxy, the difference between JDK dynamic proxy and Cglib dynamic proxy, and so on.