This is the third day of my participation in Gwen Challenge

Like attention, no more lost, your support means a lot to me!

🔥 Hi, I’m Chouchou. GitHub · Android-Notebook has been included in this article. Welcome to grow up with Chouchou Peng. (Contact information at GitHub)

preface

  • Proxy Pattern, also known as Delegate Pattern, is a structural design Pattern and a basic design skill.
  • Dynamic proxies have many interesting application scenarios, such as AOP, logging frameworks, global exception handling, transaction processing, and so on. In this article, we’ll focus on the basics of JDK dynamic proxies.

directory


Front knowledge

The content of this article will involve the following pre/related knowledge, dear I have prepared for you, please enjoy ~

  • Java reflection mechanism (including Kotlin)
  • That’s all you can ask about Java generics (including Kotlin)

1. An overview of the

  • What is a proxy (pattern)?Proxy Pattern, also called Deletage Pattern, belongs to structural design Pattern and is also a basic design skill. In general, the proxy mode is used to deal with two kinds of problems:
    • 1. Control access to underlying objects
    • 2. Add additional functionality when accessing the underlying objects

These are two very naive scenarios, and because of this, we often see proxy patterns in other design patterns. The UML class diagram and sequence diagram are as follows:

  • The basic classification of proxy: static proxy + dynamic proxy, the classification criteria is “whether the proxy relationship is determined at compile time;

  • Dynamic proxy implementation: JDK, CGLIB, Javassist, ASM


2. Static proxy

2.1 Definition of static proxy

A static proxy is a proxy schema whose proxy relationship is determined at compile time. When using static proxies, it is common practice to abstract an interface for each business class and create a proxy class accordingly. For example, you need to add log printing to network requests:

