問題記錄:想要替換別人的代碼,可是沒辦法或不能修改別人的代碼

這是我第一篇博客,謝謝指教!!!!!!!!!!!java

 

如下的代碼用到asm5.0.4api

maven:框架

<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>5.0.4</version>
</dependency>

或者手動下載:http://central.maven.org/maven2/org/ow2/asm/asm/5.0.4/asm-5.0.4.jarmaven

 

一開始,我用asm和自定義classloader的方法,成功動態加載了修改過的class(asm怎麼用我就不說了,之後有空我可能會寫些相關內容)。ide

先定義一個咱們用來修改的類this

public class TestAop {
    public void halloAop() {
        System.out.println("Hello Aop");
    }
}

而後是我實現的代碼code

public class AopClassLoader extends ClassLoader implements Opcodes {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, InstantiationException {
        new AopClassLoader("util.TestAop").loadClass("util.TestAop");
        new TestAop().halloAop();
    }
    public static void beforeInvoke() {
        System.out.println("before");
    }
    public static void afterInvoke() {
        System.out.println("after");
    }
    
    public AopClassLoader(ClassLoader parent, String... classes) {
        super(parent);
        this.parent = parent;
        this.classes = classes;
    }
    public AopClassLoader(String... classes) {
        this(Thread.currentThread().getContextClassLoader(), classes);
    }
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        boolean tag = true;
        for (String clazz: classes) {
            if (clazz.equals(name)) {
                tag = false;
            }
        }
        if (tag) {
            return super.loadClass(name);
        }
        try {
            ClassWriter cw = new ClassWriter(0);
            ClassReader reader = new ClassReader(name);
            reader.accept(new AopClassAdapter(ASM5, cw), 0);
            
            byte[] code = cw.toByteArray();
            
            Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
            method.setAccessible(true);
            
            return (Class<?>)method.invoke(parent, name, code, 0, code.length);
        } catch (Throwable e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
    private class AopClassAdapter extends ClassVisitor implements Opcodes {
        AopClassAdapter(int api, ClassVisitor cv) {
            super(api, cv);
        }
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
            if ("<init>".equals(name)) {
                return mv;
            }
            return new AopMethod(this.api, mv);
        }
        private class AopMethod extends MethodVisitor implements Opcodes {
            AopMethod(int api, MethodVisitor mv) {
                super(api, mv);
            }
            @Override
            public void visitCode() {
                super.visitCode();
                this.visitMethodInsn(INVOKESTATIC, AopClassLoader.class.getName().replace(".", "/"), "beforeInvoke", "()V", false);
            }
            @Override
            public void visitInsn(int opcode) {
                if (opcode == RETURN) {
                    mv.visitMethodInsn(INVOKESTATIC, AopClassLoader.class.getName().replace(".", "/"), "afterInvoke", "()V", false);
                }
                super.visitInsn(opcode);
            }
        }
    }
    private ClassLoader parent;
    private String[] classes;
}

網上也有很多相似的代碼,上面這份代碼雖然不是個人最終答卷,可是這份代碼也算是實現的比較好的代碼了,爲何這麼說呢,由於是用自定義的classloader去load這個咱們要修改的類,原本是沒辦法直接調用helloAop,只能用反射的方式調用的(被不一樣的classloader加載的類,是不可以互相轉化的,也就是說,沒辦法強制轉化成TestAop,固然就不可以當作TestAop的對象去執行方法了。ps:代碼寫到TestAop.class的時候,就被系統classloader給加載了,而咱們的類是被咱們自定義的classloader加載的)。可是,上面的代碼強制轉化成功了!由於,我新建(能夠當作是新建了一個class)的這個class,繼承了原來的TestAop(注意這一句return super.loadClass(name),也就是TestAop是被父類的也就是系統classloader加載的,若是去看源代碼,其實父類調用了parent去加載這個TestAop,也就是說TestAop被加載一次,可是同時兩個classloader加載進去了,這個跟被兩個classloader分別加載是有區別的),因此,咱們能夠把它強制轉化成TestAop的對象。xml

 

可是,這樣還不能在運行時修改別人的類啊,由於不能用new的方法獲取我自定義class的對象,而後我就開始想辦法替換系統classloader(其實有些框架,在調用classloader時是使用Thread.currentThread().getContextClassLoader()來獲取classloader,這個時候就能夠提早經過Thread.currentThread().setContextClassLoader(cl)去設置classloader,這樣某些框架獲取到的classloader就是咱們自定義的classloader,當別人經過這個classloader讀取到的類,就是變成咱們寫的類。可是,大多數框架,並不會手動去load一個類,就算去load了,也是new出本身要的對象,問題是剛纔說了,如今new出來對象並非咱們自定義class的實例)。可是,我找了不少辦法,並無一個通用的方法去替換系統classloader。對象

 

而後我又想了一個方法,能不能,直接用系統classloader去加載咱們自定義的類。具體方法就是,仍是用asm去自定義類並生成byte[],可是這個時候咱們用系統classloader去defineClass(方法是私有的,須要用反射去執行),封裝了以後,具體代碼在下面。繼承

public class AopClassLoader extends ClassLoader implements Opcodes {
	public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, InstantiationException {
		new AopClassLoader("test1.TestAop").loadClass("test1.TestAop");
		new TestAop().halloAop();
	}
    public static void beforeInvoke() {
        System.out.println("before");
    };
    public static void afterInvoke() {
        System.out.println("after");
    };
    public AopClassLoader(ClassLoader parent, String... classes) {
        super(parent);
        this.parent = parent;
        this.classes = classes;
    }
    public AopClassLoader(String... classes) {
        this(Thread.currentThread().getContextClassLoader(), classes);
    }
    public Class<?> loadClass(String name) throws ClassNotFoundException {
    	boolean tag = true;
    	for (String clazz: classes) {
    		if (clazz.equals(name))
    			tag = false;
    	}
    	if (tag)
    		return super.loadClass(name);
        try {
            ClassWriter cw = new ClassWriter(0);
            ClassReader reader = new ClassReader(name);
            reader.accept(new AopClassAdapter(ASM5, cw), 0);
            
            byte[] code = cw.toByteArray();
            
            Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
            method.setAccessible(true);
            
            return (Class<?>)method.invoke(parent, name, code, 0, code.length);
        } catch (Throwable e) {
        	e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
    private class AopClassAdapter extends ClassVisitor implements Opcodes {
        AopClassAdapter(int api, ClassVisitor cv) {
            super(api, cv);
        }
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
            if ("<init>".equals(name))
            	return mv;
            return new AopMethod(this.api, mv);
        }
        private class AopMethod extends MethodVisitor implements Opcodes {
            AopMethod(int api, MethodVisitor mv) {
                super(api, mv);
            }
            public void visitCode() {
                super.visitCode();
                this.visitMethodInsn(INVOKESTATIC, AopClassLoader.class.getName().replace(".", "/"), "beforeInvoke", "()V", false);
            }
            public void visitInsn(int opcode) {
            	if (opcode == RETURN)
            		mv.visitMethodInsn(INVOKESTATIC, AopClassLoader.class.getName().replace(".", "/"), "afterInvoke", "()V", false);
            	super.visitInsn(opcode);
            }
        }
    }
    private ClassLoader parent;
    private String[] classes;
}

解決,成功new出來咱們自定義的類的對象!!!!!!!!

不少細節,慢慢琢磨,不懂再問,但但願本身先琢磨。

ps:注意,上面我沒有用TestAop.class.getName()而是用了"test1.TestAOp",剛剛也說過,若是代碼寫到了TestAop.class,其實TestAop就已經被系統classloader加載了,這個時候,咱們還調用系統classloader去defineclass類TestAop,會報錯。

 

轉載請註明出處!

相關文章
相關標籤/搜索