The proxy pattern

The proxy pattern is a structural design pattern in which a client request does some extra work before it reaches the actual object. So for example,

  1. You need to find a house, so you can find a suitable house by paying the amount to the intermediary, and the intermediary needs to negotiate the price difference with the landlord, connect the tenant and the landlord, the intermediary is the agent.
  2. When you need to go home for the Spring Festival, you don’t know how to operate the 12306 app, but Meituan and Alipay have launched the function of “helping you grab tickets”. You don’t need to operate 12306, you just need to pay the amount to Meituan and Alipay, and let the platform help you grab tickets, which is actually a kind of agent.

Static proxy mode

Let’s implement the static proxy in code. Requirements:

  1. The tenant has $1,000 and needs to rent.
  2. Agents can help tenants rent a house, but they need to charge a agency fee of 100 yuan.
  3. The landlord owns the house, but he can’t find any real tenants.
  • RentSubject

Establish an interface for renting a house. Users can pay for the house keys by operating the interface.

package com.tea.modules.design.proxy;

import java.math.BigDecimal;

/ * * *@authorJaymin <br> * Theme for renting a house.<br> * For renters, just pay.<br> * 2021/2/14 18:40 */
public interface RentSubject {

    /** * Pay rent to find a house. *@paramRent rent *@return* /
    String findHouse(BigDecimal rent);
}
Copy the code
  • LandlordProxied

The landlord just collects the money and hands over the keys.

package com.tea.modules.design.proxy;

import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;

/ * * *@authorJaymin.<br> * The landlord is only responsible for collecting money to pay for the house. Does not care who makes the payment
@Slf4j
public class LandlordProxied implements RentSubject {

    @Override
    public String findHouse(BigDecimal rent) {
        log.info("Landlord received :{} rent and handed over keys.", rent);
        return "Lock"; }}Copy the code
  • RentAgencyProxy

The intermediary, the intermediary is responsible for collecting money from the tenant, after collecting the intermediary fee, pays the rent to the landlord and obtains the key, and then gives it to the tenant.

package com.tea.modules.design.proxy.statics;

import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;

/ * * *@authorJaymin.<br> * Rental agency, responsible for helping tenants find a house.< BR > * Also, after finding a house, the agent needs to pay rent to the landlord.<br> * 2021/2/14 18:45 */
@Slf4j
public class RentAgencyProxy implements RentSubject {

    private RentSubject rentSubject;

    public RentAgencyProxy(RentSubject rentSubject) {
        this.rentSubject = rentSubject;
    }

    @Override
    public String findHouse(BigDecimal rent) {
        BigDecimal actualRent = beforeRealSubject(rent);
        return this.rentSubject.findHouse(actualRent);
    }

    private BigDecimal beforeRealSubject(BigDecimal rent) {
        log.info("Agent charging current tenant rent :{}", rent);
        // The agent earns the intermediate price difference and pays it to the landlord
        BigDecimal actualRent = rent.subtract(BigDecimal.valueOf(100));
        returnactualRent; }}Copy the code
  • TenantClient

The tenant, the tenant pays the rent, gets the keys.

package com.tea.modules.design.proxy;

import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;

/ * * *@author<br> * Tenant, currently looking for a house, with $1000 in hand.< BR > * The agent found a house for $900, accepted the order and charged a broker fee of $100.< BR > * The landlord has a vacant house at this time, $900. <br> * 2021/2/14 18:53 */
@Slf4j
public class TenantClient {

    public static void main(String[] args) {
        RentSubject rentSubject = new RentAgencyProxy(new LandlordProxied());
        String house = rentSubject.findHouse(BigDecimal.valueOf(1000));
        log.info("The tenant got the key to the house :"+ house); }}Copy the code
  • Result
19:30:41. [the main] INFO 553 com. Tea. Modules. The design. The proxy. RentAgencyProxy - intermediary charging current tenant rent: 1000 19:30:41. [the main] 558 INFO Com. Tea. Modules. The design, the proxy LandlordProxied - the landlord received: 900 rent, handing over the keys. 19:30:41, 558 [main] INFO Com. Tea. Modules. The design, the proxy TenantClient - tenant to get the key to the door: the LockCopy the code
Pitfalls of static proxies

Right now for intermediary, its purpose has been very clear, namely earn price difference. In fact, the intermediary does not care about what business really needs to be done, whether it is renting, buying a house, buying furniture… Just take the money from the customer and find a real service provider to deliver it.

So for the Proxy class at this point, no matter what logic is in the final RealSubject, it is only responsible for the Proxy (that is, the amount that passes through the Proxy class is automatically deducted by 100). Imagine that there is a new business market at this point, which is also to earn the difference, then the static agency approach still needs to repackage a set of logic. If there are more and more of these classes, and the agent logic is consistent, the project will end up ballooning classes very quickly, increasing maintenance costs. At this point, the agent logic is determined for the agent, and the propped class (targetObject) may be unknown. How to separate the agent logic from the original class logic? At this point, we need dynamic proxies.

A dynamic proxy

Dynamic proxy techniques fall into two categories in Spring AOP:

  • Dynamic proxy based on JDK native.

Provides a new class that creates a set of interfaces at run time. Since Java does not support instantiating interfaces, the JDK generates a proxy class to implement a given interface at runtime, forwarding the implementation logic to the Invocation Handler when the Invocation of the proxy class interface is invoked.

Classes that use the JDK to dynamically Proxy must implement the interface (all Proxy classes are subclasses of java.lang.Reflect.proxy, starting with the class name $Proxy).

  • Dynamic proxy based on CGLIB.

CGLIB(Code Generation Library) is a class Library based on ASM(a framework for manipulating Java bytecode). In Spring AOP, CGLIB is chosen for proxying if the targetObject class does not implement an interface, that is, if the proxy class cannot be generated through the JDK’s dynamic proxy. The CGLIB dynamic proxy works by creating a subclass of targetObject that overrides the methods that require the parent class, enhancing functionality in the overridden methods. Note that classes decorated with final methods cannot be proxied using CGLIB because they are overridden by inheritance.

1. Use JDK dynamic proxy to implement proxy mode
  • IntermediaryInvocationHandler
package com.tea.modules.design.proxy.dynamic.jdkproxy;

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.math.BigDecimal;

/ * * *@authorJaymin.<br> * JDK dynamic proxies implement the logic of the mediation to earn the difference.<br> * here encapsulates Aspect logic, as opposed to Aspect in AOP
@Slf4j
public class IntermediaryInvocationHandler implements InvocationHandler {

    private Object targetObject;

    public IntermediaryInvocationHandler(Object targetObject) {
        this.targetObject = targetObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        BigDecimal actualPrice = beforeRealSubject(((BigDecimal) args[0]));
        args[0] = actualPrice;
        Object result = method.invoke(targetObject, args);
        return result;
    }

    private BigDecimal beforeRealSubject(BigDecimal money) {
        log.info("Intermediary charge :{}", money);
        // The intermediary earns the intermediate price difference and pays it to the service provider
        BigDecimal actualPrice = money.subtract(BigDecimal.valueOf(100));
        returnactualPrice; }}Copy the code

To use the JDK’s dynamic proxy, you need to implement the InvocationHandler interface and then use java.lang.reflect.proxy #newProxyInstance to generate the proxy class.

  • DynamicProxyDemo
package com.tea.modules.design.proxy.dynamic;

import com.tea.modules.design.proxy.dynamic.jdkproxy.IntermediaryInvocationHandler;
import com.tea.modules.design.proxy.statics.LandlordProxied;
import com.tea.modules.design.proxy.statics.RentSubject;
import net.sf.cglib.proxy.MethodInterceptor;

import java.lang.reflect.InvocationHandler;
import java.math.BigDecimal;

/ * * *@authorCGLIB dynamic proxy.<br> * 2021/2/14 20:11 */
public class DynamicProxyDemo {

    public static void main(String[] args) {
        jdkDynamicProxy();
    }

    / * * * the JDK dynamic proxy. < br > * here, we only need to provide a plane logic IntermediaryInvocationHandler agent logic can be completed through reuse. < br > * more rare is, as long as the class implements the arbitrary interface, If the first parameter in the method argument is the amount, then the mediation can seamlessly earn the difference, rather than creating classes
    private static void jdkDynamicProxy(a) {
        RentSubject targetObject = new LandlordProxied();
        InvocationHandler handler = new IntermediaryInvocationHandler(targetObject);
        // Gets the classloader of the currently propped classClassLoader classLoader = targetObject.getClass().getClassLoader(); Class<? >[] interfaces = targetObject.getClass().getInterfaces(); RentSubject rentSubject = (RentSubject) Proxy.newProxyInstance(classLoader, interfaces, handler); System.out.println("Is the current object a proxy class :" + Proxy.isProxyClass(rentSubject.getClass()));
        rentSubject.findHouse(BigDecimal.valueOf(1000)); }}Copy the code
  • Result
Whether the current object for the proxy class: true 16:42:23) 870 [main] INFO com. Tea. Modules. The design, the proxy. Dynamic. Jdkproxy. IntermediaryInvocationHandler - Intermediary fees: 1000 16:42:23) 870 [main] INFO com. Tea. Modules. The design. The proxy. The statics. LandlordProxied - the landlord received: 900 rent, handing over the keys.Copy the code

