クラスファイルの動的取得

 

HORBでサーバ、クライアントの通信を行う場合、それぞれのマシンに必要なクラスファイルを予めインストールしておく必要があります。
これを、必要な時に必要なものを動的に取得できないものかと考え、実際に作ってみたのが HORBDeployServer , HORBDynamicLoader です。

サンプルを例に説明を進めます。

クライアント側のマシンでClient.classを実行します。Clientは、「Television」と「Book」というボタンのあるフレーム画面を表示します。ユーザが「Television」ボタンを押すとTelevision.classを、「Book」ボタンを押すとBook.classをそれぞれサーバマシンから取得し、インスタンス化し、実行します。

クライアント側には、以下のクラスファイルがインストールされています:

  1. Client$1.class
  2. Client$2.class
  3. Client.class
  4. HORBDynamicLoader$ClassData.class
  5. HORBDynamicLoader$EntryData.class
  6. HORBDynamicLoader$ImageData.class
  7. HORBDynamicLoader.class
  8. HORBDeployServer_Proxy.class
  9. DoCommon.class
  10. HORBパッケージ

サーバ側には、以下のクラスファイルがインストールされています:

  1. HORBDeployServer$1.class
  2. HORBDeployServer.class
  3. HORBDeployServer_Skeleton.class
  4. HORBパッケージ

サーバ側の上記クラスファイルを置いたディレクトリに、storeDir というディレクトリを作成し、そのディレクトリにTelevision、Bookに関係するクラスファイルをJARファイルにして配置します。今回は、Television関係ををtv.jar、Book関係をbook.jarとします。

ディレクトリ構成:

  (任意のディレクトリ) --- ServerDir --- HORBDeployServer$1.class
                       	|             |- HORBDeployServer.class
                        |             |- HORBDeployServer_Skeleton.class
                        |             |
                        |             |- storeDir --- tv.jar
                        |                          |- book.jar
                        |
                        |- ClientDir --- Client$1.class
                                      |- Client$2.class
                                      |- Client.class
                                      |- HORBDynamicLoader$ClassData.class
                                      |- HORBDynamicLoader$EntryData.class
                                      |- HORBDynamicLoader$ImageData.class
                                      |- HORBDynamicLoader.class
                                      |- HORBDeployServer_Proxy.class
                                      |- DoCommon.class 
実行環境のダウンロード(classes.zip)
classes.zipを任意のディレクトリで展開してください。

それでは、実際に動かして見ましょう。

*以下の起動例では、CLASSPATHにHORBとカレントディレクトリが設定されている必要があります。

CLASSPATHの例:d:\horb2.0\classes;.;   これはWindowsの例です

まず、MS-DOS画面を2つ開きます。
サーバは、horb -v で起動してください。
クライアントは java Client で起動してください
TelevisionとBookというボタンを持つ画面が表示されます。Televisionボタンを押してみてください。HORBのロゴが表示されます。
これは、サーバからTelevisionクラスとロゴのJPGファイルを取得して表示しています。
Bookの方は、単に文字列を表示するBookクラスを実行しています。
では、実際のコードを見てみましょう:
ソースコードのダウンロード(sources.zip)
Client.java

public class Client{
  private HORBDynamicLoader hdl = new HORBDynamicLoader(new HorbURL("horb://localhost/"));
  Client(){
    JFrame frame = new JFrame();
    frame.getContentPane().setLayout(new FlowLayout());
    JButton tvButton = new JButton("Television");
    tvButton.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent e){
        DoCommon obj = (DoCommon)hdl.getObject("Television");
        if(obj != null)
          obj.kickDo(hdl);
      }
    });
    JButton bookButton = new JButton("Book");
    bookButton.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent e){
        DoCommon obj = (DoCommon)hdl.getObject("Book");
        if(obj != null)
          obj.kickDo();
      }
    });
    frame.getContentPane().add(tvButton);
    frame.getContentPane().add(bookButton);
    frame.pack();
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  }

  public static void main(String args[]){
    Client cient = new Client();
  }
}
HORBDynamicLoaderは、インスタンス化された時点でHORBDeployServerをインスタンス化(接続)します。
そして、Television,Bookボタンが押された際、Television、BookオブジェクトをHORBDynamicLoaderから取得しています。
    DoCommon obj = (DoCommon)hdl.getObject("Television");
動的に取得したいクラスをHORBDynamicLoader::getObject()で取得します。ただ、HORBDynamicLoader::getObject()が返すのは Object ですので、それを DoCommon にキャスティングする必要があります。
そして、取得した DoCommon の kickDo() を呼びます。
Televisionの場合、kickDo()の引数として HORBDynamicLoader を渡しています。この理由はTelevision.javaの所で説明します。

