JDK动态代理

在开发中,经常遇到代理问题,尤其是动态代理,在这里,本人对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次!!!程序员当然是要以最小的代价来完成工作!所以,我如何能只写一遍代理,然后就直接使用就可以了呢?
好了,正片开始了

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(即代理对象)的方法。

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动态代理优缺点:

优点:

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

2.代码实现简单;

缺点:

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

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

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

进阶篇

常见错误1:

在这里插入图片描述
这就是常见的栈溢出,在这里也就是递归调用InvocationHandler的invoke方法问题。产生这个问题的原因,从源码可以读取出来:
在这里插入图片描述
比如你写了一段
常见错误2:
提示实例不是对象的声明类
在这里插入图片描述
导致这个错误的原因是当我们执行method.invoke(参数1,…)方法时候,如果method这个方法,不是invoke参数1的实例方法,就会报这个,如有一个类A和B都是M的接口实现类,用A的method去调用B实例
还有一种特殊情况比如A类的method调用A类的代理类,也会出现这个问题。这个时候,要用A类的接口类方法去调用A类的代理类,就可以避免出现这个错误。
有兴趣的可以看看码云这个GVP项目:
项目地址:https://gitee.com/dromara/koalas-rpc.git
看下面截图:

 public String invoke(GenericRequest request) throws TException {
        //"getRpc"
        String methodName = request.getMethodName ();
        //["java.lang.String","java.lang.Long"]
        List<String> classTypes = request.getClassType ();
        //["4","5"]
        List<String> requestObjs = request.getRequestObj ();
        Class<?> targetImpl = AopUtils.getTargetClass ( realImpl );
        this.realClassName = targetImpl.getName ();
        checkparam(request);

        List<Class> realClassTypes = null;

        if(classTypes!=null && classTypes.size ()>0){
            realClassTypes = new ArrayList<> (  );
            for(String _classTypes:classTypes){
                try {
                    realClassTypes.add (getClassType(_classTypes));
                } catch (ClassNotFoundException e) {
                    throw new TException ("class:" + realClassName +  ",classType:"+_classTypes+ " not found in the server side!" );
                }
            }
        }

        Method method=null;
        List<Object> realRequest = null;
        if(realClassTypes==null || realClassTypes.size ()==0){
            try {
                method = targetImpl.getMethod ( methodName );
            } catch (NoSuchMethodException e) {
                throw new TException ( "class:" + realClassName +  ",method:"+methodName+ " not found !" );
            }
        } else{
            try {
                method = targetImpl.getMethod ( methodName ,realClassTypes.toArray ( new Class[0] ));
            } catch (NoSuchMethodException e) {
                throw new TException ( "class:" + realClassName +  ",method:"+methodName+ " not found !" );
            }
            realRequest = new ArrayList<> (  );

            for(int i =0;i<requestObjs.size ();i++){
                try {
                   Object o= JSONObject.parseObject (requestObjs.get ( i ),realClassTypes.get ( i ));
                   realRequest.add ( o );
                } catch (Exception e) {
                    throw new TException ( "class:" + realClassName +  ",method:"+methodName+ " ,JSONObject.parseObject error, ! text:"+ requestObjs.get ( i ) + ",classType:" + realClassTypes.get ( i ) );
                }
            }
        }

        Object ojb =null;
        if(realClassTypes==null || realClassTypes.size ()==0){
            try {
                ojb = method.invoke ( realImpl );
            } catch (Exception e) {
                Throwable cause = e.getCause ()!=null?e.getCause ():e;
                throw new TException (cause.getMessage ());
            }
        }else{
            try {
                ojb = method.invoke ( realImpl,realRequest.toArray ( new Object[0] ) );
            } catch (Exception e) {
                Throwable cause = e.getCause ()!=null?e.getCause ():e;
                throw new TException (cause);
            }
        }

        if(ojb==null){
            return "VOID";
        } else{
            return JSON.toJSONString ( ojb );
        }
    }

ojb = method.invoke ( realImpl,realRequest.toArray ( new Object[0] ) );
看这行代码,实例中realImpl是spring aop生成的一个代理类,method是WmCreateAccountServiceThriftImpl的方法,它的接口是WmCreateAccountService.Iface
源码中就是用WmCreateAccountServiceThriftImpl调用了realImpl,就会报上面的错误
最后我联系了源码作者修复了该问题,修改的方法也很简单,
在这里插入图片描述

 method = ifaceClass.getMethod ( methodName );

换用接口的方法去调就可以了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值