As you can see, create a Proxy object with proxy.newProxyInstance using the LandlordProxied that implements the interface as a targetObject. Will be in its execution findHouse callback IntermediaryInvocationHandler invoke method.

2. Use CGLIB to implement the proxy mode

CGLIB is not a JDK native package, so we need to import CGLIB’s dependencies.

  • pom.xml
    <! -- https://mvnrepository.com/artifact/cglib/cglib -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.9</version>
    </dependency>
Copy the code
  • Create a business class that does not implement an interface
package com.tea.modules.design.proxy.dynamic.cglibproxy;

import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;

/ * * *@authorJaymin.<br> * Test CGLIB to see if it can enhance classes that do not implement interfaces
@Slf4j
public class NormalLandlord {

    public String findHouse(BigDecimal rent) {
        log.info("Landlord received :{} rent and handed over keys.", rent);
        return "Lock"; }}Copy the code
  • IntermediaryMethInterceptor
package com.tea.modules.design.proxy.dynamic.cglibproxy;

import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.math.BigDecimal;

/ * * *@authorJaymin.<br> * Implementing dynamic proxy based on CGLIB.<br> * 2021/2/14 20:46 */
@Slf4j
public class IntermediaryMethInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        BigDecimal actualPrice = beforeRealSubject(((BigDecimal) args[0]));
        args[0] = actualPrice;
        Object result = methodProxy.invokeSuper(object, args);
        return result;
    }

    private BigDecimal beforeRealSubject(BigDecimal money) {
        log.info("Intermediary charge :{}", money);
        // The intermediary earns the intermediate price difference and pays it to the service provider
        BigDecimal actualPrice = money.subtract(BigDecimal.valueOf(100));
        returnactualPrice; }}Copy the code

To create a proxy class in CGLIB, you need to write a section class that implements MethodInterceptor. The business is enhanced in the Intercept method, which calls the target class as methodProxy.invokesuper.

  • DynamicProxyDemo
package com.tea.modules.design.proxy.dynamic;

import com.tea.modules.design.proxy.dynamic.cglibproxy.IntermediaryMethInterceptor;
import com.tea.modules.design.proxy.dynamic.cglibproxy.NormalLandlord;
import com.tea.modules.design.proxy.statics.LandlordProxied;
import com.tea.modules.design.proxy.statics.RentSubject;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

import java.lang.reflect.InvocationHandler;
import java.math.BigDecimal;

/ * * *@authorCGLIB dynamic proxy.<br> * 2021/2/14 20:11 */
public class DynamicProxyDemo {

    public static void main(String[] args) {
        cglibDynamicProxy();
    }


    /** * CGLIB. Create a subclass of the target class and override its methods. The final logic delegates to the MethodInterceptor */
    private static void cglibDynamicProxy(a){
        NormalLandlord targetObject = new NormalLandlord();
        MethodInterceptor methInterceptor = new IntermediaryMethInterceptor();
        NormalLandlord proxy = (NormalLandlord) Enhancer.create(targetObject.getClass(), methInterceptor);
        proxy.findHouse(BigDecimal.valueOf(1000)); }}Copy the code

The key code is actually one line: enhancer.create (targetobject.getClass (), methInterceptor), where methInterceptor is our facet class.

  • Result
16:54:46.102[the main] INFO com. Tea. Modules. The design. The proxy. Dynamic. Cglibproxy. IntermediaryMethInterceptor - intermediary fees:1000
16:54:46.118[the main] INFO com. Tea. Modules. The design. The proxy. Dynamic. Cglibproxy. NormalLandlord - the landlord received:900Rent, hand over the keys.Copy the code
summary
  • JDK dynamic proxies require proxied classes to implement interfaces. The aspect class needs to be implementedInvocationHandler.
  • CGLIB implements facets in the form of inheritance + method override, delegating logic to overriding methodsMethodInterceptor#intercept.
  • CGLIB has almost no restrictions on proxy classes, but it is important to note that proxied classes cannot befinalModifiers modify. Because Java cannot override final classes.

Simple dynamic proxy

1. How is JDK dynamic proxy implemented?

