1. Preface
First, let's take a look at Baidu Encyclopedia's introduction to the agency model:
Definition of Proxy Pattern: provide a proxy for other objects to control access to this object. In some cases, one object is not suitable or cannot directly reference another object, and the proxy object can act as an intermediary between the client and the target object.
In fact, it is the intermediary mode or the entrustment mode. In daily life, there are many agency modes, such as asking colleagues to bring rice, filing a lawsuit, etc. The main intention of using proxy mode is to provide a proxy for other objects to control access to this object. The main usage scenarios are: when an object cannot or does not want to be accessed directly, it can be accessed indirectly through a proxy object. In order to ensure the transparency of client use, the delegate object and proxy object need to implement the same interface.
stay Introduction to spring AOP In this paper, two kinds of agent methods have been mentioned, namely, static agent and dynamic agent.
- Static proxy: it is created by programmers or tools to generate the source code of the proxy class, and then compile the proxy class. The so-called static means that the bytecode file of the agent class already exists before the program runs, and the relationship between the agent class and the delegate class is determined before the program runs.
- Dynamic proxy: you don't need to care about the proxy class in the implementation phase, but specify which object in the runtime phase.
Here we continue to review and expand around these two methods.
2. Proxy Pattern
2.1 static proxy mode
For example, the following case:
interface ISubject { void doSomething(); } // Real implementation class class RealSubject_ZhangSan implements ISubject{ @Override public void doSomething() { System.out.println("Buy a mobile phone"); } } class ProxySubject_Shop implements ISubject{ private RealSubject_ZhangSan mSubject; // Who really did it public ProxySubject_Shop(RealSubject_ZhangSan subject){ this.mSubject = subject; } @Override public void doSomething() { // Acting only mSubject.doSomething(); } } public class Client { public static void main(String[] args) { RealSubject_ZhangSan subject = new RealSubject_ZhangSan(); // Construct proxy object ProxySubject_Shop proxySubject = new ProxySubject_Shop(subject); // Method of calling proxy proxySubject.doSomething(); } }
The corresponding class diagram is:
The Client knows what method needs to be called through the interface. Because the real target object cannot be accessed directly in the proxy mode, it needs to be accessed through the proxy object. Therefore, the proxy object needs to be used in the Client. Because the proxy mode specifies the need to ensure the transparency of the Client, both the proxy object and the proxy object need the same interface, that is, the doSomething method here.
It is worth noting that according to the above writing, for each specific agent class, we need to specify the specific Subject class for maintenance (agent). When there are many specific realsubjects, we need to define many agent classes. Of course, this is unreasonable. Therefore, the only interface in the proxy class should be an interface, such as:
class ProxySubject_Shop implements ISubject{ private ISubject mSubject; // The object that really does this is interface oriented programming, not specific implementation oriented public ProxySubject_Shop(ISubject subject){ this.mSubject = subject; } @Override public void doSomething() { // Acting only mSubject.doSomething(); } }
Although the above method does solve the problem of multiple real Subject classes, if the proxy class needs to do some unique operations before or after the original real Subject, it needs to define multiple proxy classes according to the type of proxy class. This is obviously unreasonable. So dynamic proxy is needed.
From the above, we know that using a static proxy is actually specified directly in the source code. On the contrary, dynamic proxy dynamically generates the agent's object through the reflection mechanism, that is, who will decide at the execution stage.
2.2 dynamic agent mode
Reflection, the technology used in dynamic Proxy, provides a convenient dynamic Proxy interface InvocationHandler and related Proxy class Proxy in Java.
- InvocationHandler: call the handler and return a result;
- Proxy: provides static methods for creating dynamic proxy classes and instances, which are used to generate this instance of dynamic proxy;
interface ISubject { void doSomething(); } // Real implementation class class RealSubject_ZhangSan implements ISubject{ @Override public void doSomething() { System.out.println("Buy a mobile phone"); } } public class Client { public static void main(String[] args) { RealSubject_ZhangSan realSubject = new RealSubject_ZhangSan(); // Dynamically construct an agent = = > agent realSubject ISubject subject = (ISubject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), new Class[]{ISubject.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(realSubject, args); } }); // Get Class RealSubject_ZhangSan's implementation interface Class object // new Class[]{ISubject.class} or: RealSubject_ZhangSan.class.getInterfaces() // Method of calling proxy subject.doSomething(); } }
Of course, a dynamic proxy class can be extracted here:
class DynamicProxy<T extends ISubject> implements InvocationHandler { private T mObj; public DynamicProxy(T obj){ this.mObj = obj; } public ISubject getProxy(){ return (ISubject) Proxy.newProxyInstance(mObj.getClass().getClassLoader(), mObj.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(mObj, args); } } public class Client { public static void main(String[] args) { RealSubject_ZhangSan realSubject = new RealSubject_ZhangSan(); // Dynamic agent ISubject subject = new DynamicProxy<RealSubject_ZhangSan>(realSubject).getProxy(); // Method of calling proxy subject.doSomething(); } }
Corresponding class diagram:
By observing the above class diagram, you can intuitively see that there is no direct relationship between the dynamic agent class and the specific implementation class. What needs to be done is to make dynamic determination in the Client proxy class. You can see that this method has lower code coupling. That is to complete the decoupling between the agent and the agent, so that there is no direct coupling relationship between the two. Of course, the method of static proxy class is more in line with the object-oriented principle. There are no regulations on which proxy method to use during development.
3. Proxy mode in Android
In Android, there are ActivityManagerProxy, ActivityManagerService, Retrofit and other proxy modes. Here, take the proxy mode in Retrofit as an example.
3.1 proxy mode in retrofit (no agent)
In order to understand the proxy mode in Retrofit, you need to first introduce relevant dependencies into the project:
// https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit implementation 'com.squareup.retrofit2:retrofit:2.9.0'
For how to use in Use cases of Retrofit This framework has been briefly used in. Here is a brief review:
- Define and specify a specific URL address to construct a Retrofit object;
- Use the Retrofit instance object obtained in the first step to call the create method. Of course, a custom interface is passed in this method. In this interface, the link return data is converted into a Java instance object.
- Similarly, add the request to the request queue and receive the returned result.
The dynamic proxy mode can be seen in the create method in the Retrofit class:
public <T> T create(final Class<T> service) { validateServiceInterface(service); return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() { private final Platform platform = Platform.get(); private final Object[] emptyArgs = new Object[0]; @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } return loadServiceMethod(method).invoke(args != null ? args : emptyArgs); } }); }
For example, the request interface is still like Use cases of Retrofit As defined in the text, namely:
public interface RequestInterface { @GET(value = "/test/1.0/users") Call<List<User>> listUsers(); // retrofit2.Call; @GET(value = "/test/1.0/users/{userid}") Call<User> getUserById(@Path(value = "userid") char userId); @FormUrlEncoded @POST(value = "/test/1.0/users") Call<Void> addUser(@Field(value = "name") String name); }
When constructing the request object in Retrofit, pass in the Class object of this interface in the create method:
RequestInterface request = retrofit.create(RequestInterface.class);
Continuing back to the create method of the Retrofit class, you can find that the generated proxy object is actually the RequestInterface interface. However, it is worth noting that the RequestInterface interface is only a Subject in the proxy mode, and we need to find the real proxy object class here. However, it's a pity that we can't find it here, so we might as well imitate it:
interface ISubject { void doSomething(); } public class Client { public static void main(String[] args) { ISubject subject = (ISubject) Proxy.newProxyInstance(ISubject.class.getClassLoader(), new Class[]{ISubject.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invoke"); return 0; } }); System.out.println("package: \n\t" + subject.getClass().getName()); System.out.println("implemented interface: "); Class<?>[] interfaces = subject.getClass().getInterfaces(); for (Class<?> anInterface : interfaces) { System.out.println("\t"+anInterface.getName()); } System.out.println("fields: "); Field[] declaredFields = subject.getClass().getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println("\t"+declaredField.getName()); } System.out.println("methods: "); Method[] declaredMethods = subject.getClass().getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println("\t"+declaredMethod.getName()); } subject.doSomething(); } }
The output result is:
stay Dynamic proxy for Retrofit2 source code parsing This paper gives the way to obtain the bytecode array of the generated dynamic proxy class and store it as a. Class file, that is:
private static void storageClassFile(ISubject subject){ // subject is the dynamic proxy object generated above String proxyName = subject.getClass().getName() + ".class"; byte[] clazz = ProxyGenerator.generateProxyClass(proxyName, new Class[]{ISubject.class}); try { OutputStream out = new FileOutputStream(proxyName); InputStream is = new ByteArrayInputStream(clazz); byte[] buff = new byte[1024]; int len; while ((len = is.read(buff)) != -1) { out.write(buff, 0, len); } is.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } }
The generated files can be seen in the following directory of the project:
Open directly using IDEA:
public final class class extends Proxy implements ISubject { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public class(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 new UndeclaredThrowableException(var4); } } public final void doSomething() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.example.scan.kaoshi.ISubject").getMethod("doSomething"); 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 new NoClassDefFoundError(var3.getMessage()); } } }
You can see that this class is indeed a class that implements the ISubject interface, and implements the basic hashCode, toString and equals methods. However, because the ProxyGenerator.generateProxyClass method exists in the sun.misc.ProxyGenerator package, which is not supported in Android, it is necessary to continue to look at the source code in Retrofit:
As you can see, first pass in the actual method called through the loadServiceMethod(method), and then map < method, servicemethod <? > > in the loadServiceMethod method Find out whether this method already exists, and return it directly if it exists. Otherwise, the annotation of this method in the declaration interface will be parsed through the ServiceMethod.parseAnnotations(this, method) method. In this method, the annotation will continue to be parsed, and finally the HttpServiceMethod.parseAnnotations method will be used for parsing, Because there are many codes, they will not be posted here. In this method, first get the return type of the method, then use Okhttp3 to encapsulate the request, get all the annotations of the method, and parse the corresponding annotation. Finally, an HttpServiceMethod object will be returned to the create method of Retrofit.java. Our subsequent use is similar to Okhttp, that is, to build the Call object and add the request to the queue.
Therefore, it is not completely a Proxy mode in Retrofit, which is easy to understand, because there is really no RealSubject object in this process, but only interface and dynamic Proxy objects. It's also easy to understand, because in the process of network request, we define RESTful style to define request link and return parameters. In fact, we don't need a real Proxy class to implement the request, but we need to parse its annotation to get the actual meaningful request parameters and return value types, Finally, it can be dynamically added to Okhttp requests and dynamically encapsulated beans.
In this process, the program handles the annotations and completes the parsing of the request API, so that the parameters and return values can be encapsulated when the underlying network request is made using Okhttp. Therefore, in fact, Retrofit is not entirely an agent mode, because there is no (and no) actual agent.
References