Some repetitive work, can be given to the program to do not do their own, this is the programmer spirit.
I’ll put the 0 out front
This is the final article in a series that teaches you to do something useful with ASM development. Including how to modify toString to do some desensitization.
1 Instrumentation
How did bytecode ASM teach you to modify bytecode? I’m sure you already have an idea of how to modify bytecode, but there is a problem. In the last video, we used the.class file in memory, which does not affect the class used in the actual JVM. This is indeed a difficult problem to solve, at least until jdk1.5, when java.lang.instrument came along. It frees Java instrument functionality from native code to solve problems in Java code. Java.lang. instrument is an implementation of the Java version provided on top of the JVM TI. The main function provided by Instrumentation is to modify the behavior of classes in the JVM. In Java SE6, there are two ways to apply Instrumentation, premain (command line) and AgentMain (runtime).
1.1 premain
We know that Java programs are always started using the main method, and premain means premain is run before main is started. Start by writing a Java class and include one of the following methods:
public static void premain(String agentArgs, Instrumentation inst);
public static void premain(String agentArgs);
Copy the code
When the above two exist at the same time, 1 takes precedence over 2. This method takes two arguments:
- AgentArgs: This is a string array of arguments passed in to main, which needs to be parsed by itself.
- Instrumentation: This is the core of our Instrumentation, an interface defined in the instrument package, is also the core part of the package, which focuses on almost all of the functional methods, such as class definition conversion and operations, and so on.
Then implement ClassFileTransformer interface, ClassFileTransform is used to transform the class, its interface transform is the key to transform the class, and its fourth input parameter is also the key to modify the bytecode in the following:
public class ClassTransformerImpl implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<? > classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("The class name is :" + className);
returnclassfileBuffer; }}Copy the code
Above we printed the names of all the classes in our transform. Back in our premain, our method is as follows:
public class PerfMonAgent {
static private Instrumentation inst = null;
public static void premain(String agentArgs, Instrumentation _inst) {
System.out.println("PerfMonAgent.premain() was called.");
// Initialize the static variables we use to track information.
inst = _inst;
// Set up the class-file transformer.
ClassFileTransformer trans = new ClassTransformerImpl();
System.out.println("Adding a PerfMonXformer instance to the JVM."); // Pass our custom class transformer in inst.addTransformer(trans); }}Copy the code
We can modify the premain method as follows:
public class PerfMonAgent {
static private Instrumentation inst = null;
public static void premain(String agentArgs, Instrumentation _inst) {
System.out.println("PerfMonAgent.premain() was called.");
// Initialize the static variables we use to track information.
inst = _inst;
// Set up the class-file transformer.
ClassFileTransformer trans = new ClassTransformerImpl();
System.out.println("Adding a PerfMonXformer instance to the JVM."); // Pass our custom class transformer in inst.addTransformer(trans); }}Copy the code
The code is defined. If you don’t use Maven, you need to add “premain-class” to the manifest property to specify which Java Class to write with Premain. If you are using Maven, you can use it
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> The < version > 2.2 < / version > < configuration > < archive > < manifestEntries > < premain-class >instrument.PerfMonAgent</ premain-class > Need to be here introduce < Boot - Class - Path > / Users/lizhao/m2 / repository/org/ow2 / asm/asm / 5.0.4 / asm - 5.0.4. Jar < / Boot - Class - Path > </manifestEntries> </archive> </configuration> </plugin> </plugins>Copy the code
Finally, you can use it. You can write a class with a main method at will:
Java-javaagent: Location of jar file [= parameters passed to premain]Copy the code
For the IDEA compiler you can enter it in the VM configuration
1.2 agentmain
Premain is the proxy method that Java SE5 has provided since its inception, and it gives developers a lot of surprises, but some of them need to stay the same, since the proxy JAR must be specified at the command line and the proxy class must be started before the main method. Therefore, requiring developers to verify the agent’s processing logic, parameter content, etc., before application can be difficult in some cases. For example, in a normal production environment, agents are not usually enabled. After all Java SE6 provides agentMain, which allows you to make changes dynamically without having to set up agents. In JavaSE6 documentation, developers may not find explicit instructions in the documentation section of the java.lang.instrument package, much less specific examples of applying AgnetMain. However, one of the less notable new features in Java SE 6 reveals the use of AgentMain. This is the Attach API provided in Java SE 6.
The Attach API is not a standard Java API, but an extended set of APIS provided by Sun for “attaching” the proxy application to the target JVM. With it, developers can easily monitor a JVM and run an additional agent. There is no space here to introduce how attach API works. In a word, accach API is necessary for the whole process. Interested students can read it by themselves: https://www.ibm.com/developerworks/cn/java/j-lo-jse61/
1.3 summary
With our Instrument we find the source of our class, and we can modify the bytecode as we wish, depending on what we learned in the previous section.
2. Start toString desensitization
2.1 design
First of all, we need to design what we are going to do next, so that we do not panic.
2.1.1 target
Modify toString bytecodes so that toString(), which used to print plaintext, is desensitized to our custom needs.
2.1.2 the custom
The intention is to customize desensitization through annotations, @desfiled to mark the fields to be desensitized, @desenstized to mark the classes for desensitization, and extend desensitization by inheriting a Basefilter.
2.2 Before Starting
Make sure before you do anything, make sure you have the tools ready
- Has the ASM plug-in been downloaded?
- Has the MAVEN package for ASM been introduced?
- Has my public account been concerned? Once that’s done we can do the following. We define our annotations first:
@java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Inherited public @interface DesFiled {/** * Encryption type * @return
*/
public Class<? extends BaseDesFilter> value() default BaseDesFilter.class;
}
@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface Desensitized {
}
Copy the code
There is also our filter interface for desensitization, and his implementation class for desensitization of mobile phone number Field, which is actually conversion:
public interface BaseDesFilter <T>{
default T desc(T needDesc){
returnneedDesc; }; } public class MobileDesFilter implements BaseDesFilter {Override public Object desc(Object needDesc) {if(needDesc instanceof Long ){
needDesc = String.valueOf(needDesc);
}
if (needDesc instanceof String){
returnDesensitizationUtil.mobileDesensitiza((String) needDesc); } // If this is an enumeration class, todoreturnneedDesc; }}Copy the code
Then we write a class for desensitization:
@Desensitized
public class StreamDemo1 {
@DesFiled(MobileDesFilter.class)
private String name;
private String idCard;
@DesFiled(AddressDesFilter.class)
private List<String> mm;
@Override
public String toString() {
return "StreamDemo1{" +
"name='" + name + '\'' + ", idCard='" + idCard + '\'' + ", mm=" + mm + '}'; }}Copy the code
This time your ASM plugin can be great magic power, (not only here, if you develop ASM related, use the plugin to see his original code, and then compare), here we use asm plugin to generate a version of ASM code this time can be screenshots save, and then we manually modify toString method:
@Override
public String toString() {
return "StreamDemo1{" +
"name='" + DesFilterMap.getByClassName("MobileDesFilter").desc(name) + '\'' + ", idCard='" + idCard + '\'' + ", mm=" + mm + '}'; }Copy the code
Using plugin generation, here we can compare what we need to add to ASM if we want to add a desensitization method.
And what we need to do is replace the red box in the second picture with the red box in the first picture. Simply put, the first diagram simply gets the this reference and then the field. The second figure is the need to obtain a reference to the desensitization method and then pass this.name for desensitization.
Now we know what we need to do, this time actually do not need to see the details of the next, you can try to see how to do it.
2.2 Start
First define a converter-like:
public class PerfMonXformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<? > classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { byte[] transformed = null; System.out.println("Transforming "+ className); ClassReader reader = new ClassReader(classfileBuffer); ClassWriter ClassWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); // Select Java8 support ASM5 ClassVisitor ClassVisitor = new DesClassVistor(opcodes.asm5,classWriter); reader.accept(classVisitor,ClassReader.SKIP_DEBUG);returnclassWriter.toByteArray(); }}Copy the code
Using the ASM knowledge we learned in the class converter section, we then defined a custom ClassVisitor called DesClassVistor to access the class and generate byte arrays through our classWriter:
public class DesClassVistor extends ClassVisitor implements Opcodes{
private static final String classAnnotationType = "L"+ Desensitized.class.getName().replaceAll("\ \."."/") +";"; /** * private Boolean des; private String className; private Map<String, FiledInfo> filedMap = new HashMap<>(); public DesClassVistor(int i) { super(i); } public DesClassVistor(int api, ClassVisitor cv) { super(api, cv); } @Override public void visit(int jdkVersion, int acc, String className, String generic, String superClass, String[] superInterface) { this.className = className; super.visit(jdkVersion, acc, className, generic, superClass, superInterface); } /** * * @paramtypeAnnotation type * @param seeing visibility * @return
*/
@Override
public AnnotationVisitor visitAnnotation(String type, boolean seeing) {
if (classAnnotationType.equals(type)){
this.des = true;
}
return super.visitAnnotation(type, seeing); } /** ** @param ACC access permission * @param name Field name * @paramtypeType * @param generic generic * @param defaultValue defaultValue * @return
*/
@Override
public FieldVisitor visitField(int acc, String name, String type, String generic, Object defaultValue) {
FieldVisitor fv = super.visitField(acc, name, type, generic, defaultValue);
if (des == false || acc >= ACC_STATIC){
return fv;
}
FiledInfo filedInfo = new FiledInfo(acc, name, type, generic, defaultValue);
filedMap.put(name, filedInfo);
FieldVisitor testFieldVisitor = new DesFieldVisitor(filedInfo,fv);
return testFieldVisitor;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (this.des == false || !"toString".equals(name)){
return mv;
}
MethodVisitor testMethodVistor = new DesMethodVistor(mv, filedMap);
return testMethodVistor; }}Copy the code
Three important methods are overridden here:
- VisitAnnotation: Determine if there is a @desensiTized annotation, and if there is, set des=true to enable the annotation
- VisitField: It is used to convert the filed in ASM into our self-defined FieldInfo and put it into map, which is convenient for subsequent processing. The filed is also given to the self-defined DesFieldVisitor for processing
- VisitMethod: This is used to put toString methods in asm’s custom DesMethodVistor to handle toString methods.
Filed processing has the following code:
public class DesFieldVisitor extends FieldVisitor {
private static final String desFieldAnnotationType = "L"+ DesFiled.class.getName().replaceAll("\ \."."/") +";";
private FiledInfo info;
public DesFieldVisitor(int i) {
super(i);
}
public DesFieldVisitor(int i, FieldVisitor fieldVisitor) {
super(i, fieldVisitor);
}
public DesFieldVisitor(FiledInfo filedInfo, org.objectweb.asm.FieldVisitor fv) {
super(Opcodes.ASM5, fv);
info = filedInfo;
}
@Override
public AnnotationVisitor visitAnnotation(String s, boolean b) {
AnnotationVisitor av = super.visitAnnotation(s, b);
if(! desFieldAnnotationType.equals(s)){return av;
}
info.setDes(true);
AnnotationVisitor avAdapter = new DesTypeAnnotationAdapter(Opcodes.ASM5, av, this.info);
returnavAdapter; }}Copy the code
The visitAnnotation is rewritten to judge whether there are DesFiled annotations and the information on them.
public class DesMethodVistor extends MethodVisitor implements Opcodes{
Map<String, FiledInfo> filedMap;
public DesMethodVistor(int i) {
super(i);
}
public DesMethodVistor(int i, MethodVisitor methodVisitor) {
super(i, methodVisitor);
}
public DesMethodVistor(MethodVisitor mv, Map<String, FiledInfo> filedMap) {
super(ASM5, mv);
this.filedMap = filedMap;
}
@Override
public void visitVarInsn(int opcode, int var) {
if(! (opcode == Opcodes.ALOAD && var == 0)){ super.visitVarInsn(opcode, var); }} /** * Add filter logic * @param opcode * @param owner * @param name * @param desc */ @override public void visitFieldInsn(int) opcode, String owner, String name, String desc) { FiledInfo filedInfo = filedMap.get(name);if (filedInfo.isNotDes()){
super.visitVarInsn(ALOAD, 0);
super.visitFieldInsn(opcode, owner, name, desc);
return;
}
mv.visitLdcInsn(filedInfo.getFilterClass().getName());
mv.visitMethodInsn(INVOKESTATIC, ASMUtil.getASMOwnerByClass(DesFilterMap.class), "getByClassName"."(Ljava/lang/String;) Lasm/filter/BaseDesFilter;".false);
super.visitVarInsn(ALOAD, 0);
super.visitFieldInsn(opcode, owner, name, desc);
mv.visitMethodInsn(INVOKEINTERFACE, ASMUtil.getASMOwnerByClass(BaseDesFilter.class), "desc"."(Ljava/lang/Object;) Ljava/lang/Object;".true);
mv.visitMethodInsn(INVOKESTATIC, ASMUtil.getASMOwnerByClass(String.class), "valueOf"."(Ljava/lang/Object;) Ljava/lang/String;".true); }}Copy the code
By rewriting visitFieldInsn method desensitized bytecode transformation. For specific code, see my ASM-log, configure vm parameters in StreamDemo, and execute main. Refer to my code:
@Desensitized
public class StreamDemo1 {
@DesFiled(MobileDesFilter.class)
private String name;
private String idCard;
@DesFiled(AddressDesFilter.class)
private List<String> mm;
@Override
public String toString() {
return "StreamDemo1{" +
"name='" + name + '\'' + ", idCard='" + idCard + '\'' + ", mm=" + mm + '}'; } public static void main(String[] args) throws Exception { StreamDemo1 streamDemo1 = new StreamDemo1(); streamDemo1.setName("18428368642"); streamDemo1.setIdCard("22321321321"); streamDemo1.setMm(Arrays.asList("Beijing is a big city in Chaoyang District","Beijing is a big city in Chaoyang District")); System.out.println(streamDemo1); }}Copy the code
Write comments on the class and on the class variable, a desensitization class using the phone number, a desensitization class using the address, execute the main method, the output is as follows:
StreamDemo1{name='* * * 184 * 8642', idCard='22321321321', mm=[Beijing is chaoyang district dozen *****, Beijing is chaoyang district dozen *****]}Copy the code
This prevents you from spending your precious time repeatedly modifying toString in each class, which is really inefficient. As a programmer, you need to have your own hack spirit and never do what you can give the program to do.
2.3 Thinking after you finish
Using bytecode to make a tool, I did learn a lot, at least in the future to understand bytecode, understand some Java syntax sugar processing is very helpful, but this tool is not very common, make a JAR package, you need to configure agent or you use attach API, so the business configuration is still quite troublesome. So our tools can be implemented using other techniques, such as annotation processors to modify abstract syntax trees, which are as business-less intrusive as Lombok.
And ASM is more than just instrument. If you look at the source code for cglib cuts, or if you look at the source code for Fastjson, you can take a class that’s already loaded in the JVM and modify its bytecode to create a new class, either a proxy class or a completely new class.
The last
Due to their own level is limited, especially in the description of this relatively cold knowledge can not abstract very well, I hope you can understand understanding, but also hope that you can do a small tool about ASM after reading, can be played method time-consuming, can also be unified transaction management.
Had intended to write immediately modify syntax tree next tutorial, want to teach you how to hand rolled a Lombok essential artifact (Java), but found that this kind of knowledge points more uncommon articles are difficult indeed, modify the syntax tree than bytecode again may be a little bit difficult, all kinds of documents are less, and combined with the recent work is busy, only wrote the morning after work, The feeling is not very good will be more complex knowledge point abstract into simple, decided not to write temporarily. If you are interested in the principle of Lombok or how to implement your own Lombok, you can refer to my Slothlog github(star by the way), where there are many annotations. If you don’t understand, you can follow my official account and add my wechat private chat.
If you think this article is helpful to you, or you want to get ahead of the subsequent chapters, or if you have any questions to provide 1 v1 free VIP service, can focus on my public, focus on hundreds of G can be free to get the latest Java learning video data, and the latest interview data, and forward your concern is the largest support to me, O (studying studying) O: