Spring JDK动态代理与Cglib动态对比与选择

在开发中,经常遇到代理问题,尤其是动态代理,在这里,本人对Java中的动态代理做一个小结。 在工作中,我们发现,当对所有业务类都需要打日志时,我们有两种方案:

  1. 在每个类中加入日志代码(每个类都写一次,累不累?!);
  2. 实现动态代理,只需要写一次日志代码就搞定了(对于我这种懒人来说,当然是这种了!); 有的人会说,那直接使用Spring的AOP不就行了么? 答案当时是:可以的!但是,你知道AOP是怎么实现的么? AOP实际上就是动态代理,而且可选的动态代理还有两种,一种是JDK自带的代理,另一种是Cglib(多以Cglib为主)。 那么在将动态代理之前,先来一个小例子帮助大家理解,不多说,直接上代码:
//业务接口类:
public interface OrderService {

String getOrder();

long getOrderId();

}


//业务实现类:
public class OrderServiceImpl implements OrderService {
  @Override
  public String getOrder() {
    System.out.println("------getOrder-----");
    return "order_123456";
  }

  @Override
  public long getOrderId() {
    System.out.println("------getOrderId-----");
    return 123456;
  }
}

对于这个业务,我们需要进行代理,在每次执行getOrder方法的时候,我们需要进行其他一些操作,那么我试着去代理一下:

public class OrderServiceStaticProxy {

  private OrderService orderService;

  OrderServiceStaticProxy(OrderService orderService) {
    this.orderService = orderService;
  }

  public String getOrder() {
    System.out.println("------before getOrder-----");
    String result = orderService.getOrder();
    System.out.println("------after getOrder-----");
    return result;
  }

  public long getOrderId() {
    return orderService.getOrderId();
  }
  
}

测试类:

public class StaticProxyTest {

  public static void main(String[] args) {
    OrderService orderService = new OrderServiceImpl();
    OrderServiceStaticProxy orderServiceProxy
            = new OrderServiceStaticProxy(orderService);
    System.out.println(orderServiceProxy.getOrder());
    System.out.println(orderServiceProxy.getOrderId());
  }

}

输出:

------before getOrder-----
------getOrder-----
------after getOrder-----
order_123456
------getOrderId-----
123456

好了,我们已经成功为业务实现了一个代理了! 这是一个静态代理,OrderServiceStaticProxy类通过持有OrderService对象,来实现对OrderService所有方法的代理,比如控制在执行方法前后打印日志等。但是这种方法的缺点也很明显,就是我每写一个类,再增加一个代理类,有100个,我就写100次,有1000个,我就写1000次!!!程序员当然是要以最小的代价来完成工作!所以,我如何能只写一遍代理,然后就直接使用就可以了呢? 好了,正片开始了

1. JDK动态代理

废话不多说,直接上代码,代码之下,无所遁形:

public class OrderServiceProxy implements InvocationHandler {


  private Object target;

  OrderServiceProxy() {
    super();
  }

  OrderServiceProxy(Object target) {
    super();
    this.target = target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object result;
    if (Objects.equals("getOrder", method.getName())) {
      System.out.println("-------before-------");
      result = method.invoke(target, args);
      System.out.println("-------after-------");
    } else {
      result = method.invoke(target, args);
    }
    return result;
  }
}


public class ProxyTest {

  public static void main(String[] args) {

    OrderServiceImpl orderService = new OrderServiceImpl();
    InvocationHandler invocationHandler = new OrderServiceProxy(orderService);

    OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(
            orderService.getClass().getClassLoader(),
            orderService.getClass().getInterfaces(),
            invocationHandler);

    System.out.println(orderServiceProxy.getOrder());
    System.out.println(orderServiceProxy.getOrderId());

  }

}

输出:

-------before-------
------getOrder-----
-------after-------
order_123456
------getOrderId-----
123456

