SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

プラグインで拡張できるJavaプログラムを作る

プラグイン方式によるプログラムの機能拡張

プラグイン対応アプリケーションの作成

 では、アプリケーション側のクラスを作成しましょう。ここでは、メインクラスとなるSamplePluginAppには計算のための処理は用意せず、サンプルとしてプラグインクラスを1つ標準で用意しておき、それをデフォルトで組み込んでおく形にしておきました。

メインクラス
package jp.tuyano.codezine;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.net.*;
import java.util.*;
import java.util.jar.*;

import javax.swing.*;

public class SamplePluginApp extends JFrame {
    private static final long serialVersionUID = 1L;
    
    private JTextField result;
    private JComboBox pluginsCombo;
    private JPanel panel;
    
    private ArrayList <SamplePluginAppPlugin>plugins;
    private SamplePluginAppPlugin selectedPlugin;
    private JPanel selectedPluginPanel;

    public static void main(String[] args) {
        new SamplePluginApp().setVisible(true);
    }
    
    // コンストラクタ。基本GUIの作成
    public SamplePluginApp(){
        result = new JTextField();
        this.add(result,BorderLayout.NORTH);
        panel = new JPanel();
        panel.setLayout(new BorderLayout());
        pluginsCombo = this.getPluginComboBox();
        pluginsCombo.setBounds(new Rectangle(25,25,100,20));
        pluginsCombo.addActionListener(new pluginComboAction());
        panel.add(pluginsCombo,BorderLayout.NORTH);
        panel.add(selectedPlugin.getPanel(),BorderLayout.CENTER);
        this.add(panel,BorderLayout.CENTER);
        JButton button = new JButton("calc");
        button.addActionListener(new buttonAction());
        this.add(button,BorderLayout.SOUTH);
        this.setSize(new Dimension(200,200));
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    
    // JComboBoxの作成
    public JComboBox getPluginComboBox(){
        JComboBox combo = new JComboBox();
        plugins = getPlugins();
        // デフォルトプラグインの準備
        selectedPlugin = new DefaultPlugin();
        selectedPluginPanel = selectedPlugin.getPanel();
        combo.addItem(selectedPlugin);
        
        for(int i = 0;i < plugins.size();i++){
            SamplePluginAppPlugin plugin = plugins.get(i);
            combo.addItem(plugin);
        }
        return combo;
    }
    
    // JComboBoxのActionListener
    class pluginComboAction implements ActionListener {
        public void actionPerformed(ActionEvent ev){
            panel.removeAll();
            panel.add(pluginsCombo,BorderLayout.NORTH);
            selectedPlugin = (SamplePluginAppPlugin)
                    pluginsCombo.getSelectedItem();
            selectedPluginPanel = selectedPlugin.getPanel();
            panel.add(selectedPluginPanel);
            panel.updateUI();
        }
    }
    
    // JButtonのActionListener
    class buttonAction implements ActionListener {
        public void actionPerformed(ActionEvent ev){
            result.setText(selectedPlugin.getResult());
        }
    }
    
    // プラグインクラスのインスタンスをArrayListにまとめて返す
    public ArrayList<SamplePluginAppPlugin> getPlugins() {
        ArrayList <SamplePluginAppPlugin>plugins =
                new ArrayList<SamplePluginAppPlugin>();
        String cpath = System.getProperty("user.dir") +
                File.separator + "plugins";
        try {
            File f = new File(cpath);
            String[] files = f.list();
            for (int i = 0; i < files.length; i++) {
                if (files[i].endsWith(".jar")) {
                    File file = new File(cpath + File.separator +
                            files[i]);
                    JarFile jar = new JarFile(file);
                    Manifest mf = jar.getManifest();
                    Attributes att = mf.getMainAttributes();
                    String cname = att.getValue("Plugin-Class");
                    URL url = file.getCanonicalFile().toURI().toURL();
                    URLClassLoader loader = new URLClassLoader(
                            new URL[] { url });
                    Class cobj = loader.loadClass(cname);
                    Class[] ifnames = cobj.getInterfaces();
                    for (int j = 0; j < ifnames.length; j++) {
                        if (ifnames[j] == SamplePluginAppPlugin.class) {
                            System.out.println("load..... " + cname);
                            SamplePluginAppPlugin plugin =
                                (SamplePluginAppPlugin)cobj.newInstance();
                            plugins.add(plugin);
                            break;
                        }
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return plugins;
    }
}

// サンプルプラグインのクラス
class DefaultPlugin implements SamplePluginAppPlugin {
    private JTextField input;
    private JPanel currentPanel;
    
    public String toString(){
        return "Default(tax)";
    }

    public String getResult() {
        String inputData = input.getText();
        if (inputData == null) inputData = "0";
        int n;
        try {
            n = Integer.parseInt(inputData);
        } catch (NumberFormatException e) {
            e.printStackTrace();
            n = 0;
        }
        double res = ((int)(n * 1.05 * 100)) / 100;
        return Double.toString(res);
    }

    public void setInputData(String str) {
        input.setText(str);
    }
    
    public String getInputData(){
        return input.getText();
    }

    public JPanel getPanel() {
        currentPanel = new JPanel();
        currentPanel.setLayout(null);
        input = new JTextField();
        input.setBounds(new Rectangle(25,25,50,20));
        currentPanel.add(input);
        return currentPanel;
    }
    
}
プラグイン方式に改良されたプログラム。デフォルトでプラグインが1つ組み込まれている。
プラグイン方式に改良されたプログラム。デフォルトでプラグインが1つ組み込まれている。

プラグイン読み込みの処理

 SamplePluginAppでは、getPluginsメソッドでプラグインをロードしArrayListにまとめる処理をしています。処理の流れを整理していきましょう。――まずは、アプリケーションのディレクトリ内にある「plugins」フォルダのパスを取得します。

String cpath = System.getProperty("user.dir") +
        File.separator + "plugins";

 アプリケーションも実行可能Jarとしてまとめる前提で考えています。この場合、System.getProperty("user.dir")でアプリケーションがおかれているディレクトリのパスが得られます。これにFile.separatorを使って"plugins"を付け足すことで「plugins」フォルダのパスが得られます。

File f = new File(cpath);
String[] files = f.list();

 「plugins」フォルダ内の全ファイルを取得します。これはFileインスタンスのlist()で得ることができます。こうして取得されたファイルの中から、forによる繰り返しを使って、この中からJarファイルだけをピックアップしていきます。

for (int i = 0; i < files.length; i++) {
    if (files[i].endsWith(".jar")) {

 ファイル名の末尾が".jar"で終わるものだけを処理していきます。まずFileインスタンスを作成し、それを元にJarFileインスタンスを作成します。

File file = new File(cpath + File.separator + files[i]);
JarFile jar = new JarFile(file);

 続いて、JarFileからManifestを取得します。これは名前の通り、マニフェストファイルを示すクラスです。そこからメインアトリビュート(属性)を取得し、その中にある"Plugin-Class"の値を取得します。

Manifest mf = jar.getManifest();
Attributes att = mf.getMainAttributes();
String cname = att.getValue("Plugin-Class");

 こうして得られたプラグインクラスをJarファイルから読み込みます。これには、JarファイルをURLとして取得し、そこからURLClassLoaderを作成します。

URL url = file.getCannicalFile().toURI().toURL();
URLClassLoader loader = new URLClassLoader(
        new URL[] { url });

 そしてこのURLClassLoaderloadClass()を使い、プラグインクラスのClassを取得し、そこからインターフェイスをすべて取り出します。

Class cobj = loader.loadClass(cname);
Class[] ifnames = cobj.getInterfaces();

 これでプラグイン・クラスにimplementsされている全インターフェイスのClassが得られました。後は、forを使い、この中にSamplePluginAppPluginが含まれていないかチェックすればいいわけです。

for (int j = 0; j < ifnames.length; j++) {
    if (ifnames[j] == SamplePluginAppPlugin.class) {

 もし含まれていた場合には、newInstance()メソッドでSamplePluginAppPluginインスタンスを作成し、これをArrayListに追加します。

System.out.println("load..... " + cname);
SamplePluginAppPlugin plugin =
    (SamplePluginAppPlugin)cobj.newInstance();
plugins.add(plugin);

 これで、JarからSamplePluginAppPluginをimplementsしてあるクラスのインスタンスを作成しArrayListにまとめる、という処理ができました。Jarファイルの作成、マニフェストの読み込み、属性の値の取得、インターフェイスの取得、クラスのインスタンスの作成――これらの流れが一通り理解できれば、Jarファイルからプラグインのクラスを読み込むことはほぼできるようになります。

次のページ
コンボボックスの処理

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

掌田 津耶乃(ショウダ ツヤノ)

三文ライター&三流プログラマ。主にビギナーに向けたプログラミング関連の執筆を中心に活動している。※現在、入門ドキュメントサイト「libro」、カード型学習サイト「CARD.tuyano.com」を公開...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1271 2007/05/15 08:00

おすすめ

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー