Javaネットワークプログラミングの精解のJava言語の反射メカニズムの1


Javaランタイム環境では、どのクラスに対してどのような属性とメソッドがあるかを知ることができますか?任意のオブジェクトについて、メソッドを呼び出すことができますか?答えは肯定的だ.このクラスの情報を動的に取得し、オブジェクトを動的に呼び出す方法の機能は、Java言語の反射(Reflection)メカニズムから来ています.Java反射メカニズムは主に以下の機能を提供しています.
◆運転時に任意のオブジェクトが属するクラスを判断する.◆実行時に任意のクラスのオブジェクトを構築する.◆実行時に任意のクラスが持つメンバー変数と方法を判断する.◆実行時に任意のオブジェクトを呼び出す方法.◆動的エージェントを生成します.
この章では、まずJava Reflection APIの使い方を説明し、次にクライアントがサーバ側のオブジェクトをリモートで呼び出すことができるリモートメソッド呼び出しの例を紹介します.サーバ側は反射メカニズムが提供する動的呼び出し方法の機能を採用し,クライアント側は反射メカニズムが提供する動的エージェント機能を採用している.
10.1 Java Reflection APIの概要
JDKでは主に以下のクラスによってJavaの反射機構を実現する.lang.reflectパッケージにあります.
◆Classクラス:クラスを表します.◆Fieldクラス:クラスを表すメンバー変数(メンバー変数はクラスの属性とも呼ばれる).◆Methodクラス:クラスを表す方法.◆Constructorクラス:クラスを表す構造方法.◆Arrayクラス:配列を動的に作成し、配列要素にアクセスする静的方法を提供する.
ルーチン10−1に示すDumpMethodsクラスは、コマンドラインパラメータによって指定されたクラス名を読み出し、そのクラスが持つメソッド情報を印刷するReflection APIの基本的な役割を示している.
ルーチン10-1 DumpMethods.java

