What is the proxy pattern
Proxy Pattern is one of the commonly used design patterns. Through Proxy Pattern, we can intervene the input and output of object access to achieve some enhanced functions without invading the original object. Examples such as request interceptors, performance statistics, or practice with AOP ideas are applications of the proxy pattern. And the proxy pattern is also the practice of the open closed principle (OCP) of object-oriented design principles, without modifying the original entity, through the proxy way to achieve the extension of capabilities.
There are usually three roles in the proxy mode:
Subject
: A set of behaviors that can be proxied, usually an interfaceProxySubject
: proxy object through which the consumer accesses the actual objectRealSubject
: The actual proxied object
How to implement the proxy pattern
There are three ways to implement the proxy pattern in Java: static proxy, dynamic proxy, and Cglib proxy.
Static agent
Static proxy means that the proxy class already exists in the compiled result before the program runs. To implement static proxy, first declare the interface Subject representing the collection of proxy behavior:
package me.leozdgao;
public interface Subject {
void action(String name);
}
Copy the code
Next we define a class to be proxied that implements the Subject interface:
package me.leozdgao;
public class RealSubject implements Subject {
@Override
public void action(String name) {
System.out.println("Get name: "+ name); }}Copy the code
The proxy is constructed based on the propped object. The implementation of the proxy class is as follows. The method implementation of the proxy class is to call the corresponding propped object method, and additional logic can be implemented before and after the invocation:
package me.leozdgao;
public class ProxySubject implements Subject {
private Subject target;
ProxySubject(Subject realOne) {
this.target = realOne;
}
@Override
public void action(String name) {
System.out.println("Before action");
this.target.action(name);
System.out.println("After action"); }}Copy the code
Finally, the specific application of static proxy:
package me.leozdgao;
public class Main {
public static void main(String[] args) {
RealSubject real = new RealSubject();
ProxySubject proxy = new ProxySubject(real);
proxy.action("demo"); }}Copy the code
Static proxy implementation is relatively clear and simple, sufficient in simple scenarios, but there are several obvious problems:
- The proxy class and proxied class must inherit the same interface, and interface changes need to modify both the proxy class and proxied class
- The abstraction of proxy behavior is not general enough; for each proxy behavior, an interface needs to be declared specifically
A dynamic proxy
Let’s look at the dynamic proxy approach, which is implemented based on the JDK’s reflection API:
package me.leozdgao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
private final Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance(a) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new ProxyInvocationHandler(target));
}
static class ProxyInvocationHandler implements InvocationHandler {
private final Object target;
public ProxyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before action");
Object result = method.invoke(target, args);
System.out.println("After action");
returnresult; }}}Copy the code
Unlike static proxies, dynamic proxies can implement a more general Proxy factory. A Proxy object instance is created using the JDK’s Proxy API proxy.newProxyInstance. The behavior of the proxy is realized by defining a class that implements the InvocationHandler interface.
The idea behind the Proxy API is that based on our passing in interfaces, we dynamically create the bytecode of a class that inherits the Proxy class and implements the interfaces that we provide, and load that class in through our passing in classLoader, For details, see the JDK source code (the core code for generating class bytecode is ProxyGenerator under the Sun.misc package).
💡 this part of the source code parsing may look at: www.cnblogs.com/liuyun1995/…
Let’s look at specific applications:
package me.leozdgao;
public class Main {
public static void main(String[] args) {
RealSubject subject = new RealSubject();
ProxyFactory factory = new ProxyFactory(subject);
Subject proxy = (Subject) factory.getProxyInstance();
proxy.action("myName"); }}Copy the code
In this case, we are proxying objects of type Object. We can also define different proxy factories for different types of objects as needed. In the process of application, we can see that dynamic proxy adds additional benefits compared to static proxy:
- No longer constrained by the need to implement a proxy class for every interface that needs to be proxied
Dynamic proxies can be implemented based on the JDK Proxy API. This is a significant limitation when we need to implement proxies for classes provided by two or three parties.
Additional agent
Cglib is an early community package that builds on the ASM package to generate bytecode at run time, similar to the idea behind dynamic proxies discussed above. The difference is that the dynamically generated class is inherited from the proxied class, which can solve the problem of using the dynamic proxy because there is no interface implementation. Of course, because it is inherited, it also brings several problems:
- Be modified as
final
The class cannot be proxied - Be modified as
final
The class method proxy will fail
Let’s look at a concrete example, starting with a class that doesn’t implement any interface:
package me.leozdgao;
public class StandaloneSubject {
public String echo(String name) {
return name;
}
public void doAction(String label) {
System.out.println("do something with label "+ label); }}Copy the code
Next we implement a proxy factory based on Cglib:
package me.leozdgao;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class ProxyFactoryWithCglib implements MethodInterceptor {
private Object target;
public ProxyFactoryWithCglib(Object target) {
this.target = target;
}
public Object getProxyInstance(a) {
Enhancer en = new Enhancer();
en.setSuperclass(target.getClass());
en.setCallback(this);
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Before action");
Object result = method.invoke(target, args);
System.out.println("after action");
returnresult; }}Copy the code
Then there is the specific application of proxy:
package me.leozdgao;
public class Main {
public static void main(String[] args) {
StandaloneSubject subject = new StandaloneSubject();
ProxyFactoryWithCglib factory = new ProxyFactoryWithCglib(subject);
StandaloneSubject proxy = (StandaloneSubject) factory.getProxyInstance();
proxy.doAction("label"); }}Copy the code
However, cglib has a long history, and iteration has been lagging behind the development of the Java community. In its Github project README, cglib also writes that JDK 17+ support is not perfect, suggesting that we try other dynamic bytecode generation solutions, such as ByteBuddy. This article will not expand the demonstration).
Application of different proxy implementations
It seems that there are a variety of schemes to achieve the proxy mode, and each scheme seems to have its own advantages and disadvantages. Let’s summarize and compare:
The application of static proxy is relatively limited. Because a proxy class must be implemented specifically for the interface of the proxy class, there is a great lack of generality, and it is difficult to separate the implementation of the proxy, and the application is relatively few.
Dynamic proxy and Cglib proxy are running behind the dynamic generation, the implementation of proxy schemes have a certain universality, but one is the JDK standard API, the other needs to introduce an additional three-party dependency. For example, dynamic proxies based on the JDK Proxy API must require proxied classes to implement interfaces and proxied methods, whereas the Cglib Proxy is based on class inheritance and does not have the limitation of the former, but has no control over final classes or class methods that are modified to be final. In most scenarios, neither of them has absolute advantages, so you need to make your own decisions based on the implementation principles behind them.
Application of Spring AOP proxy
Spring’s AOP implementation is proxy-based, so what’s behind the implementation? In fact, is also used in the article method, and through the dynamic decision at runtime, you can see DefaultAopProxyFactory source code implementation:
public class DefaultAopProxyFactory implements AopProxyFactory.Serializable {
// ...
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if(! NativeDetector.inNativeImage() && (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) { Class<? > targetClass = config.getTargetClass();if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return newJdkDynamicAopProxy(config); }}// ...
}
Copy the code
As you can see, the Cglib agent can be specified with the optimize or proxyTargetClass in Spring, and the cglib agent can be used when the proxied class does not implement an interface. In other cases, dynamic proxies based on the JDK Proxy API are used.
conclusion
Java, as a strongly typed language, provides efficiency and stability in development through strongly typed constraints, but requires a more general proxy pattern than dynamically typed languages. But the Java ecosystem is certainly quite mature, with the JDK providing standards-based proxy apis and even using dark arts like dynamic bytecode generation to implement proxies. You just need to be familiar with the implementation of the various agents in order to use them properly during the implementation.