代理模式之Java动态代理实现方法

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


代码如下:

package com.yixi.proxy;
public interface UserService {
    public int save() ;
    public void update(int id);
}

实现类:UserServiceImpl.java


代码如下:

package com.yixi.proxy;
public class UserServiceImpl implements UserService {
    @Override
    public int save() {
        System.out.println("user save....");
        return 1;
    }
    @Override
    public void update(int id) {
        System.out.println("update a user " + id);
    }
}

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


代码如下:

package com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("startTime : " +System.currentTimeMillis());
        Object obj = method.invoke(proxy, args);
        System.out.println("endTime : " +System.currentTimeMillis());
        return obj;
    }
}

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


代码如下:

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

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


代码如下:

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

com.yixi.proxy.TimeInvocationHandler.invoke(TimeInvocationHandler.java:12)异常明确告诉了是在TimeInvocationHandle的12行的问题:也就是


代码如下:

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

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


代码如下:

package com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeInvocationHandler implements InvocationHandler {
    private Object o;
    public TimeInvocationHandler(Object o){
        this.o = o;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("startTime : " +System.currentTimeMillis());
        Object obj = method.invoke(o, args);
        System.out.println("endTime : " +System.currentTimeMillis());
        return obj;
    }
}

修改Test.java


代码如下:

package com.yixi.proxy;
import java.lang.reflect.Proxy;
public class Test {
    public static void main(String[] args) {
        TimeInvocationHandler timeHandler = new TimeInvocationHandler(new UserServiceImpl());
        UserService u =  (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
        u.update(2);
        u.save();
    }
}

现在是正确的输出结果:


代码如下:

startTime : 1352879531334
update a user 2
endTime : 1352879531334
startTime : 1352879531334
user save....
endTime : 1352879531335

如果想代码少一点的话可以直接写匿名类:
package com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test {
    public static void main(String[] args) {
        final UserServiceImpl usi = new UserServiceImpl();
        UserService u =  (UserService) Proxy.newProxyInstance(
                usi.getClass().getClassLoader(),
                usi.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        System.out.println("startTime : " +System.currentTimeMillis());
                        Object obj = method.invoke(usi, args);
                        System.out.println("endTime : " +System.currentTimeMillis());
                        return obj;
                    }
                });
        u.update(2);
        u.save();
    }
}
既然method.invoke(target,args);中第一个参数是传入的是目标对象 那么invocationHandler的Invoke方法要个Object proxy参数干嘛呢 ? 还是往下看吧!
对于最重要的invoke这个方法(个人觉得)我们看下JDK是怎么说的吧:


代码如下:

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

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


代码如下:

protected InvocationHandler h;

protected Proxy(InvocationHandler h) {
    this.h = h;
    }

把InvocationHandler作为Proxy的构造方法的参数....那它要InvocationHandler干什么用呢?跟InvocationHandler中的invoke()方法有什么联系吗?
我第一个想到的是Proxy内部会调用下面的语句:


代码如下:

h.invoke(this,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();

当Test.java改成这样时:


代码如下:

public class Test {
    public static void main(String[] args) {
        final UserServiceImpl usi = new UserServiceImpl();
        UserService u =  (UserService) Proxy.newProxyInstance(
                usi.getClass().getClassLoader(),
                usi.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        return null;
                    }
                });
        u.update(2);
        u.save();
    }
}

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


代码如下:

Exception in thread "main" java.lang.NullPointerException
    at $Proxy0.save(Unknown Source)
    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类中的两个方法:


代码如下:

public interface UserService {
    public int save() ;
    public void update(int id);
}

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需要完成的业务。

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

(0)

相关推荐

  • Java动态代理的应用详解

    动态代理其实就是java.lang.reflect.Proxy类动态的根据您指定的所有接口生成一个class byte,该class会继承Proxy类,并实现所有你指定的接口(您在参数中传入的接口数组):然后再利用您指定的classloader将 class byte加载进系统,最后生成这样一个类的对象,并初始化该对象的一些值,如invocationHandler,以即所有的接口对应的Method成员. 初始化之后将对象返回给调用的客户端.这样客户端拿到的就是一个实现你所有的接口的Proxy对象

  • java代理模式与动态代理模式详解

    1.代理模式 所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. 生活中的例子:过年加班比较忙,没空去买火车票,这时可以打个电话到附近的票务中心,叫他们帮你买张回家的火车票,当然这会附加额外的劳务费.但要清楚票务中心自己并不卖票,只有火车站才真正卖票,票务中心卖给你的票其实是通过火车站实现的.这点很重要!

  • java实现动态代理方法浅析

    一些Java项目中在mybatis与spring整合中有MapperScannerConfigurer的使用,该类通过反向代理自动生成基于接口的动态代理类. 有鉴于此,本文浅析了java的动态代理. 本文使用动态代理模拟处理事务的拦截器. 接口: public interface UserService { public void addUser(); public void removeUser(); public void searchUser(); } 实现类: public class

  • 基于接口实现java动态代理示例

    Subject.java 复制代码 代码如下: package _20140416_; import java.util.List; public interface Subject {   public String say(String name,int age);   public List<Person> getAllList(String name);} RealSubject.java 复制代码 代码如下: package _20140416_; import java.util.

  • java动态代理和cglib动态代理示例分享

    java动态代理类可以分为两种. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经存在了. 动态代理:在程序运行时,运用反射机制动态创建而成. 一.首先我们进行java动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: 复制代码 代码如下: package testAOP;public interface Saying {public void sayHello(String name);    public void ta

  • 十分钟理解Java中的动态代理

    若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的. 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类. 一.概述 1. 什么是代理 我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家"委托"代理为其销售商品.关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,"委托者"对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就

  • java动态代理(jdk与cglib)详细解析

    JAVA的动态代理 代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务. 按照代理的创建时期,代理类可以分为两种. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经

  • JAVA提高第八篇 动态代理技术

    对于动态代理,学过AOP的应该都不会陌生,因为代理是实现AOP功能的核心和关键技术.那么今天我们将开始动态代理的学习: 一.引出动态代理 生活中代理应该是很常见的,比如你可以通过代理商去买电脑,也可以直接找厂商买电脑,最终都是买到了电脑.程序中也一样存在代理的情况,比如要为已经存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如:异常处理.日志.计算方法耗时等等,那么我们会怎么做呢? 1.会编写一个与目标类拥有相同接口的代理类,代理类的每个方法调用目标类的相同方法,然后在调用方法前后加

  • java使用动态代理来实现AOP(日志记录)的实例代码

    下面是一个AOP实现的简单例子: 首先定义一些业务方法: 复制代码 代码如下: /** * Created with IntelliJ IDEA. * Author: wangjie  email:tiantian.china.2@gmail.com * Date: 13-9-23 * Time: 下午3:49 */public interface BussinessService {    public String login(String username, String password

  • java jdk动态代理详解

    jdk动态代理要对一个类进行代理,被代理的类必须实现至少一个接口,并且只有接口中的方法才能被代理. jdk实现动态代理一般分为三步: 1. 编写接口和实现类. 2. 写一个处理器,该处理器实现InvocationHandler接口,该接口只有一个方法,其签名为public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;可在该处理器的实现方法中,在方法调用前和调用后加入自己的代码,从而进行动态拦截

随机推荐