Television.java

public class Television implements DoCommon{
  public void kickDo(){
    JFrame frame = new JFrame();
    JLabel label = new JLabel();
    label.setIcon(new ImageIcon("./toplogo.jpg"));
    frame.getContentPane().add(label);
    frame.pack();
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  }

  public void kickDo(ClassLoader loader){
    JFrame frame = new JFrame();
    JLabel label = new JLabel();
    label.setIcon(((HORBDynamicLoader)loader).getImageIcon("toplogo.jpg"));
    frame.getContentPane().add(label);
    frame.pack();
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  }
}
Televisionは、DoCommon インタフェースを実装しており、DoCommonで定義されている2種類のkickDo()が実装されています。違いは、toplogo.jpgファイルの取得方法です。引数無しのkickDo()は、カレントディレクトリにあるtoplogo.jpgをロードします。
引数にHORBDynamicLoaderが渡された方のkickDo()は、HORBDynamicLoader::getImageIcon()を使ってtoplogo.jpgをロードします。

HORBDynamicLoader.java

	public class HORBDynamicLoader extends ClassLoader{
HORBDynamicLoaderはClassLaoderを継承しています。
  public Object getObject(String name){
    try{
      download(name);
      return loadClass(name,true).newInstance();
    }catch(Exception e){
      e.printStackTrace();
    }
    return null;
  }
Clientから呼んでいた getObject() では、引数として渡されたクラスをインスタンス化し、そのObjectを戻すのですが、その際、download()を実行しています。
  private void download(String name) throws Exception{
    try{
      loadClass(name);
    }catch(ClassNotFoundException e){
      try{
        byte data[] = server.getJarFromClass(name);
        if(data == null)
          throw new ClassNotFoundException();

        ByteArrayInputStream bis = new ByteArrayInputStream(data);
        JarInputStream jis = new JarInputStream(bis);
        JarEntry entry;
        HashMap map = new HashMap();
        while((entry = jis.getNextJarEntry()) != null){
          String entryName = entry.getName();
          if(entryName.endsWith(".class")){
            jis.closeEntry();
            int classSize = (int)entry.getSize();
            ClassData cd = new ClassData(entryName,classSize);
            map.put(entryName,cd);
          }else if(entryName.endsWith(".gif") || entryName.endsWith(".jpg")){
            jis.closeEntry();
            int imageSize = (int)entry.getSize();
            ImageData id = new ImageData(name,imageSize);
            map.put(entryName,id);
          }
        }
        bis = new ByteArrayInputStream(data);
        jis = new JarInputStream(bis);
        while((entry = jis.getNextJarEntry()) != null){
          String entryName = entry.getName();
          if(entryName.endsWith(".class") || entryName.endsWith(".gif") || entryName.endsWith(".jpg")){
            EntryData entryData = (EntryData)map.get(entryName);
            int entrySize = entryData.size;
            byte byteData[] = new byte[entrySize];
            int readLen = 0;
            int totalLen = 0;
            while(readLen != -1){
              readLen = jis.read(byteData,totalLen,entrySize - totalLen);
              if(readLen != -1)
                totalLen += readLen;
              if(readLen == 0)
                break;
            }
            if(entryData instanceof ClassData){
              String regName = entryName.substring(0,entryName.length() - 6);
              regClass(regName,byteData);
            }else if(entryData instanceof ImageData){
              ImageIcon icon = new ImageIcon(byteData);
              imageBuffer.put(entryName,icon);
            }
          }
        }
      }catch(Exception e3){
        throw e3;
      }
    }catch(Exception e2){
      throw e2;
    }
  }
downloadでは、最初にシステムクラスローダからロード可能かチェックします。ロードできない場合、つまりClassNotFoundExceptionをキャッチした場合、HORBDeployServerからクラスファイルを取得します。
        byte data[] = server.getJarFromClass(name);
HORBDeplyServerからは、引数として渡したクラス名に関係するファイル郡がJARファイルのバイト配列で返送されます。
あとは受信したバイト配列を解析し、クラスはクラスローダにロードし、画像イメージ(GIF,JPG)はImageBufferに格納します。

HORBDeployServer.java
HORBDeployServerは、起動時、storeDirディレクトリにあるJARとクラスファイルをチェックします。
getJarFromClass()では、指定されたクラスファイルが格納されているJARファイルを読み出し、バイト列として返送します。

いかがでしたか? 結構簡単にクラスが動的に取得可能なことが判って頂けたと思います。
HORBDynamicLoaderやHORBDeployServerは、まだまだ改良しなければいけない点があると思います。皆さんからのご意見をお待ちしております。


o-kikuchi@nesic.com