Public interface HttpApi {String get(String URL); } public class RealModule implements HttpApi {@override public String get(String URL) {return "result"; }} public class Proxy implements HttpApi {private HttpApi target; Proxy(HttpApi target) { this.target = target; } @override public String get(String url) {log. I ("http-statistic", url); // Access the underlying object return target.get(url); }}Copy the code

2.2 Disadvantages of static proxies

  • 1. Repeatability: The more businesses or methods that need to be brokered, the more duplicate template code;
  • 2. Vulnerability: Once the base interface is changed, the proxy class also needs to be changed synchronously (because the proxy class also implements the base interface).

Dynamic proxy

3.1 Definition of dynamic proxy

Dynamic proxy refers to the proxy mode that the proxy relationship determines at run time. It is important to note that JDK dynamic proxies are not equivalent to dynamic proxies. The former is only one implementation of dynamic proxies. Other implementations include CGLIB dynamic proxies, Javassist dynamic proxies, and ASM dynamic proxies. Because the proxy class does not exist before compilation, the proxy is not determined until runtime, and is called a dynamic proxy.

3.2 JDK dynamic Proxy example

Today we will focus on the JDK Dynamic Proxy API (Dymanic Proxy API), a feature introduced in JDK1.3. The core API is the Proxy class and the InvocationHandler interface. It works by using reflection to generate bytecode of proxy classes at run time.

Continuing with the print log example, when using dynamic proxies:

public class ProxyFactory { public static HttpApi getProxy(HttpApi target) { return (HttpApi) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogHandler(target)); } private static class LogHandler implements InvocationHandler { private HttpApi target; LogHandler(HttpApi target) { this.target = target; } // if the underlying method has no parameters, @override public Object invoke(Object proxy, Method Method, @nullable Object[] args) throws Throwable {// Extended function log. I ("http-statistic", (String) args[0]); Return method.invoke(target, args); }}}Copy the code

If you need to accommodate multiple business interfaces, you can use generics:

public class ProxyFactory { @SuppressWarnings("unchecked") public static <T> T getProxy(T target) { return (T) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogHandler(target)); } private static class string <T> implements InvocationHandler {//Copy the code

Client call:

HttpAPi proxy = ProxyFactory.getProxy<HttpApi>(target);
OtherHttpApi proxy = ProxyFactory.getProxy<OtherHttpApi>(otherTarget);
Copy the code

By passing different types with generic parameters, clients can instantiate different types of proxy objects on demand. All methods of the base interface are unified to InvocationHandler# Invoke () processing. Two disadvantages of static proxies are addressed:

  • 1. Repeatability: There is no need to write too much repetitive template code even if there are multiple underlying businesses that require an agent;
  • 2. Vulnerability: Synchronous change agents are not necessary when the underlying interface changes.

3.3 Comparison between Static proxy and Dynamic Proxy

  • Common ground: The two proxy modes implement access control and extension on the base object without changing the base object, which conforms to the open and closed principle.
  • Differences: Static proxies suffer from repeatability and vulnerability; Dynamic proxy (with generic parameters) allows one proxy to handle N basic interfaces at the same time, avoiding the disadvantages of static proxy to some extent. In principle, the Class file of the static proxy Class is generated at compile time, while the Class file of the dynamic proxy Class is generated at run time. The proxy Class does not exist in the coding phase, and the proxy relationship is not determined until run time.

4. JDK dynamic agent source analysis

In this section, we will analyze the source code of JDK dynamic Proxy. The core class is Proxy, mainly analyzing how Proxy generates Proxy classes, and how to distribute method calls to the InvocationHandler interface.

4.1 summary of the API

The Proxy class mainly includes the following apis:

Proxy describe
getProxyClass(ClassLoader, Class…) : Class Gets the proxy Class object that implements the target interface
newProxyInstance(ClassLoader,Class<? >[],InvocationHandler) : Object Gets the proxy object that implements the target interface
isProxyClass(Class<? >) : boolean Determines whether a Class object is a proxy Class
getInvocationHandler(Object) : InvocationHandler Gets the InvocationHandler inside the proxy object

4.2 Core Source Code

Proxy.java

Public static Class<? > getProxyClass(ClassLoader loader,Class<? >... interfaces){ final Class<? >[] intfs = interfaces.clone(); . Return getProxyClass0(loader, intFS); } public static Object newProxyInstance(ClassLoader,Class<? >[] interfaces,InvocationHandler h){ ... final Class<? >[] intfs = interfaces.clone(); Class<? > cl = getProxyClass0(loader, intfs); . // Private static final Class<? >[] constructorParams = { InvocationHandler.class }; final Constructor<? > cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; . Return newInstance(cons, ih); }Copy the code

As you can see, instantiating the proxy object also requires getProxyClass0(…). Get the proxy Class object, newProxyInstance(…) The constructor with an argument of InvocationHandler is then taken to instantiate a proxy-class object.

Let’s first look at how the proxy Class object is acquired:

Proxy.java

Private static Class<? > getProxyClass0(ClassLoader loader,Class<? >... interfaces) { ... Return proxyClassCache. Get (loader, interfaces) return proxyClassCache. } private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<? >[], Class<? >>{3.1 Private static Final String proxyClassNamePrefix = "$Proxy"; Private static Final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); For (Class<? > intf : // interfaces are of the same class as those in ClassLoder. // Interfaces are of the interface type. // Interfaces have no duplicates Public static final String PROXY_PACKAGE = // Public static final String PROXY_PACKAGE = "com.sun.proxy"; String proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; The proxy class 3.5 the fully qualified name of long num = nextUniqueNumber. GetAndIncrement (); String proxyName = proxyPkg + proxyClassNamePrefix + num; 3.6 generate bytecode data byte [] proxyClassFile = ProxyGenerator. GenerateProxyClass (proxyName, interfaces); 3.7 from the bytecode generation Class object return defineClass0 (loader, proxyName, proxyClassFile, 0, proxyClassFile length); Public static byte[] generateProxyClass(final String var0, Class[] var1) { ProxyGenerator var2 = new ProxyGenerator(var0, var1); . final byte[] var3 = var2.generateClassFile(); return var3; }Copy the code

ProxyGenerator.java

Private byte[] generateClassFile() {3.6.1 delegate Object hashCode, equals and toString this.addProxyMethod(hashCodeMethod, Object.class); this.addProxyMethod(equalsMethod, Object.class); this.addProxyMethod(toStringMethod, Object.class); 3.6.2 Each method of the proxy interface... for(var1 = 0; var1 < this.interfaces.length; ++var1) { ... } 3.6.3 Add constructor this.methods.add(this.generateconstructor ()) with InvocationHandler; var7 = this.proxyMethods.values().iterator(); while(var7.hasNext()) { ... ByteArrayOutputStream var9 = new ByteArrayOutputStream(); var9 = new ByteArrayOutputStream(); DataOutputStream var10 = new DataOutputStream(var9); . return var9.toByteArray(); }Copy the code

The code above is very simplified and focuses on the core flow: the JDK dynamic proxy generates a proxy class named com.sun.proxy$proxy [number starting from 0] (for example: Com.sun.proxy $Proxy0), this class inherits from java.lang.reflect.proxy. It also has an internal constructor with an InvocationHandler argument, and method calls to the proxy interface are distributed to InvocationHandler# Invoke ().

Note the red arrows in the UML class diagram, indicating that the proxy relationship between the proxy class and the HttpApi interface is determined at runtime:

Note: The steps for generating bytecodes and Class objects from bytecodes in Android are native methods:

  • private static native Class
    generateProxy (…).
  • The corresponding native method is dalvik/ VM /native/ java_lang_reflect_proxy.cpp

4.3 Viewing the source code of the agent Class

As you can see, ProxyGenerator#generateProxyClass() is actually a static public method, so we call it directly and write the byte stream of the proxy Class to disk file, using IntelliJ IDEA’s decomcompilability to view the source code.

Output bytecode:

byte[] classFile = ProxyGenerator.generateProxyClass("$proxy0",new Class[]{HttpApi.class}); / / write directly under the project path, easy to use IntelliJ IDEA decompiled function String path = "/ Users/pengxurui IdeaProjects/untitled/SRC/proxy/HttpApi class"; try(FileOutputStream fos = new FileOutputStream(path)){ fos.write(classFile); fos.flush(); System.out.println("success"); } catch (Exception e){ e.printStackTrace(); System.out.println("fail"); }Copy the code

Decompilation result:

Public final class $proxy0 extends Proxy implements HttpApi {public final class $proxy0 extends Proxy implements HttpApi { private static Method m2; private static Method m3; private static Method m0; public $proxy0(InvocationHandler var1) throws { super(var1); } /** * Object#hashCode() * Object#equals(Object) * Object#toString() Throws {try {// Forward to Invocation#invoke() return (String)super.h.i Nvoke (this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { //Object#hashCode() //Object#equals(Object) //Object#toString() m3 = Class.forName("HttpApi").getMethod("get"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); }}}Copy the code

4.4 Common Misunderstandings

  • The underlying object must implement the underlying interface, or dynamic proxies cannot be used

This idea may come from classes that don’t implement any interface, so there’s no way to get the interface’s Class object as a parameter to Proxy#newProxyInstance(). This does cause some trouble, for example:

package com.domain; public interface HttpApi { String get(); } // The other package's non-public interface package com.domain.inner; /**non-public**/interface OtherHttpApi{ String get(); } package com.domain.inner; Public class OtherHttpApiImpl /**extends OtherHttpApi**/{public String get() { return "result"; } } // Client: HttpApi api = (HttpApi) Proxy.newProxyInstance(... }, new InvocationHandler() { OtherHttpApiImpl impl = new OtherHttpApiImpl(); @Override public Object invoke(Object proxy, Method method, Object [] args) throws Throwable {/ / TODO: extension of new feature / / IllegalArgumentException: object is not an instance of declaring class return method.invoke(impl,args); }}); api.get();Copy the code

In this example, the OtherHttpApiImpl class did not implement the HttpApi interface for historical reasons. Although the method signature is exactly the same as the HttpApi interface method signature, the proxy cannot be completed. Alternatively, find the Method with the same signature from the HttpApi interface and use this Method to forward the call. Such as:

HttpApi api = (HttpApi) Proxy.newProxyInstance(... }, new InvocationHandler() { OtherHttpApiImpl impl = new OtherHttpApiImpl(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// TODO: extension new functionality if (method.getDeclaringClass()! RealMethod = impl.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes()); return realMethod.invoke(impl, args); }else{ return method.invoke(impl,args); }}});Copy the code

5. To summarize

Today, we discussed the static and dynamic agents two proxy mode, the static agent in design patterns can be seen everywhere, but there are repeatability and the fragility of the faults, at runtime to determine the dynamic proxy agent relationship, can achieve a proxy processing N based interface, to some extent avoid the shortcoming of the static agent. We are familiar with a network request framework that makes full use of dynamic proxy features. Do you know which framework we are talking about?


The resources

  • Android source code design mode analysis and actual combat — He Honghui, Aixinmin
  • Develop Java development techniques: Experience the beauty of design patterns and algorithms in architecture — in a wide range
  • An in-depth understanding of Android kernel design by Lin Xuesen
  • Wikipedia:Aspect-oriented programming
  • Explore the Dynamic Proxy API by Jeremy Blosser
  • Generically Chain Dynamic Proxies — By Srijeeb Roy
  • Dynamic proxies are surprisingly simple! – the cxuan

Creation is not easy, your “three lian” is chouchou’s biggest motivation, we will see you next time!