Uh, Java bytecode. We have already discussed this in the Understanding Java bytecode article, but continue to deepen the memory: Java bytecode is the binary representation of the source code, the JVM can read and execute bytecode.
Bytecode libraries are now widely used in Java, especially when run-time dynamic proxy generation is commonly used in Java EE. Bytecode conversions are also common use cases, such as support for AOP runtime weaving facets, or extensible class overloading techniques provided by tools such as Jrebel. In the area of code quality, you often use libraries to parse and parse bytecode.
If you are converting class bytecode, there are many bytecode libraries to choose from, the most common of which are asm,javassist and bcel. This article will briefly introduce ASM and Jitescript,jitescript-based ASM, which provides a smoother API for the generation of classes.
is ASM an abbreviation for "awesome"?
Well, maybe not. ASM is a Java API Class library provided by ObjectWeb Consortium for parsing, modifying, and generating JVM bytecode. It is widely used, often as the fastest solution for manipulating bytecode. The Oracle JDK8 partial base lambda implementation also uses the ASM class library, which shows the breadth of ASM's usefulness.
Many other frameworks and tools also leverage the capabilities of the ASM class library, including many JVM language implementations, such as Jruby,jython and Clojure. You can see ASM as a bytecode library is a good choice!
The ASM visitor Pattern
The overall architecture of the ASM Class Library uses the visitor pattern. When ASM reads and writes the section code, it uses the visitor pattern to access the various parts of the class file bytecode sequentially.
Parsing a class's bytecode is also simple, implementing a visitor for the part you are interested in, and then using Cla***eader to parse the byte array that contains the bytecode.
Similarly, use Classwriter to generate a byte code for a class, then access all the data in the class, and then call Tobytearray () to convert it to a byte array that contains the bytecode.
Modify--or convert--bytecode becomes the art of combining them, Cla***eader visits Classwriter, and uses other visitors to add/modify/delete different sections.
When using the API directly, you still need to have a level of overall understanding of the class file format, the available bytecode operations, and the stack mechanism. Some of the things that are hidden behind Java sources by the compiler are now yours to implement, such as explicitly calling the parent constructor in the constructor, and if you are instantiating the class, make sure that it must have a constructor, and that the bytecode of the constructor is represented as a method named "".
To implement a simple HelloWorld class for the Runnable interface, call the Run () method System.out the string "Hello world!", using the ASM API to generate the following:
Classwriter cw = new Classwriter (classwriter.compute_frames);
Cw.visit (V1_5, Acc_public, "HelloWorld", NULL,
Type.getinternalname (Object.class),
New string[] {type.getinternalname (Runnable.class)});
Methodvisitor CONSMV = Cw.visitmethod (Acc_public, "", "() V", null,null);
Consmv.visitcode ();
CONSMV.VISITVARINSN (aload, 0);
CONSMV.VISITMETHODINSN (Invokespecial,
Type.getinternalname (Object.class), "", "() V", false);
CONSMV.VISITINSN (RETURN);
CONSMV.VISITMAXS (1, 1);
Consmv.visitend ();
Methodvisitor RUNMV = Cw.visitmethod (Acc_public, "Run", "() V", NULL, NULL);
RUNMV.VISITFIELDINSN (Getstatic, Type.getinternalname (System.class),
"Out", Type.getdescriptor (Printstream.class));
RUNMV.VISITLDCINSN ("Hello asm!");
RUNMV.VISITMETHODINSN (Invokevirtual,
Type.getinternalname (Printstream.class), "println",
Type.getmethoddescriptor (Type.GetType (Void.class),
Type.GetType (String.class)), false);
RUNMV.VISITINSN (RETURN);
RUNMV.VISITMAXS (2, 1);
Runmv.visitend ();
As you can see from the code above, to use the default visitor mode method of the ASM API, it is possible to correctly invoke the requirements to understand the categories of the respective opcode. The opposite approach is to use generatoradapter when generating the method, which provides a named approach to exposing most of the opcode, such as the ability to select the correct opcode when returning the value of a method.
Dad, can I have fun with lambda expressions?
Lambda Expressions in Java 8 are introduced into the Java language, but no changes are occurring at the bytecode level! We still use the existing invokedynamic features added by Java 7. Does this mean that we can also run lambda expressions in Java 7?
Unfortunately, the answer is yes. The runtime support class required to create the call point for the invokedynamic call does not exist; but it is still interesting to know what we can do with it:
We will generate a lambda expression without language level support!
So what is a lambda expression? In simple terms, it is a function call that is packaged in a compatible interface at run time. Let's see if we can also wrap at run time, use an instance of the method class to represent the method to wrap, but not really use the reflection mechanism to complete the call!
Bytecode generated from lambda expression We note that the bootstrap method of the INVOKEDYNAMIC directive contains all the information about the method to be wrapped, the interface that wraps the method, and the interface method descriptor. So it seems like this is just a matter of creating bytecode that matches our methods and interface parameters.
You say you want to create a byte code? ASM can do it again!
So we need the following inputs:
- The reference to the method we want to wrap
- A reference to the function interface that wraps the method
- If it is an instance method, there is also a reference to the target object that called the method
For this we define the following methods:
Public <T> T lambdafyvirtual (class<?> iface, Method method, Object object)
Public <T> T lambdafystatic (class<?> iface, method)
Public <T> T Lambdafyconstructor (class<?> iface, Constructor Constructor)
We need to translate these methods into ASM-understandable content to write bytecode files,
This allows the lambdametafactory to read methodhandle. Methodhandles in ASM is represented by a handle type,
And it is very simple to create a handle to a given method based on the methods object (here is an instance method):
New Handle (H_invokevirtual, Type.getinternalname (Method.getdeclaringclass ()),
Method.getname (), Type.getmethoddescriptor (method));
So now handle can be used in the bootstrap method of the invokedynamic instruction, and the next step is to really generate bytecode!
Generates a factory class that provides a way to generate a lambda expression for our invokedynamic instruction invocation.
Summing up the above section, we have obtained the following method:
Public <T> T lambdafyvirtual (class<?> iface, Method method, Object object) {
class<?> Declaringclass = Method.getdeclaringclass ();
int tag = Declaringclass.isinterface ()? h_invokeinterface:h_invokevirtual;
Handle Handle = new Handle (tag, Type.getinternalname (Declaringclass),
Method.getname (), Type.getmethoddescriptor (method));
Class<function<object, t>> lambdageneratorclass =
Generatelambdageneratorclass (Iface, handle, Declaringclass, true);
Return lambdageneratorclass.newinstance (). Apply (object);
}
After the bytecode is finally generated, the bytecode is also converted to a class object. To do this we use the DefineClass implemented by the JDK proxy to inject the factory class into the same class loader as the class that defines the wrapper method. And, try adding it to the same package, so we can also access the protected and packages methods! The class has the correct name and the package needs to be clear before the bytecode is generated. We simply randomly generated the class name, which is acceptable for the purpose of this example, but it is not a good solution with extensibility.
Lengthy battles: ASM vs. Jitescript
Above, we used the classic "tv-Kitchen" technology and quietly pulled out a pot full of products from under the table! But now let's really look at the small experiments that generate bytecode.
The code implemented with ASM is as follows:
Protected byte[] Generatelambdageneratorclass (
Final String ClassName,
Final class<?> iface, final Method Interfacemethod,
Final Handle Bsmhandle, final class<?> argumenttype) throws Exception {
Classwriter cw = new Classwriter (classwriter.compute_frames);
Cw.visit (V1_7, acc_public, className, NULL,
Type.getinternalname (Object.class),
New String[]{type.getinternalname (Function.class)});
Generatedefaultconstructor (CW);
Generateapplymethod (CW, Iface, Interfacemethod, Bsmhandle, Argumenttype);
Cw.visitend ();
return Cw.tobytearray ();
}
private void Generatedefaultconstructor (Classvisitor cv) {
String desc = type.getmethoddescriptor (Type.GetType (Void.class));
Generatoradapter GA = Createmethod (CV, Acc_public, "", DESC);
Ga.loadthis ();
Ga.invokeconstructor (Type.GetType (Object.class),
New Org.objectweb.asm.commons.Method ("", desc));
Ga.returnvalue ();
Ga.endmethod ();
}
private void Generateapplymethod (Classvisitor CV, class<?> Iface,
Method Ifacemethod, Handle bsmhandle, class<?> argtype) {
Final object[] Bsmargs = new Object[]{type.gettype (Ifacemethod),
Bsmhandle, Type.GetType (Ifacemethod)};
Final String Bsmdesc = argtype!= null?
Type.getmethoddescriptor (Type.GetType (Iface), Type.GetType (Argtype)):
Type.getmethoddescriptor (Type.GetType (iface));
Generatoradapter GA = Createmethod (CV, acc_public, "apply",
Type.getmethoddescriptor (Type.GetType (Object.class),
Type.GetType (Object.class));
if (argtype! = null) {
Ga.loadarg (0);
Ga.checkcast (Type.GetType (Argtype));
}
Ga.invokedynamic (Ifacemethod.getname (), Bsmdesc, Metafactory, Bsmargs);
Ga.returnvalue ();
Ga.endmethod ();
}
private static Generatoradapter Createmethod (Classvisitor CV,
int access, string name, String desc) {
return new Generatoradapter (
Cv.visitmethod (access, name, DESC, NULL, NULL),
Access, name, DESC);
}
Jitescript implements the following code, using the instance initialization method:
Protected byte[] Generatelambdageneratorclass (
Final String className, final class<?> iface, final Method Ifacemethod,
Final Handle Bsmhandle, final class<?> argtype) throws Exception {
Final object[] Bsmargs = new object[] {
Type.GetType (Ifacemethod), Bsmhandle, Type.GetType (Ifacemethod)};
Final String Bsmdesc = argtype! = null? Sig (Iface, Argtype): Sig (Iface);
return new Jiteclass (ClassName, P (object.class),
New string[] {p (function.class)}) {{
DefineDefaultConstructor ();
DefineMethod ("Apply", Acc_public, Sig (Object.class, Object.class),
New Codeblock () {{
if (argumenttype! = null) {
Aload (1);
Checkcast (P (argumenttype));
}
Invokedynamic (Ifacemethod.getname (), Bsmdesc, Metafactory, Bsmargs);
Areturn ();
}});
}}.tobytes (jdkversion.v1_7);
}
It's obvious that the bytecode that generates a predictable pattern like above is jitescript readable and the code more concise. This is also due to the shorthand tool approach, such as SIG () rather than type.getmethoddescriptor (), where it can be statically imported.
Combine all the code together The methodhandle part of the implementation is combined with the bytecode generation section to test to see if it works correctly!
Intstream.rangeclosed (1, 5). ForEach (
Lamdafier.lambdafyvirtual (
Intconsumer.class,
System.out.getClass (). GetMethod ("println", Object.class),
System.out
));
Look, it runs correctly and outputs the desired value:
1
2
3
4
5
The above example also shows one of the real advantages of lambda expression implementations: it has the ability to convert/pack/Unpack on-demand, and in this case, void (Object) in the Intconsumer interface is defined as void (int)!
Summary: Use all the tools!
Getting started with ASM is not that difficult; yes, you need to know about bytecode, but once you have that foundation, it's fun and satisfying to go deep into the surface and create your own classes. And it can also enrich what you can't get out of Java code. Similarly, creating your own classes that are specific to the current run-time environment may find opportunities that you have never thought of.
ASM is very powerful in bytecode conversion, Jitescript makes the code simple, readable, and does not require you to choose one, they are compatible, after all, Jitescript is basically just the ASM API wrapper.
Try it yourself!
Looking back at this article, we created a simple code that uses ASM to generate a lambda expression from a method-reflective object, taking advantage of the JDK8 lambda expression to focus on all the mandatory and return type conversions!
Add Java Architect Advanced Communication Group get Java Engineering, high performance and distributed, high performance, in layman. High architecture.
Performance tuning, Spring,mybatis,netty source analysis and big data, and many other knowledge points high-level advanced dry Live free learning rights
All are Daniel take the fly to let you take a lot of detours of the group number is: 558787436 pairs of small white do not enter the best is the development experience
Note: Dabigatran requirements
1, with work experience, in the face of the current popular technology do not know where to start, the need to break through the technical bottleneck can be added.
2, in the company for a long time, have a very comfortable, but job-hopping interview wall. Need to study in a short period of time, job-hopping to get a high salary can add.
3, if there is no work experience, but the foundation is very solid, on the Java work mechanism, common design ideas, Common Java Development Framework Master Proficiency, you can add.
4, feel that they are very cow B, the general needs can be done. However, the knowledge points are not systematized, it is difficult to continue the breakthrough in the technical field can add.
5. Ali Java senior Daniel Live to explain the knowledge points, share knowledge, many years of work experience combing and summary, with everyone comprehensive, scientific to establish their own technical system and technical knowledge!
How to "bake" Your own lambda using ASM and Jitescript in Java 8