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 interface
  • ProxySubject: proxy object through which the consumer accesses the actual object
  • RealSubject: 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 asfinalThe class cannot be proxied
  • Be modified asfinalThe 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.