プログラマメモ2 - programmer no memo2

hotdeployもどき 2007/02/13

使ったことがないので詳しく言及できないのですが、Seasar2というDIコンテナにはHotDeployという機能があるでそうです。
なにやら、実行中に新しくクラスが読み込めるようです。

どうやって実現しているのだろうと簡単に調べてみたところ、

クラス・ローダに読ませたものは変更できない。では、リクエストのたびに新しいクラス・ローダに読ませればいいじゃないか、と考えました。 Seasarメディア準備号 » ひがインタビュー

という記事をみつけました。

この記事と、Seasar2のソースコードを参考にして、お手軽なコードを作成してみました。
package hotdeploy;

import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import javax.swing.JPanel;
import javax.swing.JFrame;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import java.awt.GridBagConstraints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestFrame extends JFrame {

private static final long serialVersionUID = 1L;
private JPanel jContentPane = null;
private JPanel jPanel = null;
private JButton jButton = null;

private JPanel getJPanel() {
if (jPanel == null) {
GridBagConstraints gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
jPanel = new JPanel();
jPanel.setLayout(new GridBagLayout());
jPanel.add(getJButton(), gridBagConstraints);
}
return jPanel;
}

private JButton getJButton() {
if (jButton == null) {
jButton = new JButton();
jButton.setText("load class");
jButton.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {
/*
* クラスローダーを入れ替えます。
*/
ClassLoader originalClassLoader = Thread.currentThread()
.getContextClassLoader();
MyHotDeploy myHotDeploy = new MyHotDeploy(
originalClassLoader);
Thread.currentThread().setContextClassLoader(myHotDeploy);
try {
/*
* TestAクラスを読み込みます。
*/
Class class1 = Thread.currentThread()
.getContextClassLoader().loadClass(
"hotdeploy.TestA");
Object object = class1.newInstance();
/*
* 特定のメソッドを実行します。
*/
Method method = object.getClass().getMethod("logic",
null);
method.invoke(object, new Object[] {});
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} catch (InstantiationException e2) {
e2.printStackTrace();
} catch (IllegalAccessException e3) {
e3.printStackTrace();
} catch (SecurityException e4) {
e4.printStackTrace();
} catch (NoSuchMethodException e5) {
e5.printStackTrace();
} catch (IllegalArgumentException e6) {
e6.printStackTrace();
} catch (InvocationTargetException e7) {
e7.printStackTrace();
}

/*
* クラスローダーをもとにもどしておきます。
*/
Thread.currentThread().setContextClassLoader(
originalClassLoader);
}
});
}
return jButton;
}

public static void main(String[] args) {

SwingUtilities.invokeLater(new Runnable() {
public void run() {
TestFrame thisClass = new TestFrame();
thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
thisClass.setVisible(true);
}
});
}

public TestFrame() {
super();
initialize();
}

private void initialize() {
this.setSize(300, 200);
this.setContentPane(getJContentPane());
this.setTitle("JFrame");
}

private JPanel getJContentPane() {
if (jContentPane == null) {
jContentPane = new JPanel();
jContentPane.setLayout(new BorderLayout());
jContentPane.add(getJPanel(), BorderLayout.CENTER);
}
return jContentPane;
}

}


package hotdeploy;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyHotDeploy extends ClassLoader {

public MyHotDeploy(ClassLoader parent) {
super(parent);
}

protected synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {

/*
* 特定のクラスだけ読み込みます。
*/
if (name.startsWith("hotdeploy")) {
String name2 = name.replaceAll("\\.", "/") + ".class";
InputStream inputStream = getResourceAsStream(name2);
byte[] bs = getBytes(inputStream);
Class class1 = defineClass(name, bs, 0, bs.length);
return class1;
}

return super.loadClass(name, resolve);
}

public static final byte[] getBytes(InputStream is) {
byte[] bs = null;
byte[] buf = new byte[8192];
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int n = 0;
while ((n = is.read(buf, 0, buf.length)) != -1) {
baos.write(buf, 0, n);
}
bs = baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bs;
}

}


package hotdeploy;

public class TestA {

public void logic(){
System.out.println("okikae");
}
}


簡単な説明。
TestFrameを実行しますと、ボタンつきフレームがでてきます。
TestAのクラスを書き換えて、コンパイルして、ボタンをおしますと新しいクラスを読み込みこんで実行します。

自分で実際のクラスファイルを読み込んでいます。
実行時に、リフレクションを使用しないといけないのが面倒かもしれませんね。

アプリケーションの実行時に動的にロジックを変更する方法としては、現状なら、スクリプトファイルにロジックを切り出して、その都度実行という方法がいいかなぁと考えています。

: