分享

Java网络编程精解之Java语言的反射机制二

 dengxianzhi 2011-04-13

    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

    package proxy;
    import java.util.Date;
    public interface HelloService{
    public String echo(String msg);
    public Date getTime();
    }

    例程10-12  HelloServiceImpl.java

    package proxy;
    import java.util.Date;
    public class HelloServiceImpl implements HelloService{
    public String echo(String msg){
    return "echo:"+msg;
    }
    public Date getTime(){
    return new Date();
    }
    }

    例程10-13  HelloServiceProxy.java

    package proxy;
    import java.util.Date;
    public class HelloServiceProxy implements HelloService{
    private HelloService helloService;     //表示被代理的HelloService实例

    public HelloServiceProxy(HelloService helloService){
    this.helloService=helloService;
    }

    public void setHelloServiceProxy(HelloService helloService){
    this.helloService=helloService;
    }

    public String echo(String msg){
    System.out.println("before calling echo()");   //预处理
    String result=helloService.echo(msg);    //调用被代理的HelloService实例的echo()方法
    System.out.println("after calling echo()");   //事后处理
    return result;
    }
    public Date getTime(){
    System.out.println("before calling getTime()");   //预处理
    Date date=helloService.getTime();    //调用被代理的HelloService实例的getTime()方法
    System.out.println("after calling getTime()");   //事后处理
    return date;
    }
    }

    在Client1类(如例程10-14所示)的main()方法中,先创建了一个HelloServiceImpl对象,又创建了一个HelloServiceProxy对象,最后调用HelloServiceProxy对象的echo()方法。

      例程10-14  Client1.java

      package proxy;
      public class Client1{
      public static void main(String args[]){
      HelloService helloService=new HelloServiceImpl();
      HelloService helloServiceProxy=new HelloServiceProxy(helloService);
      System.out.println(helloServiceProxy.echo("hello"));
      }
      }

      运行Client1类,打印结果如下:

      before calling echo()
      after calling echo()
      echo:hello

      如图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)
      throws IllegalArgumentException

      参数loader指定动态代理类的类加载器,参数interfaces指定动态代理类需要实现的所有接口。

      (2)newProxyInstance()静态方法负责创建动态代理类的实例,它的完整定义如下:

      public static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
      InvocationHandler handler) throws IllegalArgumentException

      参数loader指定动态代理类的类加载器,参数interfaces指定动态代理类需要实现的所有接口,参数handler指定与动态代理类关联的 InvocationHandler对象。

      以下两种方式都创建了实现Foo接口的动态代理类的实例:

      /****  方式一  ****/
      //创建InvocationHandler对象
      InvocationHandler handler = new MyInvocationHandler(...);
      //创建动态代理类
      Class proxyClass = Proxy.getProxyClass(
      Foo.class.getClassLoader(), new Class[] { Foo.class });
      //创建动态代理类的实例
      Foo foo = (Foo) proxyClass.
      getConstructor(new Class[] { InvocationHandler.class }).
      newInstance(new Object[] { handler });

      /****  方式二  ****/
      //创建InvocationHandler对象
      InvocationHandler handler = new MyInvocationHandler(...);
      //直接创建动态代理类的实例
      Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
      new Class[] { Foo.class },
      handler);

      由Proxy类的静态方法创建的动态代理类具有以下特点:

      ◆动态代理类是public、final和非抽象类型的;
      ◆动态代理类继承了java.lang.reflect.Proxy类; 
      ◆动态代理类的名字以“$Proxy”开头;
      ◆动态代理类实现getProxyClass()和newProxyInstance()方法中参数interfaces指定的所有接口; 
      ◆Proxy类的isProxyClass(Class cl)静态方法可用来判断参数指定的类是否为动态代理类。只有通过Proxy类创建的类才是动态代理类;
      ◆动态代理类都具有一个public类型的构造方法,该构造方法有一个InvocationHandler类型的参数。

        由Proxy类的静态方法创建的动态代理类的实例具有以下特点:

        ◆假定变量foo是一个动态代理类的实例,并且这个动态代理类实现了Foo接口,那么“foo instanceof Foo”的值为true。把变量foo强制转换为Foo类型是合法的:

        (Foo) foo   //合法

        ◆每个动态代理类实例都和一个InvocationHandler实例关联。Proxy类的getInvocationHandler(Object proxy)静态方法返回与参数proxy指定的代理类实例所关联的InvocationHandler对象。
        ◆假定Foo接口有一个amethod()方法,那么当程序调用动态代理类实例foo的amethod()方法时,该方法会调用与它关联的InvocationHandler对象的invoke()方法。

        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;
        import java.lang.reflect.*;
        public class HelloServiceProxyFactory {
        /** 创建一个实现了HelloService接口的动态代理类的实例
        *  参数helloService引用被代理的HelloService实例
        */ 
        public static HelloService getHelloServiceProxy(final HelloService helloService){
        //创建一个实现了InvocationHandler接口的匿名类的实例
        InvocationHandler handler=new InvocationHandler(){
        public Object invoke(Object proxy,Method method,Object args[])throws Exception{
        System.out.println("before calling "+method);    //预处理
        Object result=method.invoke(helloService,args);
        //调用被代理的HelloService实例的方法
        System.out.println("after calling "+method);    //事后处理
        return result;
        }
        };

        Class classType=HelloService.class;
        return (HelloService)Proxy.newProxyInstance(classType.getClassLoader(),
        new Class[]{classType},
        handler);
        }//# getHelloServiceProxy()
        }

        如例程10-16所示的Client2类先创建了一个HelloServiceImpl实例,然后创建了一个动态代理类实例helloServiceProxy,最后调用动态代理类实例的echo()方法。

        例程10-16  Client2.java

        package proxy;
        public class Client2{
        public static void main(String args[]){
        HelloService helloService=new HelloServiceImpl();
        HelloService helloServiceProxy=
        HelloServiceProxyFactory.getHelloServiceProxy(helloService);
        System.out.println("动态代理类的名字为"
        +helloServiceProxy.getClass().getName());
        System.out.println(helloServiceProxy.echo("Hello"));
        }
        }

        运行Client2,打印结果如下:

        动态代理类的名字为$Proxy0
        before calling public abstract java.lang.
        String proxy.HelloService.echo(java.lang.String)
        after calling public abstract java.lang.
        String proxy.HelloService.echo(java.lang.String)
        echo:Hello

        从以上打印结果看出,动态代理类的名字为$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代理类的对象
          HelloService helloService1=new HelloServiceProxy(connector);
          //通过代理类调用远程服务器上的HelloServiceImpl对象的方法
          System.out.println(helloService1.echo("hello"));

          从以上程序代码可以看出,SimpleClient客户端调用远程对象的方法的代码与调用本地对象的方法的代码很相似,由此可以看出,代理类简化了客户端的编程。

           

          图10-7  SimpleClient通过HelloService代理类调用远程对象的方法

          Connector类负责建立与远程服务器的连接,以及接收和发送Socket对象。如例程10-17所示是Connector类的源程序。

          例程10-17  Connector.java

          package proxy1;
          import java.io.*;
          import java.net.*;
          import java.util.*;
          public class Connector {
          private String host;
          private int port;
          private Socket skt;
          private InputStream is;
          private ObjectInputStream ois;
          private OutputStream os;
          private ObjectOutputStream oos;

          public Connector(String host,int port)throws Exception{
          this.host=host;
          this.port=port;
          connect(host,port);
          }

          public void send(Object obj)throws Exception{     //发送对象
          oos.writeObject(obj);
          }
          public Object receive() throws Exception{      //接收对象
          return ois.readObject();
          }
          public void connect()throws Exception{      //建立与远程服务器的连接
          connect(host,port);
          }
          public void connect(String host,int port)throws Exception{    //建立与远程服务器的连接
          skt=new Socket(host,port);
          os=skt.getOutputStream();
          oos=new ObjectOutputStream(os);
          is=skt.getInputStream();
          ois=new ObjectInputStream(is);
          }
          public void close(){        //关闭连接
          try{
          }finally{
          try{
          ois.close();
          oos.close();
          skt.close();
          }catch(Exception e){
          System.out.println("Connector.close: "+e);
          }
          }
          }
          }

          HelloService代理类有两种创建方式:一种方式是创建一个HelloServiceProxy静态代理类,如例程10-18所示;还有一种方式是创建HelloService的动态代理类,如例程10-19所示ProxyFactory类的静态getProxy()方法就负责创建HelloService的动态代理类,并且返回它的一个实例。

          例程10-18  HelloServiceProxy.java(静态代理类)

          package proxy1;
          import java.util.Date;
          public class HelloServiceProxy implements HelloService{
          private String host;
          private int port;

          public HelloServiceProxy(String host,int port){
          this.host=host;
          this.port=port;
          }
          public String echo(String msg)throws RemoteException{
          Connector connector=null;
          try{
          connector=new Connector(host,port);
          Call call=new Call("proxy1.HelloService","echo",
          new Class[]{String.class},new Object[]{msg});
          connector.send(call);
          call=(Call)connector.receive();
          Object result=call.getResult();
          if(result instanceof Throwable)
          throw new RemoteException((Throwable)result);    //把异常都转换为RemoteException
          else
          return (String)result;
          }catch(Exception e){
          throw new RemoteException(e);     //把异常都转换为RemoteException
          }finally{if(connector!=null)connector.close();}
          }

          public Date getTime()throws RemoteException{
          Connector connector=null;
          try{
          connector=new Connector(host,port);
          Call call=new Call("proxy1.HelloService","getTime",new Class[]{},new Object[]{});
          connector.send(call);
          call=(Call)connector.receive();
          Object result=call.getResult();
          if(result instanceof Throwable)
          throw new RemoteException((Throwable)result);  //把异常都转换为RemoteException
          else
          return (Date)result;
          }catch(Exception e){
          throw new RemoteException(e);      //把异常都转换为RemoteException
          }finally{if(connector!=null)connector.close();}
          }
          }

            例程10-19  ProxyFactory.java(负责创建动态代理类及其实例)

            package proxy1;
            import java.lang.reflect.*;
            public class ProxyFactory {
            public static Object getProxy(final Class classType,final String host,final int port){
            InvocationHandler handler=new InvocationHandler(){
            public Object invoke(Object proxy,Method method,Object args[])
            throws Exception{
            Connector connector=null;
            try{
            connector=new Connector(host,port);
            Call call=new Call(classType.getName(),
            method.getName(),method.getParameterTypes(),args);
            connector.send(call);
            call=(Call)connector.receive();
            Object result=call.getResult();
            if(result instanceof Throwable)
            throw new RemoteException((Throwable)result);  //把异常都转换为RemoteException
            else
            return result;
            }finally{if(connector!=null)connector.close();}
            }
            };

            return Proxy.newProxyInstance(classType.getClassLoader(),
            new Class[]{classType},
            handler);
            }
            }

            无论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;
            import java.io.*;
            import java.net.*;
            import java.util.*;

            public class SimpleClient {
            public static void main(String args[])throws Exception {
            //创建静态代理类实例
            HelloService helloService1=new HelloServiceProxy("localhost",8000);
            System.out.println(helloService1.echo("hello"));
            System.out.println(helloService1.getTime());

            //创建动态代理类实例
            HelloService helloService2=
            (HelloService)ProxyFactory.getProxy(HelloService.class,"localhost",8000);
            System.out.println(helloService2.echo("hello"));
            System.out.println(helloService2.getTime());
            }
            }

            先运行命令“java proxy1.SimpleServer”,再运行命令“java proxy1.SimpleClient”,SimpleClient端的打印结果如下:

            echo:hello
            Thu Nov 02 10:54:48 CST 2006
            echo:hello
            Thu Nov 02 10:54:49 CST 2006

            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{
              if(className.equals("HelloService1"))
              return new HelloService1();
              if(className.equals("HelloService2"))
              return new HelloService2();
              if(className.equals("HelloService3"))
              return new HelloService3();
              if(className.equals("HelloService4"))
              return new HelloService4();
              ...
              if(className.equals("HelloService1000"))
              return new HelloService1000();
              }

              以上程序代码很冗长,而且可维护性差。如果在以后软件的升级版本中去除了一个HelloService4类,或者增加了一个HelloService1001类,都需要修改以上newInstance()方法。

              如果运用反射机制,就可以简化程序代码,并且提高软件系统的可维护性和可扩展性:

              public Object newInstance(String className) throws Exception{
              Class classType=Class.forName(className);
              return classType.newInstance();
              }

              Java反射机制在服务器程序和中间件程序中得到了广泛运用。在服务器端,往往需要根据客户的请求,动态调用某一个对象的特定方法。此外,有一种ORM(Object-Relation Mapping,对象-关系映射)中间件能够把任意一个JavaBean持久化到关系数据库中。在ORM中间件的实现中,运用Java反射机制来读取任意一个JavaBean的所有属性,或者给这些属性赋值。在作者的另一本书《精通Hibernate:Java对象持久化技术详解》中阐述了Java反射机制在Hibernate(一种ORM中间件)的实现中的运用。

              在JDK类库中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中。

              ◆Class类:代表一个类。
              ◆Field类:代表类的属性。
              ◆Method类:代表类的方法。
              ◆Constructor类:代表类的构造方法。
              ◆Array类:提供了动态创建数组,以及访问数组元素的静态方法。
              ◆Proxy类和InvocationHandler接口:提供了生成动态代理类及其实例的方法。

              本章还介绍了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;
              Object tester=classType.newInstance();
              Method addMethod=classType.getMethod("test",new Class[]{int.class,int.class});
              Object result=addMethod.invoke(tester,
              new Object[]{new Integer(100),new Integer(200)});

              B.

              Class classType=Tester.class;
              Object tester=classType.newInstance();
              Method addMethod=classType.getMethod("test",new Class[]{int.class,int.class});
              int result=addMethod.invoke(tester,
              new Object[]{new Integer(100),new Integer(200)});


              C.

              Class classType=Tester.class;
              Object tester=classType.newInstance();
              Method addMethod=classType.getMethod("test",new Class[]{int.class,Integer.class});
              Object result=addMethod.invoke(tester,
              new Object[]{new Integer(100),new Integer(200)});

              D.

              Class classType=Tester.class;
              Object tester=classType.newInstance();
              Method addMethod=classType.getMethod("test",new Class[]{int.class,Integer.class});
              Integer result=addMethod.invoke(tester,
              new Object[]{new Integer(100),new Integer(200)});


              2.以下哪些方法在Class类中定义?(多选)

              A.getConstructors()  B.getPrivateMethods() C.getDeclaredFields()
              D.getImports()   E.setField()

              3.以下哪些说法正确?(多选)

              A.动态代理类与静态代理类一样,必须由开发人员编写源代码,并编译成.class文件
              B.代理类与被代理类具有同样的接口
              C.java.lang.Exception类实现了java.io.Serializable接口,因此Exception对象可以被序列化后在网络上传输
              D.java.lang.reflect包中的Proxy类提供了创建动态代理类的方法

              4.以下哪些属于动态代理类的特点?(多选)

              A.动态代理类是public、final和非抽象类型的
              B.动态代理类继承了java.lang.reflect.Proxy类 
              C.动态代理类实现了getProxyClass()或newProxyInstance()方法中参数interfaces指定的所有接口 
              D.动态代理类可以继承用户自定义的任意类 
              E.动态代理类都具有一个public类型的构造方法,该构造方法有一个InvocationHandler类型的参数

              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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多