`
frank-liu
  • 浏览: 1665459 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java Proxy类的讨论

 
阅读更多

前言

    我们常用的一些proxy的手法或者设计模式都是本质上通过一个中间的代理类来引用实际的功能实现类。这个代理类提供和实际功能类同样的功能接口,所以从使用者的角度来说看起来是一样的,这样也就看不出区别来。和proxy模式有相似思想的设计模式包括有decorator, adaptor等模式。代理类很多时候并不仅仅只是一个简单的功能转发,有的时候它也可以做一些适当的功能增强。当然,这里很多时候实现的代理是基于一个已知类的声明和功能,然后静态的代码实现相应的规约。所以一个代理类只能代理一个具体的功能类。如果有变化的话,我们需要再静态的去实现适配的类。在这个时候,我们可能会考虑是否有一种可以动态生成的代理类呢?如果我们可以实现动态的生成代理类的话,需要一些什么条件呢?这里我们会讨论proxy模式以及java中Proxy类使用的思路。

Proxy模式思想介绍

    我们一般讨论到Proxy相关的模式时,从字面上就很容易理解,既然是一种代理的模式,肯定是我们本来希望一些使用的功能由某个类来实现,但是由于某些原因我们将一些具体的实现的功能交给另外一个类来做。这个使用那些其他类提供功能的类就称其为代理。在使用的时候,我们用这个代理类或者原来的类是看起来没什么区别的。通常对应的一种关系如下:

    这里的关系也比较好理解,我们既然从用户使用的角度来说,Proxy类和具体实现类看起来没什么区别。那必然从用户的角度他们是可以互换的。所以他们必然需要实现同一个接口才可能。而这种模式典型的示例代码如下:

Contract interface:

public interface Contract {
    public void doSomething();
}

 Impl:

public class Impl implements Contract {
    public void doSomething() {
        System.out.println("Do something in Implementation");
    }
}

Proxy:

public class Proxy implements Contract {
    private Contract contract;

    public Proxy(Contract contract) {
        this.contract = ocntract;
    }

    public void doSomething() {
        // Can do something overhead
        contract.doSomething();
        // Do something afterward...
    }
}

    从使用者的角度来说,如果我们引用Proxy类,它本身将一些功能实现转发给了具体的Impl类,同时它也可以针对实现做一些增强和调整。如果现在我们来看Proxy模式的作用,我们可以发现,对于一个需要被代理的类来说,它的某些需要被代理的功能最好能够提取到某个接口中来以方便建立代理。对于代理的具体实现,我们可以添加自己的可定制部分。另外,这边还有一个问题就是,比如前面的类里有多个方法需要被代理,具体的代理类也需要写很多个对应的代理方法。这些写法不难,只是感觉很重复和琐碎。

和其他模式及应用的关系

    透过前面写的代码和类图关系,我们会发现Proxy模式和一些常用的设计模式有很多相似的地方。一些比较典型的有Decorator, Adaptor。另外,我们从代码里可以看到,在一些代理方法执行的地方,Proxy类可以做一些其他特性的定制,既可以在方法执行前也可以在方法执行后。这些东西和java EE里的AOP概念很接近。

    我们先来看看Decorator模式的类关系描述:

    这里,每个Decorator都有一个指向同样接口的引用。它本身只是做一些代理。继承Decorator的类会针对具体特性做一些增强。这个Decorator的类本身就相当于是一个Proxy。关于Decorator pattern的详细描述可以参考我的这篇文章。从这些关系我们可以看到,Decorator pattern增加的继承可以使得代理增加的特性更加灵活和丰富。

    至于Adaptor pattern的描述,其本身更加简单:

    我们这里的Adapter就是一个Proxy,他本身要引用另外一个Adaptee的特性,只是要使得他本身符合接口Target的规约而已。

    现在,到这一步的时候,我们发现其实原来很多的应用方式都用到了Proxy模式的思想,只是平时不太留意到而已。我们手工编码实现的Proxy需要有明确的接口规约,这样才能模拟出这样的结果来。在一些情况下,如果我们需要动态的生成一些Proxy类的话,有没有什么好的办法呢?因为对于一些接口来说,每次我们通过手工的去实现他们其实从写代码的角度来说挺没劲的,来来去去就是那么个套路。既然这些事情是如此无趣,能不能让程序给我们生成呢?

Proxy类介绍

    在Java里有专门用于动态代理类生成的方法,就是java.lang.reflect.Proxy。它采用反射的机制来调用原来的被封装方法。我们以前面的示例为基础来看看用Proxy类封装代理之后的常用做法。

    前面的示例里我们首先定义了接口Contract和具体的实现Impl类。这里就不重复。为了能够代理这个类的功能,我们首先定义一个类DynamicProxyHandler:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;

    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
        System.out.println("Proxy: " + proxied);
        System.out.println("Method name: " + method.getName());
        System.out.println("Args: " + args);

        return method.invoke(proxied, args);
    }
}

    这个ProxyHandler类实现接口InvocationHandler。我们在实现的invoke方法里添加了一些自己定义的信息。然后通过method.invoke方法来调用目标对象的方法,并返回调用的结果。我们可以说这里是我们定义自己定制部分特性的切入点,在代理类被调用的时候,触发的就是这个方法。同时被代理的对象将作为构造函数的参数传递进来。

    我们再来看是怎么使用这个DynamicProxyHandler将目标对象包装起来:

import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;

public class ProxyDemo {
    public static void main(String[] args) {
        // Original way without proxy
        System.out.println("Run without proxy");
        Contract contract = new Impl();
        contract.doSomething();

        // With proxy
        System.out.println("Run with proxy");
        InvocationHandler handler = new DynamicProxyHandler(contract);
        Contract cont = (Contract)Proxy.newProxyInstance(
            contract.getClass().getClassLoader(), 
            new Class[] { Contract.class }, handler);
        cont.doSomething();
    }
}

    这部分代理使用Proxy部分比较有意思的是我们通过Proxy.newProxyInstance可以返回一个被封装的Contract对象。我们需要将Contract的具体对象的,以及它的Class对象和我们定义的handler传入。在调用的时候我们会发现如下的结果:

Do something in implementation!
Proxy: Impl@6e1b0caf
Method name: doSomething
Args: null
Do something in implementation!

    我们如果查阅Proxy.newProxyInstance的官方文档,会发现它的方法原型 如下:

newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

    这里的classLoader是定义要代理的那个类的classLoader,一般我们通过该类的Class.getClassLoader()来获得。 而这个interfaces则表示我们要代理的类实现的接口,比如这里我们要代理的类Impl实现了接口Contract,如果它实现了多个而且我们需要都代理的话,则需要放在这里。最后的这个InvocationHandler则是方法被代理执行的对象。一般通过实现InvocationHandler来实现。

    现在我们回过头来看看这种Proxy的实现,这里定义的Proxy对象并没有去直接声明实现某个代理类的接口。另外,虽然我们这里传入了需要被代理的对象,如果将传入对象封装的过程封装成方法的话,我们可以传入任何实现该指定接口的对象,而不只是固定的某一个对象。这样我们就有了另外一个好处,那就是只要将实现该接口的对象传进来,我们就可以代理,不需要再手工的编码创建类。我们在开发的时候会简单一些。

和AOP的比较

    如果很多对AOP比较了解的都知道,AOP的实现可以分为静态切入和动态织入两种方式。典型静态切入的库有aspectJ,而动态的主要是CGLib。这几种织入的方式和Proxy比起来更加强大的地方在于他们可以在方法执行的不同阶段切入时并不要求这个类方法是实现某个接口的。而这里Proxy还是受限于接口的定义限制。所以说,可以将Proxy的功能增强当作一个弱化版的AOP。

Proxy的实现

    既然前面我们使用了Proxy的newProxyInstance方法来封装被代理对象,那么在Java里面,它是怎么实现的呢?我们可以找到这个方法的代码:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass(loader, interfaces);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            Constructor cons = cl.getConstructor(constructorParams);
            return cons.newInstance(new Object[] { h });
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        } catch (IllegalAccessException e) {
            throw new InternalError(e.toString());
        } catch (InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            throw new InternalError(e.toString());
        }
    }

    这个方法其实比较简单,就是通过调用类本身的getProxyClass方法来获取到一个我们代理后生成的Class对象,然后再通过反射调用该对象的构造函数来生成目标对象。那么这个getProxyClass是怎么来生成目标Class对象呢?我们再深入的看看。

    getProxyClass方法本身就需要一个能够实现指定一系列接口的类,而且需要这个类是动态生成的。当然,这个类既然是动态生成的,我们就需要针对接口里所有的方法进行审核。如果我们对jvm再深入理解一点的话,会发现一般java里生成的Class文件有一个固定的格式可以遵循的。比如class文件里指明所使用的java的版本,这个类是接口还是类,里面的成员和方法等信息。既然我们需要一个这样的类,我们完全可以通过一个模板的类来生成这样的类文件。以后我们再通过类加载器将其加载进来就可以了。

    getProxyClass方法通过委托ProxyGenerator来生成这个文件,然后再加载进来。而getProxyClass方法主要是检查这些接口的列表是否重复了,这些接口是否都声明在同一个package里面。然后将生成后的Class对象放到一个weak reference的缓存里。这个几百行的方法里其实主要就是做一些检查工作,没什么特殊的地方。

 

总结

    Proxy模式以及它的一些应用其实平时使用的并不多。更多的时候是在一些J2EE的应用里,比如生成RMI的stub,以前要用工具类rmic来做,现在基本上就是依赖的Proxy。另外,Proxy的思想里也可以用到AOP的应用上。虽然这部分比较少见,但是它和jvm的很多地方关系比较密切,比如class文件的动态生成。以前我们要生成这些需要自己写出来然后用javac编译器来生成,这里自动按照指定的规约来生成,确实比较少见。

 

参考材料

core java volumn I

head first design patterns

http://www.ibm.com/developerworks/java/library/j-jtp08305/index.html

http://www.infoq.com/cn/articles/cf-java-reflection-dynamic-proxy

http://openjdk.java.net/

  • 大小: 8.7 KB
  • 大小: 4.7 KB
分享到:
评论

相关推荐

    JAVA经典设计模式大全

    2.5.2 工厂类和产品类 33 2.5.3 Abstract Factory模式 35 2.6 支持多种窗口系统 35 2.6.1 我们是否可以使用Abstract Factory 模式 35 2.6.2 封装实现依赖关系 35 2.6.3 Window和WindowImp 37 2.6.4 Bridge ...

    jsr80 java 访问 usb

    它包括一个 RMI proxy 和一个 daemon 应用程序,它让 Java 应用程序可以访问远程计算机上的 USB 设备。 usb.util : 这个包提供了一些有用的实用程序,可以将 firmware下载到 USB 设备上、将 USB 系统的内容转储到 ...

    java设计模式CatalogDAOFactory是典型的工厂方法

    CatalogDAOFactory是典型的工厂方法,catDao是通过动态类装入器className获得CatalogDAOFactory具体实现子类,这个实现子类在Java宠物店是用来操作catalog数据库,用户可以根据数据库的类型不同,定制自己的具体实现...

    将 Flex 集成到 Java EE 应用程序的最佳实践(完整源代码)

    现在,Java EE 后端与 Flex 前端的接口已经定义好了,要完成 Java EE 后端的接口实现类非常容易,利用 Spring 强大的依赖注入功能,可以通过几行简单的代码完成: 清单 2. FlexServiceImpl class public ...

    java银行笔试题-tdd-bank-account-java:TD-银行账户-java

    在本次会议中,我们将讨论为什么 XP 实践很重要,然后我们可以编写一些代码来练习两个最基本的实践。 代码将非常简单,这不是编写复杂的代码,恰恰相反。 我们会让你们两人一起构建非常简单的测试驱动代码。 它会让...

    +Flex+集成到+Java+EE+应用程序的最佳实践(完整源代码)

    现在,Java EE 后端与 Flex 前端的接口已经定义好了,要完成 Java EE 后端的接口实现类非常容易,利用 Spring 强大的依赖注入功能,可以通过几行简单的代码完成: 清单 2. FlexServiceImpl class public class ...

    设计模式文档

    2.5.2 工厂类和产品类 33 2.5.3 Abstract Factory模式 35 2.6 支持多种窗口系统 35 2.6.1 我们是否可以使用Abstract Factory 模式 35 2.6.2 封装实现依赖关系 35 2.6.3 Window和WindowImp 37 2.6.4 Bridge 模式 40 ...

    二十三种设计模式【PDF版】

    经常以那些技术只适合大型项目为由,避开或忽略它们,实际中,Java 的接口或抽象类是真正体现 Java 思想的核心所在,这些 你都将在 GoF 的设计模式里领略到它们变幻无穷的魔力。 GoF 的设计模式表面上好象也是一种...

    BunnyX Dynamic XML Proxy-开源

    一个基于Java的代理,可动态转换现有Web应用程序中HTML,以提供XML数据和XML-RPC服务,而无需修改Web应用程序。 一种格式实现了在研究生论文中讨论的称为RMA的算法。

    Java理论与实践:用动态代理进行修饰

    简介:动态代理工具 是 java.lang.reflect 包的一部分,在 JDK 1.3 版本中添加到 JDK,它允许程序创建 代理对象,代理对象能实现一个或多个已知接口,并用反射代替内置的虚方法分派,编程地分派对接口方法的调用。...

    android-framework-hal

    Audio系统的本地核心接口,类的层次结构 7.3 Audio系统和上层接口 Audio系统的JAVA层次的接口 7.4 Audio硬件抽象层 Audio系统的移植,Audio硬件抽象层的实现方法 ALSA Audio HAL实现 8 Android的Video ...

    Apache ShardingSphere分布式数据库中间层生态圈.rar

    Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款相互独立,却又能够混合部署配合使用的产品组成。 它们均提供标准化的数据分片、分布式...

    xmljava系统源码-kingdeehit-sharding-sphere:基于sharding-sphere数据库中间件搭建的解决动态数据

    ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库...

    Apache-ShardingSpher-20200421.pdf

    ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库...

    新版Android开发教程.rar

    � Google 提供了一套 Java 核心包 (J2SE 5,J2SE 6) 的有限子集,尚不承诺遵守 Java 任何 Java 规范 , 可能会造 成J ava 阵营的进一步分裂。 � 现有应用完善度不太够,需要的开发工作量较大。--------------------...

    SeimiCrawler文档离线版

    •SeimiCrawler一个敏捷强大的Java爬虫框架 •1.简介 •2.需要 •3.快速开始 ◦3.1.maven依赖 ◦3.2.在SpringBoot中 ◦3.3.常规用法 •4.原理 ◦4.1.基本原理 ◦4.2.集群原理 •5.如何开发 ◦5.1.约定 ◦...

    在线考试系统文献综述

    JSP技术使用Java编程语言编写类XML的tags和scriptlets,来封装产生动态网页的处理逻辑。网页还能通过tags和scriptlets访问存在于服务端的资源的应用逻辑。JSP将网页逻辑与网页设计和显示分离,支持可重用的基于组件...

    asp.net知识库

    如何判断ArrayList,Hashtable,SortedList 这类对象是否相等 帮助解决网页和JS文件中的中文编码问题的小工具 慎用const关键字 装箱,拆箱以及反射 动态调用对象的属性和方法——性能和灵活性兼备的方法 消除由try/...

Global site tag (gtag.js) - Google Analytics