10.3 代理模式 代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,如图10-2所示。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 图10-2 代理模式 按照代理类的创建时期,代理类可分为两种。 ◆静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 ◆动态代理类:在程序运行时,运用反射机制动态创建而成。 10.3.1 静态代理类 如图10-3所示,HelloServiceProxy类(如例程10-13所示)是代理类,HelloServiceImpl类(如例程10-12所示)是委托类,这两个类都实现了HelloService接口(如例程10-11所示)。其中HelloServiceImpl类是HelloService接口的真正实现者,而HelloServiceProxy类是通过调用HelloServiceImpl类的相关方法来提供特定服务的。HelloServiceProxy类的echo()方法和getTime()方法会分别调用被代理的HelloServiceImpl对象的echo()方法和getTime()方法,并且在方法调用前后都会执行一些简单的打印操作。由此可见,代理类可以为委托类预处理消息、把消息转发给委托类和事后处理消息等。 图10-3 HelloServiceProxy类是HelloService的代理类 例程10-11 HelloService.java
例程10-12 HelloServiceImpl.java
例程10-13 HelloServiceProxy.java
在Client1类(如例程10-14所示)的main()方法中,先创建了一个HelloServiceImpl对象,又创建了一个HelloServiceProxy对象,最后调用HelloServiceProxy对象的echo()方法。 例程10-14 Client1.java package proxy; 运行Client1类,打印结果如下: before calling echo() 如图10-4所示显示了Client1调用HelloServiceProxy类的echo()方法的时序图。 图10-4 Client1调用HelloServiceProxy类的echo()方法的时序图 例程10-13的HelloServiceProxy类的源代码是由程序员编写的,在程序运行前,它的.class文件就已经存在了,这种代理类称为静态代理类。 10.3.2 动态代理类 与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类。java.lang.reflect包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。 Proxy类提供了创建动态代理类及其实例的静态方法。 (1)getProxyClass()静态方法负责创建动态代理类,它的完整定义如下: public static Class getProxyClass(ClassLoader loader, Class[] interfaces) 参数loader指定动态代理类的类加载器,参数interfaces指定动态代理类需要实现的所有接口。 (2)newProxyInstance()静态方法负责创建动态代理类的实例,它的完整定义如下: public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 参数loader指定动态代理类的类加载器,参数interfaces指定动态代理类需要实现的所有接口,参数handler指定与动态代理类关联的 InvocationHandler对象。 以下两种方式都创建了实现Foo接口的动态代理类的实例: /**** 方式一 ****/ 由Proxy类的静态方法创建的动态代理类具有以下特点: ◆动态代理类是public、final和非抽象类型的; 由Proxy类的静态方法创建的动态代理类的实例具有以下特点: ◆假定变量foo是一个动态代理类的实例,并且这个动态代理类实现了Foo接口,那么“foo instanceof Foo”的值为true。把变量foo强制转换为Foo类型是合法的: (Foo) foo //合法 ◆每个动态代理类实例都和一个InvocationHandler实例关联。Proxy类的getInvocationHandler(Object proxy)静态方法返回与参数proxy指定的代理类实例所关联的InvocationHandler对象。 InvocationHandler接口为方法调用接口,它声明了负责调用任意一个方法的invoke()方法: Object invoke(Object proxy,Method method,Object[] args) throws Throwable 参数proxy指定动态代理类实例,参数method指定被调用的方法,参数args指定向被调用方法传递的参数,invoke()方法的返回值表示被调用方法的返回值。 如图10-5所示,HelloServiceProxyFactory类(如例程10-15所示)的getHello- ServiceProxy()静态方法负责创建实现了HelloService接口的动态代理类的实例。 图10-5 HelloServiceProxyFactory类创建动态代理类实例 例程10-15 HelloServiceProxyFactory.java package proxy; Class classType=HelloService.class; 如例程10-16所示的Client2类先创建了一个HelloServiceImpl实例,然后创建了一个动态代理类实例helloServiceProxy,最后调用动态代理类实例的echo()方法。 例程10-16 Client2.java package proxy; 运行Client2,打印结果如下: 动态代理类的名字为$Proxy0 从以上打印结果看出,动态代理类的名字为$Proxy0。如图10-6所示显示了Client2调用动态代理类$Proxy0的实例helloServiceProxy的echo()方法的时序图。 图10-6 Client2调用动态代理类$Proxy0的echo()方法的时序图 10.3.3 在远程方法调用中运用代理类 如图10-7所示,SimpleClient客户端通过HelloService代理类来调用SimpleServer服务器端的HelloServiceImpl对象的方法。客户端的HelloService代理类也实现了HelloService接口,这可以简化SimpleClient客户端的编程。对于SimpleClient客户端而言,与远程服务器的通信的细节被封装到HelloService代理类中。SimpleClient客户端可以按照以下方式调用远程服务器上的HelloServiceImpl对象的方法: //创建HelloService代理类的对象 从以上程序代码可以看出,SimpleClient客户端调用远程对象的方法的代码与调用本地对象的方法的代码很相似,由此可以看出,代理类简化了客户端的编程。 图10-7 SimpleClient通过HelloService代理类调用远程对象的方法 Connector类负责建立与远程服务器的连接,以及接收和发送Socket对象。如例程10-17所示是Connector类的源程序。 例程10-17 Connector.java package proxy1; public Connector(String host,int port)throws Exception{ public void send(Object obj)throws Exception{ //发送对象 HelloService代理类有两种创建方式:一种方式是创建一个HelloServiceProxy静态代理类,如例程10-18所示;还有一种方式是创建HelloService的动态代理类,如例程10-19所示ProxyFactory类的静态getProxy()方法就负责创建HelloService的动态代理类,并且返回它的一个实例。 例程10-18 HelloServiceProxy.java(静态代理类) package proxy1; public Date getTime()throws RemoteException{ 例程10-19 ProxyFactory.java(负责创建动态代理类及其实例) package proxy1; return Proxy.newProxyInstance(classType.getClassLoader(), 无论HelloService的静态代理类还是动态代理类,都通过Connector类来发送和接收Call对象。ProxyFactory工厂类的getProxy()方法的第一个参数classType指定代理类实现的接口的类型,如果参数classType的取值为HelloService.class,那么getProxy()方法就创建HelloService动态代理类的实例。如果参数classType的取值为Foo.class,那么getProxy()方法就创建Foo动态代理类的实例。由此可见,getProxy()方法可以创建任意类型的动态代理类的实例,并且它们都具有调用被代理的远程对象的方法的能力。 如果使用静态代理方式,那么对于每一个需要代理的类,都要手工编写静态代理类的源代码;如果使用动态代理方式,那么只要编写一个动态代理工厂类,它就能自动创建各种类型的动态代理类,从而大大简化了编程,并且提高了软件系统的可扩展性和可维护性。 如例程10-20所示的SimpleClient类的main()方法中,分别通过静态代理类和动态代理类去访问SimpleServer服务器上的HelloServiceImpl对象的各种方法。 例程10-20 SimpleClient.java package proxy1; public class SimpleClient { //创建动态代理类实例 先运行命令“java proxy1.SimpleServer”,再运行命令“java proxy1.SimpleClient”,SimpleClient端的打印结果如下: echo:hello proxy1.SimpleServer类的源程序与本章10.2节的例程10-9的remotecall.Simple- Server类相同。如图10-8和图10-9所示分别显示了SimpleClient通过HelloService静态代理类和动态代理类访问远程HelloServiceImpl对象的echo()方法的时序图。 图10-8 SimpleClient通过HelloService静态代理类(HelloServiceProxy)访问远程对象的方法的时序图 图10-9 SimpleClient通过HelloService动态代理类($Proxy0)访问远程对象的方法的时序图 10.4 小结 Java反射机制是Java语言的一个重要特性。考虑实现一个newInstance(String className)方法,它的作用是根据参数className指定的类名,通过该类的不带参数的构造方法创建这个类的对象,并将其返回。如果不运用Java反射机制,必须在newInstance()方法中罗列参数className所有可能的取值,然后创建相应的对象: public Object newInstance(String className) throws Exception{ 以上程序代码很冗长,而且可维护性差。如果在以后软件的升级版本中去除了一个HelloService4类,或者增加了一个HelloService1001类,都需要修改以上newInstance()方法。 如果运用反射机制,就可以简化程序代码,并且提高软件系统的可维护性和可扩展性: public Object newInstance(String className) throws Exception{ Java反射机制在服务器程序和中间件程序中得到了广泛运用。在服务器端,往往需要根据客户的请求,动态调用某一个对象的特定方法。此外,有一种ORM(Object-Relation Mapping,对象-关系映射)中间件能够把任意一个JavaBean持久化到关系数据库中。在ORM中间件的实现中,运用Java反射机制来读取任意一个JavaBean的所有属性,或者给这些属性赋值。在作者的另一本书《精通Hibernate:Java对象持久化技术详解》中阐述了Java反射机制在Hibernate(一种ORM中间件)的实现中的运用。 在JDK类库中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中。 ◆Class类:代表一个类。 本章还介绍了Java反射机制、静态代理模式和动态代理模式在远程方法调用中的运用。本章以SimpleClient客户调用SimpleServer服务器上的HelloServiceImpl对象的方法为例,探讨了实现远程方法调用的一些技巧。本书第11章介绍的RMI框架是JDK类库提供的一个现成的完善的远程方法调用框架。即使程序员不了解这个框架本身的实现细节,也能使用这个框架。不过,熟悉框架本身的实现原理,可以帮助程序员更娴熟地运用RMI框架。本章对实现远程方法调用所作的初步探讨,有助于程序员去进一步探索RMI框架本身的实现原理。 10.5 练习题 1.假定Tester类有如下test方法: public int test(int p1, Integer p2) 以下哪段代码能正确地动态调用一个Tester对象的test方法?(单选) A. Class classType=Tester.class; B. Class classType=Tester.class; Class classType=Tester.class; D. Class classType=Tester.class; A.getConstructors() B.getPrivateMethods() C.getDeclaredFields() 3.以下哪些说法正确?(多选) A.动态代理类与静态代理类一样,必须由开发人员编写源代码,并编译成.class文件 4.以下哪些属于动态代理类的特点?(多选) A.动态代理类是public、final和非抽象类型的 5.在本章10.3.3节(在远程方法调用中运用代理类)介绍的例子中,Connector类位于服务器端还是客户端?(单选) A.服务器端 B.客户端 6.在本章10.3.3节(在远程方法调用中运用代理类)介绍的例子中,HelloServiceProxy类位于服务器端还是客户端?(单选) A.服务器端 B.客户端 7.运用本章介绍的动态代理机制,重新实现第1章的EchoServer服务器与EchoClient客户,具体实现方式参照本章10.3.3节(在远程方法调用中运用代理类)所介绍的例子。 答案:1.C 2.AC 3.BCD 4.ABCE 5.B 6.B |
|
来自: dengxianzhi > 《java》