在讲解这段代码之前,我先简单讲一个JVM中的加载机制。 总得来说,就是我们写的java代码,机器是不认识的,所有需要编译,转成二进制文件,就是编译完后的.class文件。那么,这些二进制文件是我们一启动JVM就加载进去了么?答案是:否。这些二进制文件只有在被调用的时候才会由相应的ClassLoader加载进JVM中,这时候,才能真正有了对象供程序调用。好,加载机制先了解到这里(详细的过程,可查阅JVM相关的资料,这里不细说) 先说OrderServiceProxy,它实现了InvocationHandler,InvocationHandler中有一个方法invoke(),invoke()中三个参数,分别是:需要代理的对象proxy,代理对象的方法method,以及需要的参数args。什么意思呢?就是说,假如我现在知道了我需要代理对象、方法、参数了,我不就可以根据判断来就执行我需要额外执行业务了吗?然后通过method.invoke(target, args)来调用代理对象的方法了,至于method.invoke(target, args)这里面的实现逻辑,我们是不用关心的,因为里头其实就是写如何找到相关的类,执行相关方法的流程,JDK都已经封装好了。 然后,再通过Proxy.newProxyInstance()来生成一个代理类,这个代理类,实际上是调用invoke()方法来执行target(即代理对象)的方法。 如果想了解Proxy具体原理的话,往下看:

public class Proxy implements java.io.Serializable {
    ...
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * 1.通过ClassLoader和接口来查找接口类
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * 2.通过接口来获取构造对象
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            /*
             * 3.通过构造对象和InvocationHandler来构造实例,并返回
             */
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
}

具体来讲就三个步骤:

  1. 根据ClassLoader和Interface来获取接口类(前面已经讲了,类是由ClassLoader加载到JVM的,所以通过ClassLoader和Interface可以找到接口类)

  2. 获取构造对象;

  3. 通过构造对象和InvocationHandler生成实例,并返回,就是我们要的代理类。 Java动态代理优缺点: 优点:

  4. Java本身支持,不用担心依赖问题,随着版本稳定升级;

  5. 代码实现简单; 缺点:

  6. 目标类必须实现某个接口,换言之,没有实现接口的类是不能生成代理对象的;

  7. 代理的方法必须都声明在接口中,否则,无法代理;

  8. 执行速度性能相对cglib较低;

    2.Cglib动态代理

    使用cglib代理需要添加jar依赖:

      <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>2.2.2</version>
      </dependency>
    

    同样,也是直接上代码: “`java public class CglibProxy implements MethodInterceptor {

    @Override public Object intercept(Object proxy, Method method, Object[] params, MethodProxy methodProxy)

        throws Throwable {
    

    Object result; if (Objects.equals(“getOrder”, method.getName())) {

    System.out.println("----before-----");
    result = methodProxy.invokeSuper(proxy, params);
    System.out.println("-----after-----");
    

    } else {

    result = methodProxy.invokeSuper(proxy, params);
    

    } return result; }

    public Object getProxy(Object target) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); enhancer.setClassLoader(target.getClass().getClassLoader()); return enhancer.create(); }

}

```java
public class CglibTest {

  public static void main(String[] args) {

    OrderServiceImpl orderService = new OrderServiceImpl();
    CglibProxy proxy = new CglibProxy();

    OrderServiceImpl orderServiceProxy 
            = (OrderServiceImpl) proxy.getProxy(orderService);
    System.out.println(orderServiceProxy.getOrder());
    System.out.println(orderServiceProxy.getOrderId());

  }

}

输出:

------getOrder-----
-----after-----
order_123456
------getOrderId-----
123456

Cglib原理:

1.通过字节码增强技术动态的创建代理对象;

2.代理的是代理对象的引用;

Cglib优缺点:

优点: 1.代理的类无需实现接口; 2.执行速度相对JDK动态代理较高;

缺点: 1.字节码库需要进行更新以保证在新版java上能运行; 2.动态创建代理对象的代价相对JDK动态代理较高;

Tips: 1.代理的对象不能是final关键字修饰的

请登录后发表评论

    没有回复内容