“This is my 17th day of the August Genwen Challenge.More challenges in August”

First, what is the agency model?

The proxy pattern is a structural pattern that provides a proxy for other objects to control access to the object. In some cases, the proxy pattern can be used when an object is inappropriate or cannot directly reference another object. The proxy object acts as an intermediary between the client and the target object.

2. Application scenarios of proxy mode

Daily ticketing scalpers, rental agents, brokers, matchmaking agents, express delivery agents, business agents, non-intrusive log monitoring, etc., are the actual embodiment of the agency model. When you cannot or do not want to refer to an object directly or it is difficult to access an object, you can access it indirectly through a proxy object.

The proxy pattern is used for two purposes: to protect and enhance target objects.

Static proxy

For example, when some people are of marriageable age, their parents can’t wait to have grandchildren. Now, under all kinds of pressure, many people are choosing to marry and have children later. So anxious parents began to set up blind dates for their children, more anxious than the children themselves. The code to create the top-level interface IPerson is as follows:

public interface IPerson {
    void findLove(a);
}
Copy the code

ZhangSan = IPerson = IPerson = IPerson

public class ZhangSan implements IPerson {
    @Override
    public void findLove(a) {
        System.out.println("Son demands: White, beautiful, long legs."); }}Copy the code

The father ZhangLaoSan is going to help his son ZhangSan with the blind date, and the IPerson interface is implemented:

public class ZhangLaoSan implements IPerson {

    private ZhangSan zhangSan;

    public ZhangLaoSan(ZhangSan zhangSan) {
        this.zhangSan = zhangSan;
    }

    @Override
    public void findLove(a) {
        System.out.println("Chang Lao SAN begins his search.");
        zhangSan.findLove();
        System.out.println("Start dating."); }}Copy the code

Test code:

public class Test {
    public static void main(String[] args) {
        ZhangLaoSan zhangLaoSan = new ZhangLaoSan(newZhangSan()); zhangLaoSan.findLove(); }}Copy the code

One drawback of the above scenario is that his father will only look for a blind date for his children, and the children of other families will not take care of it. But in society the business has grown into an industry, with matchmakers, matchmaking agencies and all kinds of bespoke packages. Using static proxies is too expensive and requires a more generic solution that meets the needs of any single person looking for a partner.

Dynamic proxy

Continue above, static proxy to dynamic proxy. Using dynamic agents basically as long as an IPerson can provide matchmaking services. The underlying implementation of dynamic proxies is generally not something we need to implement ourselves, and there are plenty of apis out there. In the Java ecosystem, the most common use today is the JDK’s own proxies and CGLib’s libraries. Let’s start by updating the code based on the JDK’s dynamic proxy.

Create a media human:

public class JdkMeiRen implements InvocationHandler {

    private IPerson target;

    public IPerson getInstance(IPerson target) {
        this.target = target;
        Class<? extends IPerson> clazz = target.getClass();
        return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    private void before(a) {
        System.out.println("I'm the matchmaker. I've gathered your needs and started looking.");
    }

    private void after(a) {
        System.out.println("It was agreed to begin intercourse."); }}Copy the code

Create another class ZhaoLiu:

public class ZhaoLiu implements IPerson {
    @Override
    public void findLove(a) {
        System.out.println("Zhao Six requirements: a car, a house and a degree."); }}Copy the code

The test code is as follows:

public class Test {
    public static void main(String[] args) {
        JdkMeiRen jdkMeiRen = new JdkMeiRen();
        IPerson zhangSan = jdkMeiRen.getInstance(new ZhangSan());
        zhangSan.findLove();
        System.out.println("= = = = = = = = = = = = = = = = = = = = =");
        IPerson zhaoLiu = jdkMeiRen.getInstance(newZhaoLiu()); zhaoLiu.findLove(); }}Copy the code

The operating effect is shown in the figure below:

Five, handwritten JDK dynamic proxy implementation principle

Not just the why, but the why. Given that the JDK dynamic proxy is so powerful, how is it implemented? Let’s first explore the principle, and imitate the JDK dynamic proxy to write a dynamic proxy of their own.

We all know that JDK dynamic proxies use bytecode recombination to regenerate objects in place of the original objects to achieve dynamic proxies.

The JDK dynamic proxy generates objects as follows:

  1. Gets a reference to the proxied object, and gets all its interfaces, reflecting the fetch.
  2. The JDK dynamic proxy class regenerates a new class that implements all the interfaces implemented by the proxy class.
  3. Java code is generated dynamically, and the newly added business logic method has certain logical code calls (represented in the code).
  4. Compile the newly generated Java code.class file.
  5. Reload into the JVM to run.

This process is called bytecode recombination. There is a specification in the JDK that will automatically generate.class files starting with $in the ClassPath. So is there any way to see the “real face” of the replacement object? To do this test, we stream the in-memory object bytecode to a new.class file, and then use a decompile tool to view its source code.

public class JDKProxyTest {

    public static void main(String[] args) {
        try {
            byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0".new Class[]{IPerson.class});
            FileOutputStream fos = new FileOutputStream("$Proxy0.class");
            fos.write(bytes);
            fos.close();
        } catch(Exception e) { e.printStackTrace(); }}}Copy the code

$proxy0.class = $proxy0.class = $proxy0.class = $proxy0.class

public final class $Proxy0 extends Proxy implements IPerson {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final void findLove(a) throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final String toString(a) throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final int hashCode(a) throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.mark.proxy.staticproxy.IPerson").getMethod("findLove");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

We found that $Proxy0 extends the Proxy class, implements the IPerson interface, and overrides methods like findLove(). Reflection finds all methods of the target object in the static block and saves references to all methods. Where does this code come from? In fact, the JDK automatically generated it for us. Instead of relying on the JDK, we now generate source code dynamically, compile it dynamically, and then replace the target object and execute it ourselves.

Create the MarkInvocationHandler interface:

public interface MarkInvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
Copy the code

Create MarkClassLoader class:

public class MarkClassLoader extends ClassLoader {

    private File classPathFile;

    public MarkClassLoader(a) {
        String path = MarkClassLoader.class.getResource("").getPath();
        System.out.println("MarkClassLoader path = " + path);
        this.classPathFile = new File(path);
    }

    @Override
    protectedClass<? > findClass(String name)throws ClassNotFoundException {
        String className = MarkClassLoader.class.getPackage().getName() + "." + name;
        System.out.println("findClass className = " + className);
        if(classPathFile ! =null) {
            File classFile = new File(classPathFile, name.replaceAll("\."."/") + ".class");
            if (classFile.exists()) {
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try {
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte[] buff = new byte[1024];
                    int len;
                    while((len = in.read(buff)) ! = -1) {
                        out.write(buff, 0, len);
                    }
                    return defineClass(className, out.toByteArray(), 0, out.size());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if(out ! =null) {
                        try {
                            out.close();
                        } catch(IOException e) { e.printStackTrace(); }}if(in ! =null) {
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return null; }}Copy the code

Create MarkProxy class:

public class MarkProxy {

    public static final String ln = "\r\n";

    public static Object newProxyInstance(MarkClassLoader classLoader, Class
       [] interfaces, MarkInvocationHandler h) {
        try {
            //1, generate source code dynamically. Java file
            String src = generateSrc(interfaces);
            System.out.println(src);

            //2, Java file output disk
            String filePath = MarkProxy.class.getResource("").getPath();
            System.out.println(filePath);
            File f = new File(filePath + "$Proxy0.java");
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();

            //3. Compile the generated.java file into a.class file
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null.null.null);
            Iterable iterable = manage.getJavaFileObjects(f);

            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null.null.null, iterable);
            task.call();
            manage.close();

            // the generated. Class file is loaded into the JVM
            Class proxyClass = classLoader.findClass("$Proxy0");
            Constructor c = proxyClass.getConstructor(MarkInvocationHandler.class);
            f.delete();

            Return the new proxy object after the bytecode reorganization
            return c.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class
       [] interfaces) {
        StringBuffer sb = new StringBuffer();
        sb.append(MarkProxy.class.getPackage() + ";" + ln);
        sb.append("import " + interfaces[0].getName() + ";" + ln);
        sb.append("import java.lang.reflect.*;" + ln);
        sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
        sb.append("MarkInvocationHandler h;" + ln);
        sb.append("public $Proxy0(MarkInvocationHandler h) { " + ln);
        sb.append("this.h = h;");
        sb.append("}" + ln);
        for (Method m : interfaces[0].getMethods()) { Class<? >[] params = m.getParameterTypes(); StringBuffer paramNames =new StringBuffer();
            StringBuffer paramValues = new StringBuffer();
            StringBuffer paramClasses = new StringBuffer();

            for (int i = 0; i < params.length; i++) {
                Class clazz = params[i];
                String type = clazz.getName();
                String paramName = toLowerFirstCase(clazz.getSimpleName());
                paramNames.append(type + "" + paramName);
                paramValues.append(paramName);
                paramClasses.append(clazz.getName() + ".class");
                if (i > 0 && i < params.length - 1) {
                    paramNames.append(",");
                    paramClasses.append(",");
                    paramValues.append(",");
                }
            }

            sb.append("public " + m.getReturnType().getName() + "" + m.getName() + "(" + paramNames.toString() + "{" + ln);
            sb.append("try{" + ln);
            sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod("" + m.getName() + "",new Class[]{" + paramClasses.toString() + "});" + ln);
            sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", m.getReturnType()) + ";" + ln);
            sb.append("}catch(Error _ex) { }");
            sb.append("catch(Throwable e){" + ln);
            sb.append("throw new UndeclaredThrowableException(e);" + ln);
            sb.append("}");
            sb.append(getReturnEmptyCode(m.getReturnType()));
            sb.append("}");
        }
        sb.append("}" + ln);
        return sb.toString();
    }

    private static Map<Class, Class> mappings = new HashMap<Class, Class>();

    static {
        mappings.put(int.class, Integer.class);
    }

    private static String getReturnEmptyCode(Class
        returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "return 0;";
        } else if (returnClass == void.class) {
            return "";
        } else {
            return "return null;"; }}private static String getCaseCode(String code, Class
        returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "(" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
        return code;
    }

    private static boolean hasReturnValue(Class
        clazz) {
        returnclazz ! =void.class;
    }

    private static String toLowerFirstCase(String src) {
        char[] chars = src.toCharArray();
        chars[0] + =32;
        returnString.valueOf(chars); }}Copy the code

Create MarkMeiRen class:

public class MarkMeiRen implements MarkInvocationHandler {

    // The proxied object saves the reference
    private Object target;

    public Object getInstance(Object target) {
        this.target = target; Class<? > clazz = target.getClass();return MarkProxy.newProxyInstance(new MarkClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        method.invoke(target, args);
        after();
        return null;
    }

    private void before(a) {
        System.out.println("I'm the matchmaker. I'm going to find you a match. Now I've confirmed your needs.");
        System.out.println("Start looking");
    }

    private void after(a) {
        System.out.println("If it suits you, be ready to do it."); }}Copy the code

The client test code is as follows:

public class Test {
    public static void main(String[] args) {
        IPerson zhangSan = (IPerson) new MarkMeiRen().getInstance(newZhangSan()); System.out.println(zhangSan.getClass()); zhangSan.findLove(); }}Copy the code

The running results are as follows:

To this, handwritten JDK dynamic proxy is completed, “friends” is not another interview with a “killer”?

6. CGLib proxy call API and principle analysis

CglibMeiRen class: CglibMeiRen class:

public class CglibMeiRen implements MethodInterceptor {

    public Object getInstance(Class
        clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

    private void before(a) {
        System.out.println("I'm the matchmaker. I'm going to find you a match. Now I've confirmed your needs.");
        System.out.println("Start looking");
    }

    private void after(a) {
        System.out.println("If it suits you, be ready to do it."); }}Copy the code

Create single Customer class

public class Customer {

    public void findLove(a) {
        System.out.println("Pale and beautiful with long legs."); }}Copy the code

One small detail is that the target object of the CGLib proxy does not need to implement any interface. It implements the dynamic proxy by inheriting the target object dynamically. Take a look at the test code:

public class Test {
    public static void main(String[] args) {
        Customer customer = (Customer) newCglibMeiRen().getInstance(Customer.class); System.out.println(customer.getClass()); customer.findLove(); }}Copy the code

The running results are as follows:

How does the CGLib proxy work? The CGLib proxyed.class file is written to disk and decompiled to see what it is:

public class Test {
    public static void main(String[] args) {
        // Use CGLib's proxy class to write. Class files in memory to local disks
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F://test/");
        Customer customer = (Customer) newCglibMeiRen().getInstance(Customer.class); System.out.println(customer.getClass()); customer.findLove(); }}Copy the code

Re-executing the code, we see three more. Class files in the specified directory, as shown below:

Through the debug tracking found that Customer EnhancerByCGLIBEnhancerByCGLIBEnhancerByCGLIBd1ca6fd2. Class is additional agent generated proxy class, inherited the Customer class.

// Proxy methods (called by methodproxy.invokesuper ())
final void CGLIB$findLove$0()
  {
    super.findLove();
  }
Copy the code

The CGLib agent executes proxy methods more efficiently than the JDK because CGLib uses the FastClass mechanism, which simply states: This class assigns an index (int) to the method of the proxy class or propped class. This index is used as an input parameter. FastClass can directly locate the method to be called and call it directly, eliminating the need for reflection calls. So calls are more efficient than JDK proxies calling through reflection.

CGLib and JDK dynamic proxy comparison

  1. The JDK dynamic proxy implements the proxied object’s interface, and the CGLib proxy inherits the proxied object.
  2. Both the JDK dynamic proxy and the CGLib proxy generate bytecodes at runtime. The JDK dynamic proxy writes Class bytecodes directly, while CGLib uses the ASM framework to write Class bytecodes. The CGLib proxy is more complex and less efficient than the JDK dynamic proxy.
  3. JDK dynamic proxy calls proxy methods through reflection mechanism, CGLib proxy calls methods directly through FastClass mechanism, CGLib proxy execution efficiency.

Examples of proxy patterns in Spring ecology

ProxyFactoryBean getObject()

public Object getObject(a) throws BeansException {
    initializeAdvisorChain();
    if (isSingleton()) {
      return getSingletonInstance();
    }
    else {
      if (this.targetName == null) {
        logger.info("Using non-singleton proxies with singleton targets is often undesirable. " +
            "Enable prototype proxies by setting the 'targetName' property.");
      }
      returnnewPrototypeInstance(); }}Copy the code

In the getObject() method, getSingletonInstance() and newPrototypeInstance() are primarily called. In the Spring configuration, beans generated by the Spring proxy are singleton objects if nothing is done. If you modify scope, create new prototype objects one at a time. The logic in newPrototypeInstance() is a bit complicated, so you can read it yourself.

Spring uses dynamic proxies to implement AOP with two very important classes: JdkDynamicAopProxy and CglibAopProxy. Take a look at the class diagram below.

Proxy selection principles in Spring

  1. Spring leverages JDK dynamic proxies when a Bean has an implementation interface.
  2. When a Bean does not implement an interface, Spring selects the CGLib proxy.
  3. < AOP :aspectj-autoproxy proxy-target-class=”true” />

Advantages and disadvantages of the agency model

Advantages:

  1. The proxy pattern separates the proxy object from the actual target object being called.
  2. To a certain extent, the coupling degree of the system is reduced and the expansibility is good.
  3. It can protect the target object.
  4. You can enhance the function of the target object.

Disadvantages:

  1. The proxy pattern increases the number of classes in the system design.
  2. Adding a proxy object to both the client and the target object slows request processing.
  3. Increased system complexity.

10. Friendship links

Design Patterns – Factory Patterns learning tour

Design Patterns – a learning tour of singleton patterns

Design Patterns – A learning journey of prototyping patterns

Design Patterns – Builder patterns learning tour

Welcome to follow the wechat public account (MarkZoe) to learn from and communicate with each other.