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 這種就只能在一個類實現所有邏輯了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM