动态代理

概述

这里主要是对比jdk的动态代理和cglib的动态代理,然后再对jdk的代理模式进行原理分析。有不对的地方请指出,谢谢。

内容

jdk实现动态代理:

jdk的动态代理主要是需要一个类(Proxy)和一个接口(InvocationHandler),Proxy主要是用来生成class文件并加载到内存(这个后面会详细讲到),InvocationHandler主要是调用代理方法。

以下是代码:

//目标接口
public interface TargetInterface {

    void print();

}
//目标实现类
public class Target implements TargetInterface {
    @Override
    public void print() {
        System.out.println("打印的主要内容");
    }
}
//代理类
public class Agent implements InvocationHandler {

    private TargetInterface target;

    public Object getInstance(TargetInterface target) {
        this.target = target;
        Class clazz = target.getClass();
        System.out.println("clazz:"+clazz);
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy:"+proxy.getClass());
        System.out.println("打印前...");
        method.invoke(target,args);
        System.out.println("打印后...");
        return null;
    }
}
//测试类
public class Test {
    public static void main(String[] args) throws IOException {
        TargetInterface target = (TargetInterface) new Agent().getInstance(new Target());
        System.out.println("target:"+target.getClass());
        target.print();
    }
}

这个是个非常简单的测试,看到这里的调用不知道原理肯定还是会很懵逼的,所以下面对代码进行分析。

修改Test方法,这样是获取jdk动态代理生成的$Proxy0对象,具体的生成方法就不详细介绍了,其实就是四点,1、写一个$Proxy0.java的文件。2、调用jdk的方法对这个文件进行编译生成class文件。3、利用反射将这个class文件生成对象并加载。4、删除.java及.class文件。

//测试类
public class Test {
    public static void main(String[] args) throws IOException {
        TargetInterface target = (TargetInterface) new Agent().getInstance(new Target());
        System.out.println("target:"+target.getClass());
        target.print();


        byte[] datas = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{TargetInterface.class});
        FileOutputStream f=new FileOutputStream("$Proxy0.class");
        f.write(datas);
        f.flush();
        f.close();
    }
}

这样在项目的根目录下会生成一个$Proxy.class文件,打开这个文件

public final class $Proxy0 extends Proxy implements TargetInterface {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void print() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);//重点
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.cf.design.proxy.jdk.TargetInterface").getMethod("print");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

这个类继承Proxy,并实现了目标接口类,重点处显示的是调用了父类的h.invoke(),也就是Proxy的h.invoke(),查看Proxy可以发现h就是InvocationHandler,所以就是为什么代理类Agent需要实现InvocationHandler了,其实调用的就是Agent的invoke()。

cglib实现动态代理:

jdk动态代理机制只能代理实现接口的类,一般没有实现接口的类不能进行代理,cglib就是针对类来实现代理的,它的原理是对指定的目标类生成它的一个子类,并覆盖其中方法实现增强,但因为采用的事集成,所以不能对final修饰的类进行代理。

cglib代理和jdk代理的区别:

1、cglib主要是针对类来进行代理,而jdk代理必须要有接口。

2、cglib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用java反射效率更高。

需要导入两个包:cglib.jar、asm.jar

下面是测试代码:

//目标类
public class Target {
    public void print(){
        System.out.println("打印的内容");
    }
}
//代理类
public class Agent implements MethodInterceptor {

    public Object getInstance(){
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(Target.class);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("打印之前...");
        methodProxy.invokeSuper(o,objects);
        System.out.println("打印之后...");
        return null;
    }
}
//测试类
public class Test {
    public static void main(String[] args){
        Target target= (Target) new Agent().getInstance();
        target.print();
    }
}

总结

刚开始看动态代理的时候觉得特别难以理解,通过对源码的分析后才发现原来是这么回事,也让自己收获了很多开发上的技巧,比如通过IO去写一个java文件,再编译成class文件,再使用反射成为对象,加载进内存,以前就从来没想过还可以这么干,所以技术还是需要灵活运用。

 

 

你可能感兴趣的:(设计模式)