Java动态代理详解:JDK和CGLIB的区别和实现

目录

一、 JDK和CGLIB动态代理原理

二、InvocationHandler接口详解

三、Proxy类详解

四、 代码示例

五、JDK和CGLIB动态代理总结


一、 JDK和CGLIB动态代理原理

简单来说:

  JDK动态代理只能对实现了接口的类生成代理,而不能针对类;

  CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承);

1、JDK动态代理
      利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

2、CGLiB动态代理
      利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

3、何时使用JDK还是CGLiB?

  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
  • 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

4、如何强制使用CGLIB实现AOP?

  • 添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
  • 在Spring配置文件中加入

5、JDK动态代理和CGLIB字节码生成的区别?

  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。

6、CGlib比JDK快?

  • 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
  • 在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。

7、Spring在选择用JDK还是CGLiB的依据:

  • 当Bean实现接口时,Spring会用JDK的动态代理。
  • 当Bean没有实现接口时,Spring使用CGlib是实现。
  • 如果Bean实现了接口,强制使用CGlib时,(添加CGLIB库,在spring配置中加入)。

二、InvocationHandler接口详解

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
看下官方文档对InvocationHandler接口的描述:

     {@code InvocationHandler} is the interface implemented by
     the invocation handler of a proxy instance.

     

Each proxy instance has an associated invocation handler.      When a method is invoked on a proxy instance, the method      invocation is encoded and dispatched to the {@code invoke}      method of its invocation handler.

每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:

    /**
    * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
    * method:我们所要调用某个对象真实的方法的Method对象
    * args:指代代理对象方法传递的参数
    */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

三、Proxy类详解

Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。

    public static Object newProxyInstance(ClassLoader loader, 
                                            Class[] interfaces, 
                                            InvocationHandler h)

这个方法的作用就是创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:

  • loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
  • interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
  • h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。

四、 代码示例

接口:

package com.proxy;

/**
 * 用户管理接口
 */
public interface UserManager {
    //新增用户抽象方法
    void addUser(String userName,String password);
    //删除用户抽象方法
    void delUser(String userName);
}

实现类:

package com.proxy;

/**
 * 用户管理实现类,实现用户管理接口(被代理的实现类)
 */
public class UserManagerImpl implements UserManager{

    //重写用户新增方法
    @Override
    public void addUser(String userName, String password) {
        System.out.println("调用了用户新增的方法!");
        System.out.println("传入参数:\nuserName = " + userName +", password = " + password);
    }

    //重写删除用户方法
    @Override
    public void delUser(String userName) {
        System.out.println("调用了删除的方法!");
        System.out.println("传入参数:\nuserName = "+userName);
    }
}

JDK代理实现:

package com.proxy.JDKProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK动态代理实现InvocationHandler接口
 */
public class JdkProxy implements InvocationHandler {

    private Object targetObject;  //需要代理的目标对象


    //定义获取代理对象的方法(将目标对象传入进行代理)
    public Object getJDKProxy(Object targetObject){
        //为目标对象target赋值
        this.targetObject = targetObject;
        //JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出
        Object proxyObject = Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
        //返回代理对象
        return proxyObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理,监听开始!");
        // 调用invoke方法,result存储该方法的返回值
        Object result = method.invoke(targetObject,args);
        System.out.println("JDK动态代理,监听结束!");
        return result;
    }

//    public static void main(String[] args) {
//        JdkProxy jdkProxy = new JdkProxy();  //实例化JDKProxy对象
//        UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());   //获取代理对象
//        user.addUser("admin","123456");
//    }
}

CGLIB代理实现:

package com.proxy.CglibProxy;

import com.proxy.UserManager;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Cglib动态代理:
 * (需要导入两个jar包,asm-5.0.3.jar,cglib-3.1.jar 版本可自行选择)
 */

//Cglib动态代理,实现MethodInterceptor接口
public class CglibProxy implements MethodInterceptor {
    private Object target;//需要代理的目标对象

    //重写拦截方法
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("Cglib动态代理,监听开始!");
        Object result = method.invoke(target,args);//方法执行参数:target 目标对象 arr参数数组
        System.out.println("Cglib动态代理,监听结束!");
        return result;
    }

    //定义获取代理对象的方法
    public UserManager getCglibProxy(Object targetObject) {
        this.target = targetObject;//为目标对象target赋值
        Enhancer enhancer = new Enhancer();
        //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
        enhancer.setSuperclass(targetObject.getClass()); //UserManagerImpl
        enhancer.setCallback(this);//设置回调
        Object result = enhancer.create();//创建并返回代理对象
        return (UserManager) result;
    }


//    public static void main(String[] args) {
//        CglibProxy cglibProxy = new CglibProxy(); //实例化CglibProxy对象
//        UserManager user = cglibProxy.getCglibProxy(new UserManagerImpl());//获取代理对象
//        user.delUser("admin");
//    }

}

客户端测试类:

package com.proxy;

import com.proxy.CglibProxy.CglibProxy;
import com.proxy.JDKProxy.JdkProxy;

public class ClientTest {
    public static void main(String[] args) {

        JdkProxy jdkProxy = new JdkProxy();  //实例化JDKProxy对象
        UserManager userJdk = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());   //获取代理对象
        userJdk.addUser("admin","123456");

        CglibProxy cglibProxy = new CglibProxy(); //实例化CglibProxy对象
        UserManager userCglib = cglibProxy.getCglibProxy(new UserManagerImpl());//获取代理对象
        userCglib.delUser("admin");
    }
}

运行结果:

JDK动态代理,监听开始!
调用了用户新增的方法!
传入参数:
userName = admin, password = 123456
JDK动态代理,监听结束!
Cglib动态代理,监听开始!
调用了删除的方法!
传入参数:
userName = admin
Cglib动态代理,监听结束!

五、JDK和CGLIB动态代理总结

1. JDK动态代理只能对实现了接口的类生成代理,而不能针对类 ,使用的是 Java反射技术实现,生成类的过程比较高效。
2. CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,使用asm字节码框架实现,相关执行的过程比较高效,生成类的过程可以利用缓存弥补,因为是继承,所以该类或方法最好不要声明成final 

3. JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:

  • 实现InvocationHandler 
  • 使用Proxy.newProxyInstance产生代理对象
  • 被代理的对象必须要实现接口

4. CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理;
 

 

参考:
JDK和CGLIB动态代理区别 

Spring的两种动态代理:JDK和CGLIB的区别和实现
 

你可能感兴趣的:(Java)