• java 动态代理


    今天一个偶然的机会我突然想看看JDK的动态代理,因为以前也知道一点,而且只是简单的想测试一下使用,使用很快里就写好了这么几个接口和类:

    接口类:UserService.java

    1 package com.yixi.proxy;
    2 
    3 public interface UserService {
    4 
    5     public int save() ;
    6     
    7     public void update(int id);
    8     
    9 }

    实现类:UserServiceImpl.java

     1 package com.yixi.proxy;
     2 
     3 public class UserServiceImpl implements UserService {
     4 
     5     @Override
     6     public int save() {
     7         System.out.println("user save....");
     8         return 1;
     9     }
    10 
    11     @Override
    12     public void update(int id) {
    13         System.out.println("update a user " + id);
    14     }
    15 
    16 }

    然后猴急猴急的就写好了自己要的InvocationHandler:这个的功能是很简单的就是记录一下方法执行的开始时间和结束时间

    TimeInvocationHandler.java

     1 package com.yixi.proxy;
     2 
     3 import java.lang.reflect.InvocationHandler;
     4 import java.lang.reflect.Method;
     5 
     6 public class TimeInvocationHandler implements InvocationHandler {
     7     
     8     @Override
     9     public Object invoke(Object proxy, Method method, Object[] args)
    10             throws Throwable {
    11         System.out.println("startTime : " +System.currentTimeMillis());
    12         Object obj = method.invoke(proxy, args);
    13         System.out.println("endTime : " +System.currentTimeMillis());
    14         return obj;
    15     }
    16 
    17 }

    所有的准备工作都弄好了 当然要开始写测试了!

    Test.java

     1  package com.yixi.proxy;
     2  import java.lang.reflect.Proxy;
     3   
     4   public class Test {
     5   
     6      public static void main(String[] args) { 
     7          TimeInvocationHandler timeHandler = new TimeInvocationHandler();
     8          UserService u =  (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
     9          u.update(2);
    10          u.save();
    11      }
    12  }

    愉快地Run了一下,不过它并不给你面子 结果是满屏幕的异常:

     1 startTime : 1352877835040
     2 startTime : 1352877835040
     3 startTime : 1352877835040
     4 Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
     5     at $Proxy0.update(Unknown Source)
     6     at com.yixi.proxy.Test.main(Test.java:11)
     7 Caused by: java.lang.reflect.InvocationTargetException
     8     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     9     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    10     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    11     at java.lang.reflect.Method.invoke(Method.java:597)
    12     at com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)
    13     ... 2 more

    com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)

    异常明确告诉了是在TimeInvocationHandle的12行的问题:也就是

    1 public Object invoke(Object proxy, Method method, Object[] args)
    2             throws Throwable {
    3         System.out.println("startTime : " +System.currentTimeMillis());
    4         Object obj = method.invoke(proxy, args);
    5         System.out.println("endTime : " +System.currentTimeMillis());
    6         return obj;
    7     }

    从方法上来看没什么错误啊!因为在invoke()这个方法上貌似提供了method.invoke(Object,Object[])所要的所有的参数,我们会理所应当的去使用它,如果你真那样想的话 那你就中了JDK的陷阱了,先看下正确的写法吧 防止有些同学没心情看后面的 至少给个正确的解法:

    修改TimeInvocationHandler.java

     1 package com.yixi.proxy;
     2 
     3 import java.lang.reflect.InvocationHandler;
     4 import java.lang.reflect.Method;
     5 
     6 public class TimeInvocationHandler implements InvocationHandler {
     7     
     8     private Object o;
     9     
    10     public TimeInvocationHandler(Object o){
    11         this.o = o;
    12     }
    13     
    14     @Override
    15     public Object invoke(Object proxy, Method method, Object[] args)
    16             throws Throwable {
    17         System.out.println("startTime : " +System.currentTimeMillis());
    18         Object obj = method.invoke(o, args);
    19         System.out.println("endTime : " +System.currentTimeMillis());
    20         return obj;
    21     }
    22 
    23 }

    修改Test.java

     1 package com.yixi.proxy;
     2 
     3 import java.lang.reflect.Proxy;
     4 
     5 public class Test {
     6 
     7     public static void main(String[] args) {
     8         TimeInvocationHandler timeHandler = new TimeInvocationHandler(new UserServiceImpl());
     9         UserService u =  (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
    10         u.update(2);
    11         u.save();
    12     }
    13 }

    现在是正确的输出结果:

    1 startTime : 1352879531334
    2 update a user 2
    3 endTime : 1352879531334
    4 startTime : 1352879531334
    5 user save....
    6 endTime : 1352879531335

    如果想代码少一点的话可以直接写匿名类:

     1 package com.yixi.proxy;
     2 
     3 import java.lang.reflect.InvocationHandler;
     4 import java.lang.reflect.Method;
     5 import java.lang.reflect.Proxy;
     6 
     7 public class Test {
     8 
     9     public static void main(String[] args) {
    10         final UserServiceImpl usi = new UserServiceImpl();
    11         UserService u =  (UserService) Proxy.newProxyInstance(
    12                 usi.getClass().getClassLoader(),
    13                 usi.getClass().getInterfaces(),
    14                 new InvocationHandler() {
    15                     
    16                     @Override
    17                     public Object invoke(Object proxy, Method method, Object[] args)
    18                             throws Throwable {
    19                         System.out.println("startTime : " +System.currentTimeMillis());
    20                         Object obj = method.invoke(usi, args);
    21                         System.out.println("endTime : " +System.currentTimeMillis());
    22                         return obj;
    23                     }
    24                 });
    25         u.update(2);
    26         u.save();
    27     }
    28 }

    既然method.invoke(target,args);中第一个参数是传入的是目标对象 那么invocationHandler的Invoke方法要个Object proxy参数干嘛呢 ? 还是往下看吧!

    对于最重要的invoke这个方法(个人觉得)我们看下JDK是怎么说的吧:

     1 invoke
     2 Object invoke(Object proxy,
     3               Method method,
     4               Object[] args)
     5               throws Throwable在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。 
     6 
     7 参数:
     8 proxy - 在其上调用方法的代理实例
     9 method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
    10 args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。 

    proxy - 在其上调用方法的代理实例 ? 这句话是什么意思呢? 代理? method是代理的方法? 那我执行代理的method不是就应该是Object obj = method.invoke(proxy, args);吗? 当时我也没转过弯来,去讨论群,去google都没找到什么灵感,想想还是这个看看源码吧 也许能看到点什么!

    打开Proxy类的源码发现有这么一个构造方法:

    1 protected InvocationHandler h;
    2 
    3 protected Proxy(InvocationHandler h) {
    4     this.h = h;
    5     }

    把InvocationHandler作为Proxy的构造方法的参数....那它要InvocationHandler干什么用呢?跟InvocationHandler中的invoke()方法有什么联系吗?

    我第一个想到的是Proxy内部会调用下面的语句:

    1 h.invoke(this,this.getClass().getMethod(methodName),args);

    因为总得去调用invoke方法才能执行相应的method方法吧,

    我们先来看下这个

    在这里你就会发现貌似有点感觉了:当u.update(2)时  Proxy就会调用 handler.invoke(proxyClass,update,2)  也就是调用了proxyClass.update(2);

    当u.save();时 Proxy就会调用handler.invoke(proxyClass,save,null)  也就是调用了proxyClass.save();

     所以一开始的错误是对InvocationHandler的invoke方法的理解的错误! 整个的invoke()方法

    1                     @Override
    2                     public Object invoke(Object proxy, Method method, Object[] args)
    3                             throws Throwable {
    4                         System.out.println("startTime : " +System.currentTimeMillis());
    5                         Object obj = method.invoke(usi, args);
    6                         System.out.println("endTime : " +System.currentTimeMillis());
    7                         return obj;
    8                     }

    其实就是代理对象的一个代理方法,执行代理对象的一个方法就会访问一次invoke()方法;在invoke方法中的Object obj = method.invoke(usi, args); 是按原对象本应该执行的方式执行,该返回什么就返回什么。不知道你能想到点什么啵。下面来验证一下:

    当Test.java改成这样时:

     1 public class Test {
     2 
     3     public static void main(String[] args) {
     4         final UserServiceImpl usi = new UserServiceImpl();
     5         UserService u =  (UserService) Proxy.newProxyInstance(
     6                 usi.getClass().getClassLoader(),
     7                 usi.getClass().getInterfaces(),
     8                 new InvocationHandler() {
     9                     
    10                     @Override
    11                     public Object invoke(Object proxy, Method method, Object[] args)
    12                             throws Throwable {
    13                         return null;
    14                     }
    15                 });
    16         u.update(2);
    17         u.save();
    18     }
    19 }

    注意这时候的匿名类的方法的返回的是null,运行一下就会发现:

    1 Exception in thread "main" java.lang.NullPointerException
    2     at $Proxy0.save(Unknown Source)
    3     at com.yixi.proxy.Test.main(Test.java:17)

    17行有空指针 也就是这里的u.save()方法有为null的元素 难道是u是空的? 不应该啊如果u是null的话那么u.update(2)在那里就会报空指针异常了,当我把17行注释掉以后异常没了说明u.update()能正常执行。那这到底是为什么呢?

    其实这就是invoke方法返回null的缘故:

    注意一下UserService类中的两个方法:

    1 public interface UserService {
    2 
    3     public int save() ;
    4     
    5     public void update(int id);
    6     
    7 }

    Save()方法返回的是int型的 而update方法返回的是void型的;根据上面的猜测是 handler.invoke()是实现 proxyClass.update(2);的,invoke方法中的return方法的是相应的代理方法的返回值,

    所以在invoke方法返回null的时候代理的update方法接收到返回值是null, 而它本来就是返回void 所以没有报异常, 而代理save必须返回int型的数值 我们这返回的还是null,JVM无法将null转化为int型 所以就报了异常了

    这样解释就能解释通了,也能相对证明前面的猜测。

    InvocationHandler中invoke方法中第一个参数proxy貌似只是为了让Proxy类能给自己的InvocationHandler对象的引用调用方法时能传入代理对象proxyClass的引用,来完成proxyClass需要完成的业务。

    文采不行!能力有限!希望大家指正...

     

  • 相关阅读:
    经典的Java基础面试题集锦
    2016春招Android开发实习生(网易传媒)笔试
    十三、集合点和事务
    十一、LoadRunner组成和工作原理
    Java+selenium之WebDriver常见特殊情况如iframe/弹窗处理(四)
    修改jar包内容并打包上传到私服
    Information:java: Multiple encodings set for module chunk platf "GBK" will be used by compile
    十、创建、运行和监控测试场景
    在gitlab新建分支,IDEA切换时找不到的解决办法
    Git 代码撤销、回滚到任意版本(当误提代码到本地或master分支时)
  • 原文地址:https://www.cnblogs.com/yixiwenwen/p/2770068.html
Copyright © 2020-2023  润新知