• ASM之ClassVisitor类设计


    ClassVisitor

    • 访问者模式
    • 我们不讲访问者模式,只说说这个类的设计的个人思考
    
    package org.springframework.asm;
    
    /**
     * A visitor to visit a Java class. The methods of this class must be called in
     * the following order: <tt>visit</tt> [ <tt>visitSource</tt> ] [
     * <tt>visitModule</tt> ][ <tt>visitOuterClass</tt> ] ( <tt>visitAnnotation</tt> |
     * <tt>visitTypeAnnotation</tt> | <tt>visitAttribute</tt> )* (
     * <tt>visitInnerClass</tt> | <tt>visitField</tt> | <tt>visitMethod</tt> )*
     * <tt>visitEnd</tt>.
     * 
     * @author Eric Bruneton
     */
    public abstract class ClassVisitor {
    
        protected final int api;
        protected ClassVisitor cv;
    
        public ClassVisitor(final int api) {
            this(api, null);
        }
    
        public ClassVisitor(final int api, final ClassVisitor cv) {
            if (api < Opcodes.ASM4 || api > Opcodes.ASM6) {
                throw new IllegalArgumentException();
            }
            this.api = api;
            this.cv = cv;
        }
    
        public void visit(int version, int access, String name, String signature,
                String superName, String[] interfaces) {
            if (cv != null) {
                cv.visit(version, access, name, signature, superName, interfaces);
            }
        }
    
        public void visitSource(String source, String debug) {
            if (cv != null) {
                cv.visitSource(source, debug);
            }
        }
    
        public ModuleVisitor visitModule(String name, int access, String version) {
            if (api < Opcodes.ASM6) {
                throw new RuntimeException();
            }
            if (cv != null) {
                return cv.visitModule(name, access, version);
            }
            return null;
        }
    
        public void visitOuterClass(String owner, String name, String desc) {
            if (cv != null) {
                cv.visitOuterClass(owner, name, desc);
            }
        }
    
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            if (cv != null) {
                return cv.visitAnnotation(desc, visible);
            }
            return null;
        }
        public AnnotationVisitor visitTypeAnnotation(int typeRef,
                TypePath typePath, String desc, boolean visible) {
    		/* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1
            if (api < Opcodes.ASM5) {
                throw new RuntimeException();
            }
            */
            if (cv != null) {
                return cv.visitTypeAnnotation(typeRef, typePath, desc, visible);
            }
            return null;
        }
    
        public void visitAttribute(Attribute attr) {
            if (cv != null) {
                cv.visitAttribute(attr);
            }
        }
    
        public void visitInnerClass(String name, String outerName,
                String innerName, int access) {
            if (cv != null) {
                cv.visitInnerClass(name, outerName, innerName, access);
            }
        }
    
        public FieldVisitor visitField(int access, String name, String desc,
                String signature, Object value) {
            if (cv != null) {
                return cv.visitField(access, name, desc, signature, value);
            }
            return null;
        }
    
        public MethodVisitor visitMethod(int access, String name, String desc,
                String signature, String[] exceptions) {
            if (cv != null) {
                return cv.visitMethod(access, name, desc, signature, exceptions);
            }
            return null;
        }
    
        public void visitEnd() {
            if (cv != null) {
                cv.visitEnd();
            }
        }
    }
    
    

    为什么不声明为接口?

    • 构造时需要一个成员变量,接口不支持。

    为什么要使用静态代理?

    • 像下面这样声明不香吗?
    public abstract class ClassVisitorV2 {
    
        protected final int api;
    
        public ClassVisitorV2(final int api) {
            if (api < Opcodes.ASM4 || api > Opcodes.ASM6) {
                throw new IllegalArgumentException();
            }
            this.api = api;
        }
    
        public void visit(int version, int access, String name, String signature,
                String superName, String[] interfaces) {
        }
    
        public void visitSource(String source, String debug) {
        }
    
        public ModuleVisitor visitModule(String name, int access, String version) {
            if (api < Opcodes.ASM6) {
                throw new RuntimeException();
            }
            return null;
        }
    
        public void visitOuterClass(String owner, String name, String desc) {
        }
    
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            return null;
        }
        public AnnotationVisitor visitTypeAnnotation(int typeRef,
                TypePath typePath, String desc, boolean visible) {
    		/* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1
            if (api < Opcodes.ASM5) {
                throw new RuntimeException();
            }
            */
            return null;
        }
    
        public void visitAttribute(Attribute attr) {
        }
    
        public void visitInnerClass(String name, String outerName,
                String innerName, int access) {
        }
    
        public FieldVisitor visitField(int access, String name, String desc,
                String signature, Object value) {
            return null;
        }
    
        public MethodVisitor visitMethod(int access, String name, String desc,
                String signature, String[] exceptions) {
            return null;
        }
    
        public void visitEnd() {
        }
    }
    
    • 使用者只需要实现自己需要的方法,类似缺省适配器模式的声明

    • 那为什么实际源码里要使用静态代理模式,声明一个ClassVisitor成员变量?

      • 个人猜测问题还是出在api成员变量上,我们看到原本有两个方法里面用到了这个变量,这里面被注释了一个,但是没准后面更多的方法也要用到这个变量,如
          public ModuleVisitor visitModule(String name, int access, String version) {
              if (api < Opcodes.ASM6) {
                  throw new RuntimeException();
              }
              if (cv != null) {
                  return cv.visitModule(name, access, version);
              }
              return null;
          }
      
      • 如果使用我们说的这种声明方式,那么子类只需要重写父类方法,如果没有调用super.visitModule方法,这里面的判断逻辑就会丢掉了,也就是api判断没了。
      		classReader.accept(new ClassVisitorV2(Opcodes.ASM6) {
      			@Override
      			public ModuleVisitor visitModule(String name, int access, String version) {
      				return null;
      			}
      		}, 0);
      
      • 如果按照下面使用,应该也是没啥问题的,api的判断也有了,有个问题就是没办法强制使用者去调用父类visitModule方法,用户也没法意识到这个问题。
      		classReader.accept(new ClassVisitorV2(Opcodes.ASM6) {
      			@Override
      			public ModuleVisitor visitModule(String name, int access, String version) {
      				super.visitModule(name, access, version);
      				return null;
      			}
      		}, 0);
      
      • 当然,源码里的这种声明方式,如果我们使用覆盖的方式来实现逻辑,如果没有调用super.visitModule方法,跟我说的声明方式也就没区别了,这样也会丢失api的判断
      		classReader.accept(new ClassVisitor(Opcodes.ASM6) {
      			@Override
      			public ModuleVisitor visitModule(String name, int access, String version) {
      				return null;
      			}
      		}, 0);
      
      • 当然,也可以在子类调用super.visitModule,这样api的判断还是有的
      		classReader.accept(new ClassVisitor(Opcodes.ASM6) {
      			@Override
      			public ModuleVisitor visitModule(String name, int access, String version) {
      				super.visitModule(name, access, version);
      				return null;
      			}
      		}, 0);
      
      • 上面说的,都得依赖用户知道每个方法需不需要调用父类的被重写方法,这个依赖是不明确且有风险的

      • 说了这么多,源码里这么设计的好处是什么呢?可以使用类似装饰模式的分离职责包装我们的具体实现类,ClassVisitor负责装饰;我们的实现类负责重写逻辑,不用关心父类调用。

      • 所以源码里既然用了这种设计方式,应该是希望我们这样用吧

      classReader.accept(new ClassVisitor(Opcodes.ASM6,new MyClassVisitor(Opcodes.ASM6)) {
      		}, 0);
      class MyClassVisitor extends ClassVisitor {
      
      	public MyClassVisitor(int api) {
      		super(api);
      	}
      
      	@Override
      	public ModuleVisitor visitModule(String name, int access, String version) {
      		return null;
      	}
      }
      
      • 这样我们的MyClassVisitor就可以随便重写方法,而不用担心一些必要判断丢失了
    • 其他的Visitor也采用了类似的设计

    • 另外,asm的官方文档提到,ClassVisitor可以当做过滤器来使用,也就是多个ClassVisitor互相嵌套,每个ClassVisitor实现不同职责;ClassVisitorV2 这种就只能在一个类实现所有逻辑了

  • 相关阅读:
    了解 DICOM 基本协议与其相关
    C# PropertyInfo 反射实体部分字段
    ref(引用参数) 和 out(输出参数) 区别
    Linq Where Expression<Func<T,bool>> 查询条件
    随笔规范
    C# 集合分析
    C# 几种常用的数据类型
    关于 C# 方法参数的理解
    打算开始写博客了
    有趣的算法、逻辑面试题
  • 原文地址:https://www.cnblogs.com/zby9527/p/13273360.html
Copyright © 2020-2023  润新知