动态代理的两种实现方式

JDK动态代理

利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

CGlib动态代理

利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

区别

JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。

方式一:JDK动态代理

目标接口类

/**
 * 目标接口类
 */
public interface UserManager {
    void addUser(String username, String password);
    void delUser(String username);
}

接口实现类

/**
 * 动态代理:
 *      1. 特点:字节码随用随创建,随用随加载
 *      2. 作用:不修改源码的基础上对方法增强
 *      3. 分类:
 *              1)基于接口的动态代理
 *                      1. 基于接口的动态代理:
 *                              1)涉及的类:Proxy
 *                              2)提供者:JDK官方
 *                              3)如何创建代理对象:
 *                                      使用Proxy类中的newProxyInstance方法
 *                              4)创建代理对象的要求
 *                                      被代理类最少实现一个接口,如果没有则不能使用
 *                              5)newProxyInstance方法的参数:
 *                                      ClassLoader:类加载器,它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
 *                                      Class[]:字节码数组,它是用于让代理对象和被代理对象有相同方法。固定写法。
 *                                      InvocationHandler:用于提供增强的代码,它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类
 *              2)基于子类的动态代理
 */
public class JDKProxy implements InvocationHandler {
    // 用于指向被代理对象
    private Object targetObject;
    public Object newProxy(Object targetObject) {
        // 将被代理对象传入进行代理
        this.targetObject = targetObject;
        // 返回代理对象
        return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),this.targetObject.getClass().getInterfaces(),this);
    }

    /**
     * 被代理对象的任何方法执行时,都会被invoke方法替换,即:代理对象执行被代理对象中的任何方法时,实际上执行的时当前的invoke方法
     * @param proxy(代理对象的引用)
     * @param method(当前执行的方法)
     * @param args(当前执行方法所需的参数)
     * @return(和被代理对象方法有相同的返回值)
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在原来的方法上增加了日志打印功能,增强代码
        printLog();
        Object ret = null;
        // 调用invoke方法(即执行了代理对象调用被调用对象中的某个方法)
        ret = method.invoke(targetObject, args);
        return ret;
    }

    /**
     * 模拟日志打印
     */
    private void printLog() {
        System.out.println("日志打印:printLog()");
    }
}

测试类

public class TestJDKProxy {
    public static void main(String[] args) {
        UserManager userManager = new UserManagerImpl();
        JDKProxy jdkProxy = new JDKProxy();
        UserManager userManagerProxy = (UserManager)jdkProxy.newProxy(userManager);
        System.out.println("--------------------没有使用增强过的方法--------------------");
        userManager.addUser("root","root");
        userManager.delUser("root");
        System.out.println("--------------------使用代理对象增强过的方法--------------------");
        userManagerProxy.addUser("scott","tiger");
        userManagerProxy.delUser("scott");
    }
}

测试结果

--------------------没有使用增强过的方法--------------------
调用了UserManagerImpl.addUser()方法!
调用了UserManagerImpl.delUser()方法!
--------------------使用代理对象增强过的方法--------------------
日志打印:printLog()
调用了UserManagerImpl.addUser()方法!
日志打印:printLog()
调用了UserManagerImpl.delUser()方法!

方式二:CGlib动态代理

/**
 * 动态代理:
 *      1. 特点:字节码随用随创建,随用随加载
 *      2. 作用:不修改源码的基础上对方法增强
 *      3. 分类:
 *              1)基于接口的动态代理
 *              2)基于子类的动态代理
 *                      1. 基于子类的动态代理:
 *                              1)涉及的类:Enhancer
 *                              2)提供者:第三方cglib库
 *                              3)如何创建代理对象:
 *                                      使用Enhancer类中的create方法
 *                              4)创建代理对象的要求
 *                                      被代理类不能是最终类
 *                              5)create方法的参数:
 *                                      Class:字节码,它是用于指定被代理对象的字节码。固定写法。
 *                                      Callback():用于提供增强的代码,它是让我们写如何代理。我们一般都是些一个该接口的实现类。固定写法。
 */
public class CGLibProxy implements MethodInterceptor {
    // 用于指向被代理对象
    private Object targetObject;

    // 用于创建代理对象
    public Object createProxy(Object targetObject) {
        this.targetObject = targetObject;
        return new Enhancer().create(this.targetObject.getClass(),this);
    }

    /**
     * 
     * @param proxy(代理对象的引用)
     * @param method(当前执行的方法)
     * @param args(当前执行方法所需的参数)
     * @param methodProxy(当前执行方法的代理对象)
     * @return(和被代理对象方法有相同的返回值)
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object ret = null;
        // 过滤方法
        if ("addUser".equals(method.getName())) {
            // 日志打印
            printLog();
        }
        ret = method.invoke(targetObject, args);
        return ret;
    }

    /**
     * 模拟日志打印
     */
    private void printLog() {
        System.out.println("日志打印:printLog()");
    }
}

测试类

public class TestCGLibProxy {
    public static void main(String[] args) {
        CGLibProxy cgLibProxy = new CGLibProxy();
        UserManager userManager = new UserManagerImpl();
        UserManager cgLibProxyProxy = (UserManager)cgLibProxy.createProxy(userManager);
        System.out.println("--------------------没有使用增强过的方法--------------------");
        userManager.addUser("root","root");
        userManager.delUser("root");
        System.out.println("--------------------使用代理对象增强过的方法--------------------");
        cgLibProxyProxy.addUser("scott","tiger");
        cgLibProxyProxy.delUser("scott");
    }
}

测试结果

--------------------没有使用增强过的方法--------------------
调用了UserManagerImpl.addUser()方法!
调用了UserManagerImpl.delUser()方法!
--------------------使用代理对象增强过的方法--------------------
日志打印:printLog()
调用了UserManagerImpl.addUser()方法!
调用了UserManagerImpl.delUser()方法!

总结

1)JDK代理使用的是反射机制实现aop的动态代理,CGLIB代理使用字节码处理框架asm,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,cglib创建效率较低,执行效率高;
2)JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLIB则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。

原文链接:https://blog.csdn.net/weixin_...

你可能感兴趣的:(动态代理)