[Reprinted] Java Dynamic proxy, reprinted java Dynamic
Java Dynamic proxy
This article analyzes the dynamic proxy in public technical points for the open-source Android project source code
Project address: Jave Proxy, analyzed version: openjdk 1.6, Demo address: Proxy Demo
ANALYST: Caij, Proofreader: Trinea, proofreader status: Completed
1. Related Concepts 1.1 proxy
In some cases, we do not want or cannot directly access object A. Instead, we access an intermediary object B and Access Object A by B. In this way, we call it A proxy.
Here, the class to which object A belongs is called the delegate class, also known as the proxy class, and the class to which object B belongs is called the proxy class.
Proxy advantages:
- Hide delegate class implementation
- Decouple and perform some additional processing without changing the delegate code, such as adding initial judgment and other public operations.
Depending on whether the proxy class already exists before running the program, you can divide the proxy into static proxy and dynamic proxy.
1.2 static proxy
The proxy method that already exists before the agent class runs is called static proxy.
The above explanation shows that the methods written by the developer or the compiler to generate the proxy class belong to static proxy. The following is a simple static proxy instance:
class ClassA { public void operateMethod1() {}; public void operateMethod2() {}; public void operateMethod3() {};}public class ClassB { private ClassA a; public ClassB(ClassA a) { this.a = a; } public void operateMethod1() { a.operateMethod1(); }; public void operateMethod2() { a.operateMethod2(); }; // not export operateMethod3()}
AboveClassAYesdelegate class,ClassBIs a proxy class,ClassBAll functions inClassACorresponding function, and hiddenClassOfoperateMethod3()Function.
In static proxies, proxy classes and delegate classes often inherit the same parent class or implement the same interface.
1.3 dynamic proxy
A dynamic proxy is a proxy that does not exist before the program runs and is dynamically generated by the program.
Java provides a dynamic proxy implementation method that can dynamically generate proxy classes at runtime. One of the major advantages of this proxy method is that it can facilitate unified or special processing of proxy functions, if you want to record the execution time of all functions, add verification and judgment before all functions are executed, and perform special operations on a special function, you do not need to modify each function as you do in the static proxy mode.
Static proxyIt is relatively simple. This article has been briefly introduced. The following focuses onDynamic proxy.
2. The dynamic proxy instance includes three steps:
(1). Create a delegate class;
(2). ImplementationInvocationHandlerInterface, which must be implemented by the intermediate class for connecting the proxy class and the delegate class;
(3). PassProxyClass to create a proxy class object.
The following is an example. If we want to calculate the execution time of all functions of a class, the traditional method is to calculate statistics before each function of the class. The dynamic proxy method is as follows:
2.1 create a delegate class
public interface Operate { public void operateMethod1(); public void operateMethod2(); public void operateMethod3();}public class OperateImpl implements Operate { @Override public void operateMethod1() { System.out.println("Invoke operateMethod1"); sleep(110); } @Override public void operateMethod2() { System.out.println("Invoke operateMethod2"); sleep(120); } @Override public void operateMethod3() { System.out.println("Invoke operateMethod3"); sleep(130); } private static void sleep(long millSeconds) { try { Thread.sleep(millSeconds); } catch (InterruptedException e) { e.printStackTrace(); } }}
OperateIt is an interface that determines some functions. We need to calculate the execution time of these functions.
OperateImplIs the delegate class, implementationOperateInterface. Each function outputs a string and waits for a while.
Dynamic proxy requires that the delegate class must implement an interface, for example, the delegate classOperateImplImplementedOperateThe reason will be announced on Weibo later.
2.2. Implement the InvocationHandler Interface
public class TimingInvocationHandler implements InvocationHandler { private Object target; public TimingInvocationHandler() {} public TimingInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); Object obj = method.invoke(target, args); System.out.println(method.getName() + " cost time is:" + (System.currentTimeMillis() - start)); return obj; }}
targetAttribute indicates the delegate object.
InvocationHandlerIs an interface that must be implemented by the intermediate class that connects the proxy class and the delegate class. There is only one
public Object invoke(Object proxy, Method method, Object[] args)
The function needs to be implemented. parameters:
proxyIndicates the following2.3 Proxy class object generated through Proxy. newProxyInstance ().
methodIndicates the function called by the proxy object.
argsThe parameter of the function called by the proxy object.
Every function that calls the proxy object is actually called.InvocationHandlerOfinvokeFunction. Here we areinvokeThe start and end time is added in the implementation, and the delegate class object is also called.targetTo complete the statistical execution time.
invokeIn the function, we can alsomethodMake some judgments to specially process some functions.
2.3. Generate Proxy objects using Proxy static functions
public class Main { public static void main(String[] args) { // create proxy instance TimingInvocationHandler timingInvocationHandler = new TimingInvocationHandler(new OperateImpl()); Operate operate = (Operate)(Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[] {Operate.class}, timingInvocationHandler)); // call method of proxy instance operate.operateMethod1(); System.out.println(); operate.operateMethod2(); System.out.println(); operate.operateMethod3(); }}
Here we will first delegate the Class Objectnew OperateImpl()AsTimingInvocationHandlerCreate a constructor input parametertimingInvocationHandlerObject;
ThenProxy.newProxyInstance(…)A new proxy object is created for the function. The actual proxy class is generated dynamically at this time. The function that calls this proxy object will calltimingInvocationHandlerOfinvokeFunction (is it a bit like a static proxy), andinvokeCall delegate objects in function implementationnew OperateImpl()Corresponding method (is it a bit similar to static proxy ).
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
loaderIndicates the class loader.
interfacesDelegate class interfaces. These interfaces must be implemented when a proxy class is generated.
hYesInvocationHandlerImplementation Class Object, responsible for connecting the proxy class and the intermediate class of the delegate class
As we can understand, the above dynamic proxy implementation is actually A double-layer static proxy. The developer provides the delegate Class B, and the program dynamically generates the proxy Class. Developers also need to provide an implementationInvocationHandlerThe subclass C connects the proxy class A and the delegate Class B. It is the delegate class of the proxy class A and the delegate Class B. The user directly calls the object of proxy class A. A forwards the call to delegate Class C, and delegate Class C forwards the call to its delegate Class B.
3. Dynamic proxy Principle
In fact, the last section has already clarified the real principle of dynamic proxy. Let's take a closer look.
3.1 generated dynamic proxy code
The following is the dynamic proxy class code automatically generated when the above example is running. For how to obtain the generated code, see ProxyUtils. To view the class file, you can use jd-gui.
import com.codekk.java.test.dynamicproxy.Operate;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements Operate{ private static Method m4; private static Method m1; private static Method m5; private static Method m0; private static Method m3; private static Method m2; public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final void operateMethod1() throws { try { h.invoke(this, m4, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final boolean equals(Object paramObject) throws { try { return ((Boolean)h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void operateMethod2() throws { try { h.invoke(this, m5, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() throws { try { return ((Integer)h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void operateMethod3() throws { try { h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() throws { try { return (String)h.invoke(this, m2, null); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m4 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod1", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m5 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod2", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m3 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod3", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } }}
We can see that the dynamically generated proxy class is$ProxyPrefix of the class name, inherited fromProxyAnd implementsProxy.newProxyInstance(…)Class of all interfaces passed in by the second parameter.
If a non-public interface exists in the interface implemented by the proxy class, its package name is the package name of this interface. Otherwisecom.sun.proxy.
TheoperateMethod1(),operateMethod2(),operateMethod3()All functions are directly handed overhProcess,hIn the parent classProxyIs defined
protected InvocationHandler h;
That isProxy.newProxyInstance(…)The third parameter.
SoInvocationHandlerThe subclass C connects the proxy class A and the delegate Class B. It is the delegate class of the proxy class A and the delegate Class B.
3.2. Generation of dynamic proxy principles
The following is an analysis of the Java 1.6 source code. The dynamic proxy class is calledProxy.newProxyInstance(…)Generated when the function is used.
(1). newProxyInstance (...)
The function code is as follows:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{ if (h == null) { throw new NullPointerException(); } /* * Look up or generate the designated proxy class. */ Class cl = getProxyClass(loader, interfaces); /* * Invoke its constructor with the designated invocation handler. */ try { Constructor cons = cl.getConstructor(constructorParams); return (Object) cons.newInstance(new Object[] { h }); } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } catch (IllegalAccessException e) { throw new InternalError(e.toString()); } catch (InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { throw new InternalError(e.toString()); }}
It can be seen that it callsgetProxyClass(loader, interfaces)Obtain the dynamic proxy class, and thenInvocationHandlerCreate a proxy class object as an input parameter of the proxy class constructor.
(2). getProxyClass (...)
The function code and explanation are as follows (the original English comments are omitted ):
/*** Get the proxy Class. If no proxy Class exists, dynamically generate the * @ param loader ClassLoader * @ param interfaces interface to be implemented by the proxy Class * @ return */public static Class <?> GetProxyClass (ClassLoader loader, Class <?>... Interfaces) throws IllegalArgumentException {if (interfaces. length> 65535) {throw new IllegalArgumentException ("interface limit exceeded");} // proxy Class object Class proxyClass = null; /* collect interface names to use as key for proxy class cache */String [] interfaceNames = new String [interfaces. length]; Set interfaceSet = new HashSet (); // for detecting duplicates/*** input parameter interfaces check, contains three parts * (1) Whether In the ClassLoader specified by the input parameter, * (2) indicates whether there are duplicates in Interface * (3) interfaces */for (int I = 0; I <interfaces. length; I ++) {String interfaceName = interfaces [I]. getName (); Class interfaceClass = null; try {interfaceClass = Class. forName (interfaceName, false, loader);} catch (ClassNotFoundException e) {} if (interfaceClass! = Interfaces [I]) {throw new IllegalArgumentException (interfaces [I] + "is not visible from class loader");} if (! InterfaceClass. isInterface () {throw new IllegalArgumentException (interfaceClass. getName () + "is not an interface");} if (interfaceSet. contains (interfaceClass) {throw new IllegalArgumentException ("repeated interface:" + interfaceClass. getName ();} interfaceSet. add (interfaceClass); interfaceNames [I] = interfaceName;} // use the List corresponding to the interface name as the cache key Object key = Arrays. asList (interfaceNames );/* * LoaderToCache is a double-layer Map * the first key is ClassLoader, the second key is the List above, and the value is the weak reference of the proxy class */Map cache; synchronized (loaderToCache) {cache = (Map) loaderToCache. get (loader); if (cache = null) {cache = new HashMap (); loaderToCache. put (loader, cache) ;}/ ** the List corresponding to the above interface name is the key search proxy class. If the result is: * (1) Weak reference, indicates that the proxy class is already in the cache * (2) pendingGenerationMarker object, indicating that the proxy class is being generated and the notification is pending for completion. * (3) null indicates that generation is not in the cache and has not started. Add the tag to the cache and continue generating the proxy class */synchronized (cache) {do {Object value = cache. get (key); if (value instanceof Reference) {proxyClass = (Class) (Reference) value ). get ();} if (proxyClass! = Null) {// proxy class already generated: return it return proxyClass;} else if (value = pendingGenerationMarker) {// proxy class being generated: wait for it try {cache. wait ();} catch (InterruptedException e) {} continue;} else {cache. put (key, pendingGenerationMarker); break ;}} while (true) ;}try {String proxyPkg = null; // package to define proxy class in/** if the interfaces contains non-pub All non-public interfaces must be under the same package. The generated proxy classes will also be under this package */for (int I = 0; I <interfaces. length; I ++) {int flags = interfaces [I]. getModifiers (); if (! Modifier. isPublic (flags) {String name = interfaces [I]. getName (); int n = name. lastIndexOf ('. '); String pkg = (n =-1 )? "": Name. substring (0, n + 1); if (proxyPkg = null) {proxyPkg = pkg;} else if (! Pkg. equals (proxyPkg) {throw new IllegalArgumentException ("non-public interfaces from different packages") ;}} if (proxyPkg = null) {// if no non-public proxy interfaces, proxyPkg = ""; // use the unnamed package} {// get the Class Name of the proxy class, in jdk 1.6, the existing processing for this generated class is missing. Long num; synchronized (nextUniqueNumberLock) {num = nextUniqueNumber ++;} String proxyName = proxyPkg + proxyClassNamePrefix + num; // dynamically generate the byte code of the proxy class // ultimately call sun. misc. proxyGenerator. generateClassFile () to get the proxy class information. Write it to DataOutputStream to implement byte [] proxyClassFile = ProxyGenerator. generateProxyClass (proxyName, interfaces); try {// native layer implementation. The virtual machine loads the proxy class and returns its Class Object proxyClass = defineClass0 (loader, proxyName, pro XyClassFile, 0, proxyClassFile. length);} catch (ClassFormatError e) {throw new IllegalArgumentException (e. toString () ;}}// add to set of all generated proxy classes, for isProxyClass proxyClasses. put (proxyClass, null);} finally {// if the proxy class is successfully generated, it is saved to the cache. Otherwise, it is deleted from the cache and the waiting call synchronized (cache) {if (proxyClass! = Null) {cache. put (key, new WeakReference (proxyClass) ;}else {cache. remove (key) ;}cache. policyall () ;}return proxyClass ;}
The function consists of three parts:
- Input parameter interfaces check, including whether it is in the ClassLoader specified by the input parameter, whether it is Interface, and whether there are duplicates in interfaces
- Use the List corresponding to the interface name as the key to find the proxy class. If the result is:
- Weak reference indicates that the proxy class is already in the cache;
- PendingGenerationMarker object, which indicates that the proxy class is being generated and will be returned after generation is complete;
- Null indicates that the agent class is not in the cache and is not generated. Add the tag to the cache to continue generating the proxy class.
- If the proxy class does not exist
ProxyGenerator.generateProxyClass(…)Generate a proxy class and store it in the cache. The notification is waiting for the cache.
Notes in the function:
- The cache key of the proxy class is the List corresponding to the interface name. Different interfaces indicate different keys, that is, different proxy classes.
- If interfaces has non-public interfaces, all non-public interfaces must be under the same package, and the agent classes generated later will be under this package.
- If the proxy class already exists in ClassLoader, It is not processed.
- You can enable System Properties
sun.misc.ProxyGenerator.saveGeneratedFilesSwitch to save the dynamic class to the destination address.
The implementation of Java 1.7 is slightly different.getProxyClass0(…)Function implementation: calls the proxy class cache in the implementation to determine whether the proxy class already exists in the cache and returns directly if it does not exist.proxyClassCacheOfvalueFactoryDynamically generate attributes,valueFactoryOfapplyFunction and the precedinggetProxyClass(…)The function logic is similar.
4. Application Scenario 4.1 AOP (Aspect-Oriented Programming) features of Spring in J2EE Web Development
Role: decoupling between target functions.
For example, in Dao, you must enable transactions for each database operation, and pay attention to permissions during operations. The general syntax is to add the corresponding logic to each function of Dao, resulting in code redundancy and high coupling.
The pseudocode before using dynamic proxy is as follows:
Dao {insert () {checks whether you have the permission to save; enables transactions; inserts; commits transactions;} delete () {checks whether you have the permission to delete transactions; opens transactions; deletes; commit transactions ;}}
The pseudocode for using dynamic proxy is as follows:
// Use dynamic proxy to combine functions of each plane, and each plane only needs to focus on its own logic to reduce code and loosely coupled invoke (Object proxy, Method method, object [] args) throws Throwable {checks whether there is permission; starts transactions; Object ob = method. invoke (dao, args); Commit transaction; return ob ;}
4.2 REST-based Android Network request framework pull fit
Purpose: simplify network request operations.
In general, we need to call each network request once.HttpURLConnectionOrHttpClientIf you make a request or drop the request into the waiting queue like Volley, Retrofit greatly simplifies these operations. The sample code is as follows:
public interface GitHubService { @GET("/users/{user}/repos") List<Repo> listRepos(@Path("user") String user);}RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("https://api.github.com") .build();GitHubService service = restAdapter.create(GitHubService.class);
In the future, we only need to directly call
List<Repo> repos = service.listRepos("octocat");
To start network requests,RetrofitThe principle is based on dynamic proxy. It uses the annotation principle at the same time. This article will not go into details. Please wait until the tracing fit source code parsing is complete.