基础 - JAVA动态代理(一)


简介
在JAVA中,代理模式应用非常广泛,在很多框架中或平时业务中都经常使用,动态代理其实没什么神秘的,只要搞懂静态代理,在深入思考下,动态代理马上就能搞明白。

静态代理
代理类一般跟目标类又共同的接口,代理类不会改变目标类逻辑,只会在目标类行为之前或之后做一些事情。我们一般不会直接访问有代理类的对象,而是访问它的代理类对象。

例如:
我家养了一只白猫了,每天喜欢睡觉。现在构建一个类
猫接口:

public interface Cat {
    /** 睡觉 **/
    void sleep();
}

白猫:

public class WhiteCat {
    void sleep() {
        ...
    }
}

现在我要记录一下白猫每天睡觉时间。如果你第一个想到的是直接修改WhiteCat类

void sleep() {
    long start = System.currentTimeMillis();
    ...
    long end = System.currentTimeMillis();
    System.out.println(end - start);
}

如果你真这么想的,请你再去学习一下设计模式7大原则。
比这优雅的办法是继承。

public class WhiteCatTmp extends WhiteCat {
    void sleep() {
        long start = System.currentTimeMillis();
        super.sleep();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

这样写是没问题,但是只针对白猫的,继承的方式非常不利于扩展。例如:现在我有领养了了几只猫,然后就需要写很多WhiteCatTmp1,WhiteCatTmp2。。。
有没有更优雅的写法?不用继承,直接使用Cat对象,不管领养多少猫,只要是猫就能满足。

public class CatTmp implements Cat {
    private Cat cat;
    CatTmp(Cat cat) {
        this.cat = cat;
    }
    void sleep() {
        long start = System.currentTimeMillis();
        cat.sleep();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

现在通用了,只要是猫都可以用这个类了。这跟代理有什么关系呢?稍等我改个名字。

public class CatProxy implements Cat {
    private Cat target;
    CatProxy(Cat cat) {
        this.target = cat;
    }
    void sleep() {
        long start = System.currentTimeMillis();
        target.sleep();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

这可不就是经典的静态代理么。

动态代理
代理类在程序运行时创建的代理方式被成为动态代理。CatProxy是在运行之前就已经编译完成,而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

举个例子:
猫解锁了新技能

public interface Cat {
    /** 拉屎 **/
    void shit();
    /** 吃粮 **/
    void eat();
    /** 睡觉 **/
    void sleep();
}

如果手动创建代理类,我们就需要修改代理类了。如果用动态代理就没有这么麻烦。

class MyInvocationHandler implements InvocationHandler {
    Cat target;
    public MyInvocationHandler(Cat target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        method.invoke(target, args);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        return null;
    }
}

调用

Cat whiteCat = new WhiteCat();
InvocationHandler handler = new MyInvocationHandler(whiteCat);
Cat catProxy = (Cat) Proxy.newProxyInstance(whiteCat.getClass().getClassLoader(), whiteCat.getClass().getInterfaces(), handler);
catProxy.shit();
catProxy.eat();
catProxy.sleep();

JAVA中共有两种动态代理,一种是JDK自带的(上面的例子),还有一种是基于asm的cglib。
原理区别如下:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
如果目标对象没有实现了接口,必须采用CGLIB库

JDK动态代理和CGLIB字节码生成的区别?
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final 。

下一篇,针对JDK中Proxy类做讲解。

posted @ 2020-05-10 16:51  源码猎人  阅读(205)  评论(0编辑  收藏  举报