Java Dynamic Agent
This article is for Android Open source project source code to parse the dynamic agent part of public technical point
Project Address: Jave Proxy, analyzed version: OpenJDK 1.6,demo address: Proxy Demo
Analyst: Caij, Reviewer: trinea, proofreading Status: complete
1. Related Concepts 1.1 agents
In some cases, we do not want or can not directly access object A, but by accessing a mediation object B, B to access a to achieve the purpose, this way we are called proxies.
Here object A belongs to the class that we call the delegate class, also known as the proxy class, and object B belongs to the class called the proxy class.
The advantages of the agent are:
- To hide the implementation of a delegate class
- Decoupling, do some extra processing without changing the delegate class code, such as adding initial judgment and other public operations
Depending on whether the agent class already exists before the program runs, you can divide the agent into static agents and dynamic proxies.
1.2 Static proxies
Proxy classes that exist before a program is run are called static proxies.
As you can see from the above explanation, the way that the developer writes or the compiler generates the proxy class belongs to the static proxy, and 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()}
The above ClassA
is the delegate class, ClassB
is the proxy class, ClassB
the function is directly called the ClassA
corresponding function, and Class
hidden operateMethod3()
functions.
Proxy classes and delegate classes in static proxies also often inherit the same parent class or implement the same interface.
1.3 Dynamic Agents
The proxy class does not exist before the program runs, and the proxy mode that is dynamically generated by the program is called a dynamic proxy.
Java provides a way for dynamic proxies to dynamically generate proxy classes at run time. One of the great benefits of this proxy approach is that it makes it easy to do uniform or special handling of the functions of the proxy class, such as recording all function execution times, adding validation judgments before all functions are executed, and special operations on a particular function rather than modifying each function as a static proxy.
静态代理
Relatively simple, this article has been briefly introduced, the following emphasis is introduced 动态代理
.
2. Dynamic proxy instance Implementation Dynamic Agent includes three steps:
(1). New delegate class;
(2). Implement InvocationHandler
interfaces, which are the interfaces that the intermediate classes that are responsible for connecting proxy classes and delegate classes must implement;
(3). Proxy
Create a new proxy class object from the class.
The following is a concrete introduction to the example, if now we want to count the execution time of all functions of a class, the traditional way is in front of each function of the class statistics, dynamic proxy mode is as follows:
2.1 New 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 (' in Voke 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 (); } }}
Operate
is an interface that has certain functions, and we want to count the execution time of these functions.
OperateImpl
is the delegate class that implements the Operate
interface. Each function simply outputs a string and waits for some time.
The dynamic proxy requires that the delegate class implement an interface, for example, where the delegate class is OperateImpl
implemented Operate
, and the reason is later published on Weibo.
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; }}
target
property represents a delegate class object.
InvocationHandler
is the interface that the intermediate classes that are responsible for connecting the proxy class and the delegate class must implement. Only one of them
public Object invoke(Object proxy, Method method, Object[] args)
Functions need to be implemented, parameters:
proxy
Represents the following 2.3 通过 Proxy.newProxyInstance() 生成的代理类对象
.
method
Represents the function that the proxy object is called.
args
Represents the parameters of a function called by a proxy object.
Each function that invokes the proxy object actually ultimately is the function that was called. InvocationHandler
invoke
Here we invoke
add the start-end timing in the implementation, which also invokes the corresponding function of the delegate class object target
, thus completing the statistical execution time requirement.
invoke
function, we can also method
make some judgments, so that some of the functions of special processing.
2.3. Generating proxy objects through proxy class static functions
public class Main {public static void Main (string[] args) {//Create proxy Instan Ce 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 first new OperateImpl()
create the object as a TimingInvocationHandler
constructor in the delegate class object timingInvocationHandler
;
Then Proxy.newProxyInstance(…)
a proxy object is created by the function, and the actual proxy class is generated dynamically at this time. The function that we call the proxy object is called timingInvocationHandler
invoke
(not a bit like a static proxy), whereas the function invoke
implementation calls the corresponding method of the delegate class object new OperateImpl()
(not a bit like a static proxy).
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
loader
Represents the class loader
interfaces
Represents the interface of the delegate class, which is required to implement when generating the proxy class
h
is the InvocationHandler
implementation class object that is responsible for connecting the intermediate classes of the proxy class and the delegate class
We can understand that, as on the dynamic proxy implementation is actually a two-tier static proxy, the developer provides the delegate class B, the program dynamically generated proxy class A. The developer also needs to provide an implemented InvocationHandler
subclass C, a subclass C connection proxy Class A and a delegate class B, which is the delegate class of the proxy class A, delegate Class B proxy class. The user calls the object of the proxy class A directly, a forwards the call to the delegate Class C, and the delegate Class C forwards the call to its delegate class B.
3. Dynamic Agent Principle
In fact, the last paragraph has already cleared up the real principle of dynamic agent. Let's analyze it carefully.
3.1 Generated dynamic proxy class code
Here is the dynamic proxy class code generated automatically when the sample program is run, and how to get the generated code see Proxyutils, view class file can be used 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 $Proxy 0 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 $Proxy 0 (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, n EW 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 (LOCALNOSUCHMETHODEXCEP Tion.getmessage ()); } catch (ClassNotFoundException localclassnotfoundexception) {throw new Noclassdeffounderror (Localclassnotfoun Dexception.getmessage ()); } }}
From this we can see that the dynamically generated proxy class is a $Proxy
class that inherits from the prefix of the class name and Proxy
implements all the Proxy.newProxyInstance(…)
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 the interface, otherwise com.sun.proxy
.
In which, the operateMethod1()
operateMethod2()
operateMethod3()
function is given directly h
to the processing, h
defined in the parent class Proxy
as
protected InvocationHandler h;
Is the Proxy.newProxyInstance(…)
third parameter.
So InvocationHandler
the subclass C joins the proxy class A and delegate Class B, which is the delegate class of proxy Class A, which delegates Class B's proxy class.
3.2. How to generate dynamic proxy classes
The following is for Java 1.6 source code analysis, dynamic proxy classes are Proxy.newProxyInstance(…)
generated when the function is called.
(1). Newproxyinstance (...)
The function code is as follows:
public static Object newproxyinstance (ClassLoader loader, Cla Ss<?>[] 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 calls the getProxyClass(loader, interfaces)
dynamic proxy class first, and then InvocationHandler
acts as a proxy class constructor into the new proxy class object.
(2). Getproxyclass (...)
The function code and explanation are as follows (omit the original English comment):
/** * Gets the proxy class, does not exist dynamically generated * @param loader proxy class belongs to ClassLoader * @param interfaces proxy class needs to implement Interface * @return */public static class< > Getproxyclass (ClassLoader loader, class<?> .... interfaces) throws illegal argumentexception{if (Interfaces.length > 65535) {throw new IllegalArgumentException ("Interface limit Excee Ded "); }//proxy class object class proxyclass = null; /* Collect interface names to use as key for proxy class cache */string[] Interfacenames = new String[interfaces.lengt h]; Set interfaceset = new HashSet (); For detecting Duplicates/** * Interfaces test, contains three parts * (1) Whether in the ClassLoader specified in the entry parameter * (2) is Interface * (3) whether there are duplicates in interfaces */for (int i = 0; i < interfaces.length; i++) {String InterfaceName = interface S[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 no visible from class loader"); } if (!interfaceclass.isinterface ()) {throw new IllegalArgumentException (Interfaceclass . GetName () + "isn't an interface"); } if (Interfaceset.contains (Interfaceclass)) {throw new IllegalArgumentException ("Repea Ted Interface: "+ interfaceclass.getname ()); } interfaceset.add (Interfaceclass); Interfacenames[i] = InterfaceName; }//The List that corresponds to the interface name as the cached key Object key = Arrays.aslist (Interfacenames); /* * Loadertocache is a two-tier MAP * The first layer key is ClassLoader, the second key is the weak reference of the List,value as the proxy class */map cache; Synchronized (loadertocache) {cache = (MAP) loadertocache.get (loader); if (cache = = null) {cache = new HashMap (); Loadertocache.put (LoadeR, Cache); }/* * To find the proxy class with the List of the interface name above, if the result is: * (1) Weak reference, indicating that the proxy class is already in the cache * (2) Pendinggenerationmarker object, representing the proxy class In the build, wait for the build to complete the notification. * (3) NULL means not in cache and does not start build, add tag to cache, continue to generate proxy class */synchronized (cache) {do {Object value = cache.ge T (key); if (value instanceof Reference) {Proxyclass = (Class) ((Reference) value). get (); } if (Proxyclass! = null) {//proxy class already Generated:return it return PR Oxyclass; } else if (value = = Pendinggenerationmarker) {//proxy class being generated:wait for it T ry {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 there are non-public interfaces in the interfaces, all non-public interfaces must be under the same package, and the subsequent generated proxy classes will also be under the 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-pub LIC interfaces from different packages "); }}} if (proxypkg = = null) {//If no Non-public proxy interfaces, proxypkg = ""; Use the unnamed package} {//Gets the class name of the proxy class, which is missing from the JDK 1.6 version of the processing that already exists for this generated class. Long num; Synchronized (nextuniquEnumberlock) {num = nextuniquenumber++; } String proxyname = proxypkg + proxyclassnameprefix + num; Dynamically generated proxy class byte code//Final Call Sun.misc.ProxyGenerator.generateClassFile () Get proxy class related information written to DataOutputStream implementation B yte[] Proxyclassfile = Proxygenerator.generateproxyclass (proxyname, interfaces); The try {//Native layer implementation, the virtual machine loads the proxy class and returns its class object proxyclass = DefineClass0 (loader, Proxyname, Proxyclassfile, 0, proxyclassfile.length); } catch (Classformaterror e) {throw new IllegalArgumentException (e.tostring ()); }}//Add to set of all generated proxies classes, for Isproxyclass proxyclasses.put (proxyclass, NULL) ; The finally {///proxy class is successfully saved to the cache, otherwise it is removed from the cache and then notifies the waiting call synchronized (cache) {if (Proxyclass! = null) { Cache.put (Key, New WeakReference (Proxyclass)); } else { Cache.remove (key); } cache.notifyall (); }} return proxyclass;}
The function mainly consists of three parts:
- Interfaces test, including whether there are duplicates within the ClassLoader specified in the entry parameter, whether it is Interface, interfaces
- The List for the interface name is the key to find the proxy class, if the result is:
- A weak reference that indicates that the proxy class is already in the cache;
- A Pendinggenerationmarker object that indicates that the proxy class is in the build, waiting for the build to finish returning;
- Null means not in the cache and does not start the build, add tags to the cache, and continue to generate the proxy class.
- If the proxy class does not exist, call the
ProxyGenerator.generateProxyClass(…)
generate proxy class and cache it, notifying the cache of the wait.
Some of the points of note in the function:
- The proxy class's cache key is the interface name corresponding to the List, the interface order of different means different key is a different proxy class.
- If there is a non-public interface in interfaces, all non-public interfaces must be under the same package, and subsequent generated proxy classes will also be under the package.
- The proxy class is not processed if it already exists in the ClassLoader.
- You can turn on the System Properties
sun.misc.ProxyGenerator.saveGeneratedFiles
switch to save the dynamic class to the destination address.
Java 1.7 Implementation is slightly different, through the getProxyClass0(…)
function implementation, the implementation of the invocation of the proxy class cache, to determine whether the proxy class in the cache already exists, there is a direct return, there is no call proxyClassCache
to the properties of the valueFactory
dynamic generation, valueFactory
the apply
function getProxyClass(…)
of the above function logic is similar.
4. Use Scenario 4.1 The AOP (aspect-oriented programming) feature of Spring in Java EE Web development
Function: decoupling between target functions.
In Dao, for example, every database operation needs to open a transaction, and you need to pay attention to the permissions when you operate. The general notation is to add logic to each of the Dao's functions, resulting in code redundancy and a high degree of coupling.
The pre-pseudo code for using dynamic proxies is as follows:
Dao { insert() { 判断是否有保存的权限; 开启事务; 插入; 提交事务; } delete() { 判断是否有删除的权限; 开启事务; 删除; 提交事务; }}
The pseudo-code using dynamic proxies is as follows:
// 使用动态代理,组合每个切面的函数,而每个切面只需要关注自己的逻辑就行,达到减少代码,松耦合的效果invoke(Object proxy, Method method, Object[] args) throws Throwable { 判断是否有权限; 开启事务; Object ob = method.invoke(dao, args); 提交事务; return ob; }
4.2 REST-based Android network request Framework Retrofit
Function: Simplifies network request operations.
In general, each network request we need to call once HttpURLConnection
or HttpClient
make a request, or like volley into the waiting queue, 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 just need to call directly
List<Repo> repos = service.listRepos("octocat");
Can start the network request, Retrofit
the principle is based on dynamic agent, it also uses the principle of annotations, this article does not do in-depth introduction, specific Please wait for Retrofit source parsing completed.
"Reprint" Java dynamic agent