Dynamic non-intrusive interception
What is called non-intrusive interception.
In Java to intercept a method call, there are many ways, the most easily and most popular is the dynamic agent.
The dynamic proxy approach is simple to implement, as long as you provide an interface and intercept processing handler and provide an attachment operation in invoke for the method call to be pulled.
Then all of the objects that need to be intercepted are generated by the agent to dynamically intercept the method calls at run time.
In fact, the dynamic agent model from the description also see its helplessness.
1. All the classes that need to be intercepted must implement an interface for the proxy to "make" instances of this class.
2. You must change the invocation mode of the original implementation. That is the original INSTANCE.M (), the call must be changed to PROXY.M ();
3. When you need to intercept a class that has not implemented an interface, you must first force the implementation of the interface and then re-use the proxy to generate the object.
4. The internal environment is not aware of the method invocation.
This dynamic is a little bit strong, in fact, once the code generation can no longer be dynamic. To achieve this interception method, the original class design method is forced to modify (must provide an interface),
Class usage is also forced to be modified (must be generated from the proxy class), which is an intrusive implementation. Simply say to implement this function you have to embed your block in your code
Truncated implementations.
If we use bytecode generators to intercept implementations, we can intercept them in a non-intrusive way. The implementation of this approach is transparent to the application. Programmers simply
You do not have to consider how to provide interception of method calls when the business logic is implemented. Everything is loadclass by the JVM and secretly replacing your class file with the ability to
In the packaging class to intercept. The benefit of this approach is that it does not affect the design and implementation of the class, and the ability to intercept is very powerful and can be obtained when a method call
Internal information such as local variables, exception stacks, and so on.
While the bytecode generator is implemented in a dynamic way at runtime, the dynamic non-intrusive interception I want to say here does not mean that the runtime intercepts this dynamic.
If we simply implement a class that has been replaced by the original class file, use ClassLoader for redefine to intercept when the JVM starts,
This is also an intrusion, either to modify System.classloader from a class of redefine, or to control the invocation of each class as if it were a proxy mode.
Furthermore, if we intercept the methods in one class, once the JVM is started, it will be intercepted throughout the process.
What I'm going to say is that the JVM executes the original class when the JVM starts up, and the JVM can dynamically execute the class that was wrapped in the bytecode generator when I need it.
Then after I debug, diagnose and so on, the JVM can execute the original class immediately, as if no interception has occurred. My word here is not very accurate,
The JVM execution class is the class object that the JVM is linked to at run time, and the JIT compiler executes according to the class cost code.
It says clearly what we are going to achieve, and here we talk about concrete implementations.
The first is the bytecode generator, before we can dynamically generate an in-memory class in the absence of a byte-code generator, we are only able to dynamically compile.
(http://blog.csdn.net/axman/archive/2004/11/04/167002.aspx)
The bytecode generator, however, provides a way to dynamically construct the class in memory. The current mainstream bytecode generator has asm,bcel,serp. Functions are basically the same,
However, ASM is very short and very performance-strong. Is my favorite byte code generator, if you like other bytecode generator, does not affect the description of this article.
This article is not a document that introduces ASM, so it does not detail the relevant content of ASM. But based on the problem to be explained, provide a very small example:
Coder implements a business logic class:
Package org.axman.test; public class TestClass {public void Test () {System.out.println ("I ' m testclass.test ()."), public void Test1 () {SYSTEM.O Ut.println ("I ' m testclass.test1 ()."); } }
This is a very common business logic, yes, we want it normal, for coder, his implementation to be in all normal way to run.
When the class is running as one of the implementations of a project, and at runtime I want to see what happens when test or test1 is called, we are going to implement
Its means of interception:
private static byte[] Getwrappedclass (String classname,string[] methods) {try{string path = Classname.replace ('. ', '/') + ". Class"; Classreader reader = new Classreader (Classloader.getsystemresourceasstream (path)); Classwriter writer = new Classwriter (CLASSWRITER.COMPUTE_MAXS); Classadapter classadapter = new Myclassadapter (writer,methods); Reader.accept (Classadapter, classreader.expand_frames); return Writer.tobytearray (); }catch (Exception e) {System.out.println (">>>>>>"); E.printstacktrace (System.out);} return null; }
The method is to produce a packaged class. Among the Myclassadapter:
Class Myclassadapter extends classadapter{private string[] methods; public Myclassadapter (Classvisitor cv,string[] Methods) {super (CV); this.methods = methods;} public Methodvisitor Visitmethod (final int access, final String name, FINA L string desc, final string signature, final string[] exceptions) {methodvisitor mv = Cv.visitmethod (access, name, DESC, s Ignature, exceptions); if (mv = = NULL | | (Access & (Opcodes.acc_abstract | opcodes.acc_native)) > 0) {return mv;} else{for (int i=0;i<methods.length;i++) {if (Name.equals (Methods[i])) return new Myadviceadapter (MV, access, name, desc, signature, exceptions); }} return MV; } }
It is very simple to return the original method if the Myadviceadapter is called in the specified methods when the new method is generated.
Myadviceadapter is also a callback interface that generates a method by inserting instructions from the Onmethodenter and Onmethodexit methods before and after the original method.
The byte code after the wrapper. Note that this is injected into the generated class file:
@SuppressWarnings ("unused") class Myadviceadapter extends Adviceadapter {private String name; private int access; private String ClassName; Protected Myadviceadapter (methodvisitor mv, int access, string name, string desc,string signature, string[] exceptions) { Super (MV, access, name, DESC); THIS.name = name; this.access = Access; } protected void Onmethodenter () {THIS.MV.VISITFIELDINSN (getstatic, "Ljava/lang/system;", "Out", "ljava/io/ PrintStream; "); THIS.MV.VISITLDCINSN ("before ........."); THIS.MV.VISITMETHODINSN (invokevirtual, "ljava/io/printstream;", "println", "(ljava/lang/string;) V");} protected void onmethodexit (int opcode) {this.mv.visitFieldInsn (getstatic, "Ljava/lang/system;", "Out", "ljava/io/printstream;"); THIS.MV.VISITLDCINSN ("after%d ........."); THIS.MV.VISITMETHODINSN (invokevirtual, "ljava/io/printstream;", "println", "(ljava/lang/string;) V");}}
Using the functionality provided by the bytecode generator, we can also get local variables in the method stack, exception stacks, and so on, which is not done by proxy mode. For detailed features, see the ASM documentation. Especially when the method throws an exception, in order to help the analysis, we need to restore the scene, so in the interceptor to export the parameters of the method runtime, local variables within the method, such as "information at the time" is very meaningful.
How do we get the JVM to execute the new class dynamically when we get to the byte[of the packaged class?
JAVA5 later the JVM provides a javaagent interface, which is the Premain method that is pre-executed before the mail method is executed. The signature of this method is:
public static void Premain (String Agentargs, Instrumentation inst);
One of the instrumentation instances Inst can redefine an original class
When the mybusiness in our project is called by the Main method, Inst can replace the original class with the wrapped class:
public static void Premain (String Agentargs, Instrumentation inst) {try{byte[] buf = Getwrappedclass ("Org.axman.test.Tes Tclass ", New string[]{" test "}); Class<?> clazz = Classloader.getsystemclassloader (). LoadClass ("Org.axman.test.TestClass"); classdefinition[] definitions = new classdefinition[] {new Classdefinition (Clazz, BUF)}; inst.redefineclasses (Definitions); }catch (Exception e) {e.printstacktrace ();}}
When packaging your app, add the following in the MANIFEST.MF file:
Premain-class: The class that contains the Primain method, preferably with main.
Can-redefine-classes:true
Boot-class-path: Packaged jar files such as Agent.jar
This interception process is completely transparent to the developer. We just need to start with a plus
The Java-javaagent:agent.jar option allows you to intercept a method in your application without being fully aware of it
However, this is still not dynamic, because after the JVM is started, all the original business calls to MyBusiness are always replaced with the wrapped code.
So instead of redefine directly in Premain, we pass inst to a thread:
public static void Premain (String Agentargs, final instrumentation inst) {new Redefiner (inst). Start ();
Class Redefiner extends thread{private final instrumentation inst; public Redefiner (Instrumentation inst) {this.inst = in St This.setdaemon (TRUE); public void Run () {//here should be enabled ServerSocket to get the command parameters from the console login. But the example of the test is obtained only from a specified file for simple timing. hashmap<string,byte[]> map = new hashmap<string,byte[]> (); Long prevlastmodified = 0L; String lastcmd = ""; while (true) {try{thread.sleep (1000); File F = new file ("D:/a.txt"); Long lm = F.lastmodified (); if (prevlastmodified = = LM) continue; prevlastmodified = LM; BufferedReader br = new BufferedReader (new FileReader (f)); String line = Br.readline ();//Read Command Br.close () from the file; string[] cols = Line.split (":"); if (Cols.length < 3) continue; String CMD = cols[0]; if (Cmd.equals (lastcmd)) continue; Lastcmd = CMD; String className = cols[1]; String[] methods = Cols[2].split (","); if (Cmd.equals ("STOP")) break;//exits, the permission should be added to verify if (Cmd.equals ("DEBUG")) {if (!map.containskey (ClassName)) {Map.put ( Classname,getoriginclass (ClassName));//Cache original Class} byte[] buf = GETWRappedclass (Classname,methods); If the wrapper class is to be cached, look at it. The cache requires space, does not cache each survival need to compute and temporary space, the frequency of their own according to the call to decide. Class<?> clazz = Classloader.getsystemclassloader (). LoadClass (ClassName); classdefinition[] definitions = new classdefinition[] {new Classdefinition (Clazz, BUF)}; inst.redefineclasses (Definitions); System.out.println ("Redefine to debug ..."); else if (cmd.equals ("RESET")) {byte[] buf = Map.get (className); if (buf = = null) continue; Class<?> clazz = Classloader.getsystemclassloader (). LoadClass (ClassName); classdefinition[] definitions = new classdefinition[] {new Classdefinition (Clazz, BUF)}; inst.redefineclasses (Definitions); System.out.println ("Redefine to reset ..."); Else }catch (Exception e) {}}} private static byte[] Getwrappedclass (String classname,string[] methods) {try{string path = CLA Ssname.replace ('. ', '/') + '. class '; Classreader reader = new Classreader (Classloader.getsystemresourceasstream (path)); Classwriter writer = new Classwriter (CLASSWRITER.COMPUTE_MAXS);Classadapter classadapter = new Myclassadapter (writer,methods); Reader.accept (Classadapter, classreader.expand_frames); return Writer.tobytearray (); }catch (Exception e) {System.out.println (">>>>>>"); E.printstacktrace (System.out);} return null; } private static byte[] Getoriginclass (string className) {try{string path = Classname.replace ('. ', '/') + ". Class"; Classreader reader = new Classreader (Classloader.getsystemresourceasstream (path)); return reader.b; }catch (Exception e) {e.printstacktrace (System.out);} return null; } }
OK, after the JVM is up and running, you can just add classname and methods to the file you want to use to communicate and then redefineclasses it when you need it, and restore the original class when you don't need it. For example, first write in a.txt: XXX:YYY:ZZZ
Such a command so that the daemon does nothing, and the main thread will print
"I ' m testclass.test ()."
"I ' m testclass.test1 ()."
Then change the a.txt content to: DEBUG:org.axman.test.TestClass:test
Will be in "I ' M testclass.test ()." Print out before and after injection information before and after. There is no redefine test1 method at this time.
Then change the contents of A.txt to: Debug:org.axman.test.testclass:test,test1 will see
"I ' m testclass.test ()." and "I ' M testclass.test1 ()." The injected information is printed before and after. And then modify it into
Reset:org.axman.test.testclass:test,test1, the default printing information is restored. The method calls are made exactly as we control them.
Intercept.
This is the real "dynamic non-intrusive interception". Of course remember a real implementation do not use files to communicate.
Detailed interception to achieve the next article.