import java.lang.reflect.*; public class DumpMethods {   public static void main(String args[]) throws Exception{     //     Class classType = Class.forName(args[0]);     //     Method methods[] = classType.getDeclaredMethods();     for(int i = 0; i < methods.length; i++)       System.out.println(methods[i].toString());   } }


コマンド「java DumpMethods java.util.Stack」を実行するとjavaが表示されます.util.Stackクラスが持つ方法で、プログラムの印刷結果は以下の通りです.

public synchronized java.lang.Object java.util.Stack.pop() public java.lang.Object java.util.Stack.push(java.lang.Object) public boolean java.util.Stack.empty() public synchronized java.lang.Object java.util.Stack.peek() public synchronized int java.util.Stack.search(java.lang.Object)


インスタンス10−2に示すReflectTesterクラスは、Reflection APIの基本的な使用方法をさらに示す.ReflectTesterクラスには、パラメータobjectと同じタイプのオブジェクトを作成し、objectオブジェクトのすべてのプロパティを新しいオブジェクトにコピーして返します.
この例では単純なJavaBeanのみをコピーし,JavaBeanの各属性にpublicタイプのgetXXX()とsetXXX()メソッドがあると仮定する.
ルーチン10-2 ReflectTester.java

import java.lang.reflect.*; public class ReflectTester {   public Object copy(Object object) throws Exception{     //     Class classType=object.getClass();     System.out.println("Class:"+classType.getName());

    //     Object objectCopy=classType.getConstructor(new Class[]{}). newInstance(new Object[]{});

    //     Field fields[]=classType.getDeclaredFields();

    for(int i=0; i      Field field=fields[i];

      String fieldName=field.getName();       String firstLetter=fieldName.substring(0,1).toUpperCase();       // getXXX()       String getMethodName="get"+firstLetter+fieldName.substring(1);       // setXXX()       String setMethodName="set"+firstLetter+fieldName.substring(1);

      // getXXX()       Method getMethod=classType.getMethod(getMethodName,new Class[]{});       // setXXX()       Method setMethod=classType.getMethod(setMethodName,new Class[]{field.getType()});

      // getXXX()       Object value=getMethod.invoke(object,new Object[]{});       System.out.println(fieldName+":"+value);       // setXXX()       etMethod.invoke(objectCopy,new Object[]{value});     }     return objectCopy;   }

  public static void main(String[] args) throws Exception{     Customer customer=new Customer("Tom",21);     customer.setId(new Long(1));

    Customer customerCopy=(Customer)new ReflectTester().copy(customer);     System.out.println("Copy information:"+customerCopy.getName()+""+ customerCopy.getAge());   } }

class Customer{    //Customer JavaBean   private Long id;   private String name;   private int age;     public Customer(){}   public Customer(String name,int age){     this.name=name;     this.age=age;   }     public Long getId(){return id;}   public void setId(Long id){this.id=id;}     public String getName(){return name;}   public void setName(String name){this.name=name;}     public int getAge(){return age;}   public void setAge(int age){this.age=age;} }


#p#
ReflectTesterクラスのcopy(Object object)メソッドは、次の手順で順次実行されます.
(1)取得対象のタイプ:

Class classType=object.getClass(); System.out.println("Class:"+classType.getName());


javaでlang.ObjectクラスではgetClass()メソッドが定義されているので、Javaオブジェクトのいずれかについて、このメソッドでオブジェクトのタイプを取得できます.ClassクラスはReflection APIのコアクラスであり、以下の方法がある.
◆getName():クラスの完全な名前を取得します.◆getFields():クラスのpublicタイプのプロパティを取得します.◆getDeclaredFields():クラスのすべての属性を取得します.◆getMethods():クラスのpublicタイプを取得する方法.◆getDeclaredMethods():クラスのすべてのメソッドを取得します.◆getMethod(String name,Class[]parameterType):クラスの特定のメソッド、nameパラメータ指定メソッドの名前、parameterTypeパラメータ指定メソッドのパラメータタイプを取得します.◆getConstrutors():クラスのpublicタイプの構築方法を取得します.◆getConstrutor(Class[]parameterType):クラスの特定の構造メソッドを取得し、parameterTypeパラメータは構造メソッドのパラメータタイプを指定します.◆newInstance():このクラスのオブジェクトをクラスのパラメータなしの構築方法で作成します.
(2)デフォルトの構築方法で新しいオブジェクトを作成します.

Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});


上記のコードは、ClassクラスのgetConstructor()メソッドを呼び出してデフォルトの構築メソッドを表すConstructorオブジェクトを取得し、ConstructorオブジェクトのnewInstance()メソッドを呼び出してインスタンスを構築します.
(3)オブジェクトのすべての属性を取得する:

Field fields[]=classType.getDeclaredFields();


ClassクラスのgetDeclaredFields()メソッドは、public、protected、デフォルト、privateアクセスレベルのプロパティを含むクラスのすべてのプロパティを返します.
(4)各属性に対応するgetXXX()メソッドとsetXXX()メソッドを取得し、これらのメソッドを実行して、元のオブジェクトの属性を新しいオブジェクトにコピーします.

for(int i=0; i  Field field=fields[i];

  String fieldName=field.getName();   String firstLetter=fieldName.substring(0,1).toUpperCase();   // getXXX()   String getMethodName="get"+firstLetter+fieldName.substring(1);   // setXXX()   String setMethodName="set"+firstLetter+fieldName.substring(1);

  // getXXX()   Method getMethod=classType.getMethod(getMethodName,new Class[]{});   // setXXX()   Method setMethod=classType.getMethod(setMethodName,new Class[]{field.getType()});

  // getXXX()   Object value=getMethod.invoke(object,new Object[]{});   System.out.println(fieldName+":"+value);   // setXXX()   setMethod.invoke(objectCopy,new Object[]{value}); }


上記のコードは、各属性に対応するgetXXX()メソッドとsetXXX()メソッドがあり、メソッド名では「get」と「set」の後ろのアルファベットが大文字であると仮定します.たとえば、CustomerクラスのnameプロパティはgetName()メソッドとsetName()メソッドに対応します.Methodクラスのinvoke(Object obj,Object args[])メソッドは、オブジェクトの特定のメソッドを動的に実行するために使用され、その最初のobjパラメータはメソッドを持つオブジェクトを指定し、2番目のargsパラメータはメソッドに伝達されるパラメータを指定します.
ルーチン10−3に示すInvokeTesterクラスのmain()メソッドでは、反射機構を用いてInvokeTesterオブジェクトのadd()およびecho()メソッドを呼び出す.
ルーチン10-3 InvokeTester.java

import java.lang.reflect.*; public class InvokeTester {   public int add(int param1,int param2){     return param1+param2;   }   public String echo(String msg){     return "echo:"+msg;   }   public static void main(String[] args) throws Exception{     Class classType=InvokeTester.class;     Object invokeTester=classType.newInstance();         // InvokeTester add()     Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});     Object result=addMethod.invoke(invokeTester,           new Object[]{new Integer(100),new Integer(200)});     System.out.println((Integer)result);

    // InvokeTester echo()     Method echoMethod=classType.getMethod("echo",new Class[]{String.class});     result=echoMethod.invoke(invokeTester,new Object[]{"Hello"});     System.out.println((String)result);   } }


add()メソッドの2つのパラメータはintタイプであり、add()メソッドを表すMethodオブジェクトを得るコードは以下の通りである.

Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});


Methodクラスのinvoke(Object obj,Object args[])メソッドが受信するパラメータはオブジェクトでなければなりません.パラメータが基本タイプデータの場合、対応するパッケージタイプのオブジェクトに変換する必要があります.invoke()メソッドの戻り値は常にオブジェクトであり、実際に呼び出されたメソッドの戻りタイプが基本タイプデータである場合、invoke()メソッドはそれを対応するパッケージタイプのオブジェクトに変換し、それを返します.
この例では、InvokeTesterクラスのadd()メソッドの両方のパラメータおよび戻り値がintタイプであるにもかかわらず、addMethodオブジェクトのinvoke()メソッドを呼び出すと、Integerタイプのパラメータのみが渡され、invoke()メソッドの戻りタイプもIntegerタイプであり、Integerクラスはint基本タイプのパッケージクラスである:

Object result=addMethod.invoke(invokeTester, new Object[]{new Integer(100),new Integer(200)}); System.out.println((Integer)result);  //result Integer


java.lang.Arrayクラスは、配列要素を動的に作成およびアクセスするための様々な静的メソッドを提供します.インスタンス10-4に示すArrayTester 1クラスのmain()メソッドは、長さ10の文字列配列を作成し、インデックス位置5の要素を「hello」に設定してから、インデックス位置5の要素の値を読み出す.
#p#
ルーチン10-4 ArrayTester 1.java

import java.lang.reflect.*; public class ArrayTester1 {   public static void main(String args[])throws Exception {     Class classType = Class.forName("java.lang.String");     // 10     Object array = Array.newInstance(classType, 10);     // 5 "hello"     Array.set(array, 5, "hello");     // 5     String s = (String) Array.get(array, 5);     System.out.println(s);   } }


インスタンス10-5に示すArrayTester 2クラスのmain()メソッドが5を作成しました.×10×15の整数配列で、インデックス位置が[3][5][10]の要素の値を37とする.
ルーチン10-5 ArrayTester 2.java

import java.lang.reflect.*; public class ArrayTester2{   public static void main(String args[]) {     int dims[] = new int[]{5, 10, 15};     Object array = Array.newInstance(Integer.TYPE, dims);     // arrayObj array[3]     Object arrayObj = Array.get(array, 3);     Class cls = arrayObj.getClass().getComponentType();     System.out.println(cls);     // arrayObj array[3][5]     arrayObj = Array.get(arrayObj, 5);     // array[3][5][10] 37     Array.setInt(arrayObj, 10, 37);     int arrayCast[][][] = (int[][][]) array;     System.out.println(arrayCast[3][5][10]);   } }


10.2リモートメソッド呼び出しにおける反射機構の運用
SimpleServerサーバ側にgetTime()メソッドとecho()メソッドを持つHelloServiceImplオブジェクトが作成されたとします.HelloServiceImplクラスはHelloServiceインタフェースを実装しています.ルーチン10−6およびルーチン10−7に示すように、それぞれHelloServiceインタフェースおよびHelloServiceImplクラスのソースプログラムである.
ルーチン10-6 HelloServices.java

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


ルーチン10-7 HelloServiceImpl.java

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


SimpleClientクライアントは、サーバ側のHelloServiceImplオブジェクトのgetTime()メソッドとecho()メソッドをどのように呼び出しますか?SimpleClientクライアントは、呼び出されたメソッド名、メソッドパラメータタイプ、メソッドパラメータ値、およびメソッドが属するクラス名またはインタフェース名をSimpleServerに送信し、SimpleServerは関連オブジェクトのメソッドを呼び出し、メソッドの戻り値をSimpleClientに送信する必要があることは明らかです.
クライアント側とサーバ側との通信をオブジェクト向けに容易に処理するために、それらが送信する情報をCallクラス(ルーチン10−8に示すように)で表すことができる.1つのCallオブジェクトは、呼び出しのクラス名またはインタフェース名、メソッド名、メソッドパラメータタイプ、メソッドパラメータ値、およびメソッド実行結果を含むクライアントによって開始されたリモートコールを表す.
ルーチン10-8 Call.java

package remotecall; import java.io.*; public class Call implements Serializable{   private String className;    //   private String methodName;   //   private Class[] paramTypes;   // private Object[] params;   //

// // , result , , result 。   private Object result;     public Call(){}   public Call(String className,String methodName,Class[] paramTypes,                             Object[] params){     this.className=className;     this.methodName=methodName;     this.paramTypes=paramTypes;     this.params=params;   }     public String getClassName(){return className;}   public void setClassName(String className){this.className=className;}

  public String getMethodName(){return methodName;}   public void setMethodName(String methodName){this.methodName=methodName;}

  public Class[] getParamTypes(){return paramTypes;}   public void setParamTypes(Class[] paramTypes){this.paramTypes=paramTypes;}

  public Object[] getParams(){return params;}   public void setParams(Object[] params){this.params=params;}

  public Object getResult(){return result;}   public void setResult(Object result){this.result=result;}

  public String toString(){     return "className="+className+" methodName="+methodName;   } }


SimpleClientがSimpleServer側のHelloServiceImplオブジェクトを呼び出すecho()メソッドの流れは次のとおりです.
(1)SimpleClientは、HelloServiceインタフェースを呼び出すecho()メソッドの情報を含むCallオブジェクトを作成する.(2)SimpleClientはオブジェクト出力ストリームを介してCallオブジェクトをSimpleServerに送信する.(3)SimpleServerはオブジェクト入力ストリームを介してCallオブジェクトを読み出し,反射機構を用いてHelloServiceImplオブジェクトのecho()メソッドを呼び出し,echo()メソッドの実行結果をCallイメージに保存する.(4)SimpleServerは、メソッド実行結果を含むCallオブジェクトをオブジェクト出力ストリームを介してSimpleClientに送信する.(5)SimpleClientは、オブジェクト入力ストリームを介してCallオブジェクトを読み出し、そこからメソッド実行結果を得る.
#p#
インスタンス10−9およびインスタンス10−10に示すように、SimpleServerおよびSimpleClientのソースプログラムである.
ルーチン10-9 SimpleServer.java

package remotecall; import java.io.*; import java.net.*; import java.util.*; import java.lang.reflect.*; public class SimpleServer {   private Map remoteObjects=new HashMap();   //     /** */   public void register(String className,Object remoteObject){     remoteObjects.put( className,remoteObject);   }   public void service()throws Exception{     ServerSocket serverSocket = new ServerSocket(8000);     System.out.println(" .");     while(true){       Socket socket=serverSocket.accept();       InputStream in=socket.getInputStream();       ObjectInputStream ois=new ObjectInputStream(in);       OutputStream out=socket.getOutputStream();       ObjectOutputStream oos=new ObjectOutputStream(out);

      Call call=(Call)ois.readObject();    // Call       System.out.println(call);       call=invoke(call);      //       oos.writeObject(call);      // Call             ois.close();       oos.close();       socket.close();     }   }

  public Call invoke(Call call){     Object result=null;     try{       String className=call.getClassName();       String methodName=call.getMethodName();       Object[] params=call.getParams();       Class classType=Class.forName(className);       Class[] paramTypes=call.getParamTypes();       Method method=classType.getMethod(methodName,paramTypes);        Object remoteObject=remoteObjects.get(className);    //       if(remoteObject==null){         throw new Exception(className+" ");       }else{         result=method.invoke(remoteObject,params);       }     }catch(Exception e){result=e;}

    call.setResult(result);        //     return call;   }

  public static void main(String args[])throws Exception {     SimpleServer server=new SimpleServer();     // HelloServiceImpl     server.register("remotecall.HelloService",new HelloServiceImpl());     server.service();   } }


ルーチン10-10 SimpleClient.java

package remotecall; import java.io.*; import java.net.*; import java.util.*; public class SimpleClient {   public void invoke()throws Exception{     Socket socket = new Socket("localhost",8000);     OutputStream out=socket.getOutputStream();     ObjectOutputStream oos=new ObjectOutputStream(out);     InputStream in=socket.getInputStream();     ObjectInputStream ois=new ObjectInputStream(in);       //Call call=new Call("remotecall.HelloService","getTime", new Class[]{},new Object[]{});     Call call=new Call("remotecall.HelloService","echo", new Class[]{String.class},new Object[]{"Hello"});      oos.writeObject(call);        // Call     call=(Call)ois.readObject();       // Call     System.out.println(call.getResult());

    ois.close();     oos.close();     socket.close();   }   public static void main(String args[])throws Exception {     new SimpleClient().invoke();   } }


コマンド「java remotecal.SimpleServer」を実行し、コマンド「java remotecal.SimpleClient」を実行すると、SimpleClient側に「echo:Hello」が印刷されます.この印刷結果は、サーバ側がHelloServiceImplオブジェクトを実行するecho()メソッドの戻り値である.図10−1に示すように、SimpleClientとSimpleServerとの通信手順が示されている.
 
図10-1 SimpleClientとSimpleServerの通信手順
関連記事リンク:Javaネットワークプログラミング精解のJava言語の反射メカニズム二