Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
The proxy pattern in Java
1 Proxy Mode
The proxy pattern is a structural design pattern used to assemble classes and objects into larger structures while keeping the structures flexible and efficient
In proxy mode, we substitute proxy objects for requests to real objects to provide additional operations or functionality
Principles of the proxy pattern:
- The structure of the actual object is not modified
- It is insensitive to the requester
- Does not depend on whether the actual object is created
The proxy pattern provides the same interface as the real object, while the decorator pattern provides an enhanced interface
Some application scenarios of proxy mode:
- Actual object lifecycle management
- Access control
- logging
The proxy mode has two implementation modes: static proxy and dynamic proxy
2 Static Proxy
Static proxy implementation steps are as follows:
- Define a behavior interface
- Create an implementation class for the behavior interface
- Create a proxy class that implements the behavior interface and have a property that holds an implementation class object instance of the behavior interface
- Change the location of the actual request object to the request proxy object
Take the message sending scenario as an example:
Behavior interface:
public interface IMessageAction {
void send(String message);
}
Copy the code
Behavior interface implementation class:
public class MessageService implements IMessageAction {
@Override
public void send(String message) {
System.out.printf("Manually send message: %s%n", message); }}Copy the code
The proxy class:
public class MessageProxy implements IMessageAction {
private final MessageService target;
public MessageProxy(MessageService target) {
this.target = target;
}
@Override
public void send(String message) {
System.out.printf("Proxy log: send message [%s]%n", message); target.send(message); }}Copy the code
Testing:
public class Test {
public static void main(String[] args) {
MessageService messageService = new MessageService();
MessageProxy messageProxy = new MessageProxy(messageService);
messageService.send("Java");
messageProxy.send("Java"); }}Copy the code
The output is:
Send message manually: Java proxy Log: Send message [Java] Send message manually: JavaCopy the code
The operation of the static proxy is done manually by the coder
This design creates a serious problem: if the actual object changes, the proxy object needs to be modified synchronously
The actual use of static proxies is rare, and can be tried only when the real object cannot be modified and additional functionality is fixed (is there really a requirement that doesn’t change)
3 Dynamic Proxy
From the JVM’s perspective, dynamic proxies dynamically generate and load class bytecode while the program is running
There is no need to create a separate proxy class, no need to care about changes to the actual object, or even the actual object
Dynamic proxies can be implemented in two ways: JDK dynamic proxy and CGLIB dynamic proxy
3.1 JDK Dynamic Proxy
Here is an example of refactoring a static proxy using the JDK dynamic proxy
Create dynamic proxy class:
public class MessageInvocationHandler implements InvocationHandler {
private final Object target;
public MessageInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException,
InvocationTargetException {
System.out.printf("Log: send message [%s]%n", args[0]);
returnmethod.invoke(target, args); }}Copy the code
Create a proxy factory:
public class MessageProxyFactory {
public static IMessageAction getProxy(Object target) {
return (IMessageAction) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
newMessageInvocationHandler(target)); }}Copy the code
Note here that the agent type returned by the factory should be the interface IMessageAction,
If the implementation class MessageService is used, a type conversion error exception is thrown
From this we can see that the JDK dynamic proxy requirement is to implement the interface
Testing:
public class Test {
public static void main(String[] args) {
MessageProxyFactory.getProxy(new MessageService()).send("Java"); }}Copy the code
The output is:
Log: Send message [Java] Manually send message: JavaCopy the code
In our example, we decoupled the proxy from the real object by not having the proxy class implement the SEND method manually
3.2 CGLIB dynamic proxy
In JDK dynamic proxies, there is a constraint that you can only proxy classes that implement the interface
CGLIB is based on the ASM bytecode generation library, which helps us implement the proxy for classes with unimplemented interfaces
In Spring’s AOP module, JDK dynamic proxies are used if the target object implements an interface, and CGLIB is used instead
To use CGLIB, you need to add dependencies to Maven:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3. 0</version>
</dependency>
Copy the code
Continue refactoring the previous example
Delete the IMessageAction interface and create the service class directly:
public class MessageService {
public void send(String message) {
System.out.printf("Manually send message: %s%n", message); }}Copy the code
Create method interceptor:
public class MessageMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.printf("Log: send message [%s]%n", args[0]);
returnmethodProxy.invokeSuper(target, args); }}Copy the code
Modify the agent factory:
public class MessageProxyFactory {
public static MessageService getProxy(Class<MessageService> messageServiceClass) {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(messageServiceClass.getClassLoader());
enhancer.setSuperclass(messageServiceClass);
enhancer.setCallback(new MessageMethodInterceptor());
return(MessageService) enhancer.create(); }}Copy the code
Testing:
public class Test {
public static void main(String[] args) {
MessageProxyFactory.getProxy(MessageService.class).send("Java"); }}Copy the code
The output is:
Log: Send message [Java] Manually send message: JavaCopy the code
CGLIB implements proxies by inheritance, and invokeSuper is called in method interceptors to get a feel for it