Many friends will have doubts, these dynamic proxy classes invisible, although you can see the effect, but what is the bottom layer to do, why the implementation of interface? OK, let’s start with the JDK’s dynamic proxy to see what a proxy class actually looks like.

  • From the Proxy newProxyInstance of
    public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        finalClass<? >[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();
        if(sm ! =null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /* * Look up or generate the designated proxy class. */Class<? > cl = getProxyClass0(loader, intfs);// Omit some code
    }
Copy the code

First, try to get the proxy class, which may be cached, and if not, do the build logic.

  • java.lang.reflect.Proxy#getProxyClass0
    private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class has already implemented the given interface through the classloader, then a copy of it is returned from the cache
        // Otherwise, it creates the proxy class through ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }
Copy the code
  • java.lang.reflect.Proxy.ProxyClassFactory#apply
public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) {/* * Generate the specified proxy class */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); }}}Copy the code

ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); This code is the key to generating the dynamic proxy class. Upon execution, the bytecode array describing the proxy class is returned. The program then reads the bytecode array and converts it into a run-time data structure-class object, which is used as a regular Class.

  • sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<? >[], int)
    public static byte[] generateProxyClass(finalString var0, Class<? >[] var1,int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        // Disk write if persistent proxy class is declared.
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run(a) {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('. ', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }

                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: "+ var4x); }}}); }return var4;
    }
Copy the code

Here we find a key criterion -saveGeneratedFiles, which is whether the proxy class needs to be persisted.

private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
Copy the code

Here will determine whether sun. Misc. ProxyGenerator. Whether saveGeneratedFiles variable to true. The default is false.

  • Set saveGeneratedFiles to true when the main method starts.
public class DynamicProxyDemo {

    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true"); jdkDynamicProxy(); }}Copy the code

To locate the generated class, we use files.write (var2, var4, new OpenOption[0]); Break point to view the path.


  • Generated proxy classes

package com.sun.proxy;

import com.tea.modules.design.proxy.statics.RentSubject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigDecimal;

public final class $Proxy0 extends Proxy implements RentSubject {
    // Omit some code
    public final String findHouse(BigDecimal var1) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}// Omit some code
}
Copy the code

Focusing on findHouse, we can see that the call to the Proxy findHouse will execute super.h.invoke, where h is protected InvocationHandler H in the Proxy class; “, which proves that our idea is right. You should also note that the Proxy class inherits from Proxy and implements the given RentSubject interface. At this point you should have a better understanding of JDK dynamic proxies.

Here is a more vivid explanation of dynamic proxy seen from Zhihu:

What does Java dynamic proxy do?

2. Why does agent failure sometimes occur?

Here is an example of a failure scenario. The bar() method does not perform the aspect logic, assuming that a logging annotation, @log, has injected SimpleServiceImpl into another class and called simpleserviceimp.foo ().

public class SimpleServiceImpl implements SimpleService {

    public void foo(a) {
        // call bar() from foo()
        this.bar();
    }
    
    @Log
    public void bar(a) {
        // some logic...}}Copy the code

Here’s why :Spring proxies SimpleServiceImpl, but the @log annotation is only on bar(), To enter the proxy class’s logic, simpleserviceimp.bar () requires a reference to the proxy class. To put it another way,SimpleServiceImpl#foo delegates logic to the Target class to execute. This.bar () is called in the Target class. This points to a reference to the target class itself, not a reference to the proxy class, and therefore cannot be wrapped by the proxy class.

If you still don’t understand, you can visit the following article to further understand:

The Spring website explains AOP proxies

A Spring AOP pit! A lot of people do it!

3. Why does Spring AOP include JDK dynamic proxies when CGLIB is much more free (without implementing interfaces)?

JDK dynamic proxy is the official Java dynamic proxy mode, which is maintained and optimized without introducing third-party dependencies. CGLIB is a third-party framework. As JDK versions are upgraded, projects may need to replace CGLIB to be compatible with the latest JDK. Performance, as JDK versions have been updated, is not much different from CGLIB.

4. Does dynamic proxy affect application performance?

If a large number of classes are generated using dynamic proxies, it may cause an overflow of memory in the method area. Starting with JDK8, the JVM removes permanent generations and replaces them with meta-spaces. By default, the dynamic proxy classes generated by the framework make it difficult for the JVM to generate exceptions that overflow method area memory. However, prior to JDK8, there was a problem with dynamic proxy classes populating the method area with large amounts of memory overflow. For an example, check out Zhiming Chou’s In-depth Understanding of The Java Virtual Machine: Advanced JVM Features and Best Practices (3rd edition). For CGLIB, check out The article: CGLIB: The Missing Manual

conclusion

OK, see here, I believe you have a certain understanding of dynamic proxy technology, in fact, we usually use dynamic proxy scenarios are relatively few, most of them are filled with business code. But learning the underlying principles of the framework will give you a better understanding of Spring AOP and help you avoid some common mistakes.