相关背景及资源:html
曹工说Spring Boot源码(1)-- Bean Definition究竟是什么,附spring思惟导图分享java
曹工说Spring Boot源码(2)-- Bean Definition究竟是什么,我们对着接口,逐个方法讲解git
曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,咱们来试一下web
曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?spring
曹工说Spring Boot源码(5)-- 怎么从properties文件读取beanjson
曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的设计模式
曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中获得了什么(上)api
曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中获得了什么(util命名空间)框架
曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中获得了什么(context命名空间上)ide
曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中获得了什么(context:annotation-config 解析)
曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(此次来讲说它的奇技淫巧)
曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中获得了什么(context:component-scan完整解析)
曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)
曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成
曹工说Spring Boot源码(15)-- Spring从xml文件里到底获得了什么(context:load-time-weaver 完整解析)
曹工说Spring Boot源码(16)-- Spring从xml文件里到底获得了什么(aop:config完整解析【上】)
曹工说Spring Boot源码(17)-- Spring从xml文件里到底获得了什么(aop:config完整解析【中】)
曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)
曹工说Spring Boot源码(19)-- Spring 带给咱们的工具利器,建立代理不用愁(ProxyFactory)
曹工说Spring Boot源码(20)-- 码网恢恢,疏而不漏,如何记录Spring RedisTemplate每次操做日志
曹工说Spring Boot源码(21)-- 为了让你们理解Spring Aop利器ProxyFactory,我已经拼了
曹工说Spring Boot源码(22)-- 你说我Spring Aop依赖AspectJ,我依赖它什么了
曹工说Spring Boot源码(23)-- ASM又立功了,Spring原来是这么递归获取注解的元注解的
工程结构图:
上一篇,咱们讲了spring是怎么获取class上的注解,以及注解的元注解的。在注解大行其道的今天,理解这些相对底层一点的知识,是绝对有必要的。另外,在上一讲中,咱们提到了,spring其实最终也是利用ASM去读取注解的,其中,还使用了访问者设计模式。
访问者设计模式有效地分离了对数据的访问和和对数据的操做,由于class结构是很固定的,因此,visitor模式就尤为适合。在访问到特定数据时,就回调应用注册的回调方法。ASM基本上就是在visitor这个设计模式的基础上创建起来的。
今天,咱们的主题有两个,1是简单地了解下ASM,2是投入实战,看看要怎么去利用ASM + java的Intrumentation机制,来在java启动时,就去修改class,实现简单的aop功能。
本篇覆盖第一个主题,下一个主题留带下一篇(demo已经ok了)。
咱们目的是读取如下测试类上的注解和全部的方法的名称。
如下代码demo见:https://gitee.com/ckl111/all-simple-demo-in-work/tree/master/asm-demo/src/main/java/com/yn/onlyvisit
测试类
package com.yn.onlyvisit; @CustomAnnotationOnClass public class Person { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface CustomAnnotationOnClass { }
定义classVisitor,里面实现visitor的回调方法
package com.yn.onlyvisit; import org.objectweb.asm.*; import org.objectweb.asm.commons.AdviceAdapter; import org.objectweb.asm.commons.AnalyzerAdapter; import org.objectweb.asm.util.ASMifier; import org.objectweb.asm.util.Textifier; import org.objectweb.asm.util.TraceMethodVisitor; import java.util.ArrayList; import java.util.List; public class MyClassVistor extends ClassVisitor { private List<String> methodList = new ArrayList<>(); private List<String> annotationOnClass = new ArrayList<>(); public MyClassVistor() { super(Opcodes.ASM6); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { //每访问到一个方法,加入到field中 System.out.println("visitMethod: " + name); methodList.add(name); return super.visitMethod(access, name, desc, signature, exceptions); } @Override public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { // 访问到类上注解,加入field annotationOnClass.add(descriptor); return super.visitAnnotation(descriptor, visible); } @Override public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { System.out.println("field:" + name); return super.visitField(access, name, descriptor, signature, value); } public List<String> getMethodList() { return methodList; } public List<String> getAnnotationOnClass() { return annotationOnClass; } }
测试代码
import org.objectweb.asm.ClassReader; import java.io.IOException; import java.util.List; public class TestClassVisit { public static void main(String[] args) throws IOException { // 使用classreader读取目标类 ClassReader classReader = new ClassReader("com.yn.onlyvisit.Person"); // new一个visitor MyClassVistor classVisitor = new MyClassVistor(); // 传入classreader classReader.accept(classVisitor,ClassReader.SKIP_DEBUG); // 此时,目标类已经读取完毕,咱们能够打印看看效果 List<String> methodList = classVisitor.getMethodList(); System.out.println(methodList); System.out.println(classVisitor.getAnnotationOnClass()); } }
输出以下:
field:name
field:age
visitMethod:
visitMethod: getName
visitMethod: setName
visitMethod: getAge
visitMethod: setAge
[, getName, setName, getAge, setAge]
[Lcom/yn/onlyvisit/CustomAnnotationOnClass;]
注意,咱们限定的是,生成全新的class,为何限定这么死,由于还有一种是,在已经存在的类的基础上,修改class。
生成全新class的场景也是常见的,好比cglib底层就使用了asm,代理类是动态生成的,对吧?虽然我还没验证,但基本就是目前要讲的这种场景。
还有就是,fastjson里也用了asm,至于里面是不是生成全新class,留带验证。
asm的官方文档,有下面这样一个例子。
目标类以下,咱们的目标,就是生成这样一个类的class:
package pkg; public interface Comparable extends Mesurable { int LESS = -1; int EQUAL = 0; int GREATER = 1; int compareTo(Object o); }
咱们只须要以下几行代码,便可完成该目标。
package com.yn.classgenerate; import org.objectweb.asm.ClassWriter; import java.io.*; import java.lang.reflect.Field; import static org.objectweb.asm.Opcodes.*; public class TestClassWriter { public static void main(String[] args) throws IOException { ClassWriter cw = new ClassWriter(0); cw.visit(V1_7, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "pkg/Comparable", null, "java/lang/Object", null); cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, new Integer(-1)).visitEnd(); cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, new Integer(0)).visitEnd(); cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I", null, new Integer(1)).visitEnd(); cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I", null, null).visitEnd(); cw.visitEnd(); byte[] b = cw.toByteArray(); File file = new File("F:\\gitee-ckl\\all-simple-demo-in-work\\asm-demo\\src\\main\\java\\com\\yn\\classgenerate\\Target.class"); FileOutputStream fos = new FileOutputStream(file); fos.write(b); fos.close(); } }
执行上述代码,在指定位置,就会生成一个Target.class,反编译以后,以下:
上面那个demo,是否够神奇?为何这么神奇呢,核心都在ClassWriter这个类。
这个类,你们能够理解为,一个class文件包含了不少东西,对吧?常量池、field集合、method集合、注解、class名、实现的接口集合等等,这个classWriter呢,其中就有不少field,分别来存储这些东西。
注意的是,上图中,有些字段,好比firstField,为何不是集合呢?按理说,一个class里不少field啊,由于,这里用了链表结构来存储field。咱们看这个field上的注释。
/** * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their * {@link FieldWriter#fv} field. This field stores the first element of this list. */ private FieldWriter firstField;
看到了吧,链表结构。
因此,ClassWriter,你们必定要好好理解,这个ClassWriter,主要的使用方法就是:提供给你一堆方法,你能够调用他们,来给里面的field设置东西,好比,你要设置类名,那你就调用:
cw.visit(V1_7, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "pkg/Comparable", null, "java/lang/Object", null);
要加个field,那就这样:
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, new Integer(0)).visitEnd();
如小标题所言,ClassWriter是实现了ClassVisitor的。
public class ClassWriter extends ClassVisitor
前面咱们说的那些,手动去调用的方法,也是来源于ClassVisitor的。
cw.visit(V1_7, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "pkg/Comparable", null, "java/lang/Object", null);
该方法,来源于:
org.objectweb.asm.ClassVisitor#visit public void visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { if (cv != null) { cv.visit(version, access, name, signature, superName, interfaces); } }
那么,接下来这段话,你们好好理解下:
前面的demo中,咱们手动调用了ClassWriter的各类visit方法,去生成class;可是,咱们又知道,ClassWriter的那些方法,来自于ClassVisitor,而:当咱们向下面这样来编码的时候,ClassVisitor的方法会自动被调用(忘了的,往前翻到:ASM的核心之读取功能),那么,咱们能够实现以下的class复制功能了:
package com.yn.classgenerate; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import static org.objectweb.asm.Opcodes.ASM4; public class CopyClassVersion1 { public static void main(String[] args) throws IOException { ClassReader classReader = new ClassReader("com.yn.classgenerate.CopyClass"); //1 ClassWriter cw = new ClassWriter(0); //2 classReader.accept(cw, 0); byte[] b2 = cw.toByteArray(); File file = new File("F:\\gitee-ckl\\all-simple-demo-in-work\\asm-demo\\src\\main\\java\\com\\yn\\classgenerate\\CopyClass2.class"); FileOutputStream fos = new FileOutputStream(file); fos.write(b2); fos.close(); } }
这里的核心,就是要把classWriter,当成ClassVisitor,传递给ClassReader。
com.yn.classgenerate.CopyClass
这个类,classWriter的各个visit方法,不断被回调,所以,com.yn.classgenerate.CopyClass
的各种field、method等,不断被写入classWriter中,因而,复制就这样完成了。前面那个复制class的操做中,classreader是直接回调classWriter的,咱们其实也能够在中间横插一脚。
public class CopyClass { public static void main(String[] args) throws IOException { ClassReader classReader = new ClassReader("com.yn.classgenerate.CopyClass"); ClassWriter cw = new ClassWriter(0); // cv forwards all events to cw ClassVisitor cv = new ClassVisitor(ASM4, cw) { }; classReader.accept(cv, 0); byte[] b2 = cw.toByteArray(); File file = new File("F:\\gitee-ckl\\all-simple-demo-in-work\\asm-demo\\src\\main\\java\\com\\yn\\classgenerate\\CopyClass2.class"); FileOutputStream fos = new FileOutputStream(file); fos.write(b2); fos.close(); } }
在上面这个例子中,咱们从classReader的下面这句开始看:
classReader.accept(cv, 0);
那么,能够知道,classReader是去回调cv,那么cv是谁?
ClassVisitor cv = new ClassVisitor(ASM4, cw) { };
cv的构造函数里,传入了cw,cw呢,就是classwriter。
如今的链路是这样的:
classReader --> cv --> cw。
上面这个链路中,classReader确定会回调cv,可是cv,怎么就肯定它会当个二传手呢?
看看ClassVisitor的构造函数:
public ClassVisitor(final int api, final ClassVisitor classVisitor) { if (api < Opcodes.ASM4 || api > Opcodes.ASM6) { throw new IllegalArgumentException(); } this.api = api; this.cv = classVisitor; }
其把ClassVisitor保存到了一个域:cv中。这个cv如何被使用呢?咱们看看下面的方法:
org.objectweb.asm.ClassVisitor#visit public void visit( final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { if (cv != null) { cv.visit(version, access, name, signature, superName, interfaces); } } public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { if (cv != null) { return cv.visitAnnotation(descriptor, visible); } return null; }
这就有意思了,若是cv不为null,就调用cv去处理,这就是个delegate啊,代理啊。
上面的demo中,cv简直是尽忠职守,本身在中间,丝绝不作什么事,就是一个称职的代理。但不是全部代理都须要这样,甚至是不鼓励这样。
官网中有个demo,以下所示,能够修改class的版本:
public class ChangeVersionAdapter extends ClassVisitor { public ChangeVersionAdapter(ClassVisitor classVisitor) { super(ASM4, classVisitor); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { cv.visit(V1_8, access, name, signature, superName, interfaces); } }
测试类:
public class TestChangeClassVersion { public static void main(String[] args) throws IOException { ClassReader classReader = new ClassReader("com.yn.classgenerate.CopyClass"); ClassWriter cw = new ClassWriter(0); ClassVisitor cv = new ChangeVersionAdapter(cw) { }; classReader.accept(cv, 0); byte[] b2 = cw.toByteArray(); File file = new File("F:\\gitee-ckl\\all-simple-demo-in-work\\asm-demo\\src\\main\\java\\com\\yn\\classgenerate\\CopyClass2.class"); FileOutputStream fos = new FileOutputStream(file); fos.write(b2); fos.close(); } }
官网还画了个图,贴心:
经过这样,classWriter中,版本号已经被改了,但它还被蒙在鼓里,可怜。
在ClassVisitor中,有几个特殊的方法:
主要就是这几个,你看他们的返回值,不太同样,是xxxVistor,和ClassVisitor有点像?那就对了。
咱们看看fieldVisitor:
其结构和方法,都和ClassVisitor相似,也就是说,咱们能够返回一个自定义的FieldVistor,而后,ASM框架,就会使用咱们返回的这个FieldVisitor去visit咱们的field的相关属性,回调fieldVisitor中的相关方法。
那,怎么删除呢?返回null。
这么简单吗,是的。
package com.yn.classgenerate; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import static org.objectweb.asm.Opcodes.ASM6; // 该demo来自官网文档 public class RemoveMethodAdapter extends ClassVisitor { private String mName; private String mDesc; public RemoveMethodAdapter( ClassVisitor cv, String mName, String mDesc) { super(ASM6, cv); this.mName = mName; this.mDesc = mDesc; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (name.equals(mName) && desc.equals(mDesc)) { // 这样就能够了。 // do not delegate to next visitor -> this removes the method return null; } return cv.visitMethod(access, name, desc, signature, exceptions); } }
asm的基本操做大概如此,这些比较粗浅,下一讲咱们会实现一个有用一点的东西,会结合java的instrument机制来说。
你们要跟着个人demo一块儿来实践,https://gitee.com/ckl111/all-simple-demo-in-work/tree/master/asm-demo
这样才能学的劳。