国内最全IT社区平台 联系我们 | 收藏本站
华晨云阿里云优惠2
您当前位置:首页 > php开源 > php教程 > Java动态代理

Java动态代理

来源:程序员人生   发布时间:2017-03-10 10:16:53 阅读次数:9090次

学习Java的同学注意了!!! 
学习进程中遇到甚么问题或想获得学习资源的话,欢迎加入Java学习交换群,群号码:183993990  我们1起学Java!


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

接口类: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:这个的功能是很简单的就是记录1下方法履行的开始时间和结束时间

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下,不过它其实不给你面子 结果是满屏幕的异常:

复制代码
 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点的话可以直接写匿名类:

复制代码
 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);中第1个参数是传入的是目标对象 那末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个构造方法:

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

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

我第1个想到的是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();

 所以1开始的毛病是对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                     }
复制代码

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

当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下就会发现:

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的原因:

注意1下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方法中第1个参数proxy貌似只是为了让Proxy类能给自己的InvocationHandler对象的援用调用方法时能传入代理对象proxyClass的援用,来完成proxyClass需要完成的业务。

学习Java的同学注意了!!! 
学习进程中遇到甚么问题或想获得学习资源的话,欢迎加入Java学习交换群,群号码:183993990  我们1起学Java!

生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
程序员人生
------分隔线----------------------------
分享到:
------分隔线----------------------------
关闭
程序员人生