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

hotdeployもどき その2 2007/02/15

hotdeployもどき その2を考えてみました。

hotdeployを実現するには、クラスローダーを用意して、hotdeployかけたいクラスを読み込めばいいことがわかりました。

そこでおきる問題は、hotdeployのクラスローダーで読みこんだクラスと、先に読み込んだクラスローダーで読み込まれるクラスはまったく別ものだということです。

つまり、同じクラスでもキャストができません。

ですので、hotdeployの対象となるクラスだけ読み込みなおすということと、実装と定義の分離が重要となります。

hotdeployする対象のクラスとそのインターフェイスを分離することで、実行時にリフレクションを使わないで実行できます。

その際の注意点は、インターフェイスはオリジナルのクラスローダーで読み込ませないようにすることです。

お手軽な実験ソースです。

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 JButton jButton1 = 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);
jPanel.add(getJButton1(), new 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;
}

/**
* This method initializes jButton1
*
* @return javax.swing.JButton
*/
private JButton getJButton1() {
if (jButton1 == null) {
jButton1 = new JButton();
jButton1.setText("load class2");
jButton1.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();

Class[] classes = object.getClass().getInterfaces();
for (int i = 0; i < classes.length; i++) {
Class class2 = classes[i];
System.out.println(class2);
}
/*
* インターフェイスはMyHotDeployクラスローダーで読み込ませるClassCastExceptionがでてしまいます。
*/
if (object instanceof ILogic) {
ILogic logic = (ILogic) object;
logic.logic();
}

} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} catch (InstantiationException e2) {
e2.printStackTrace();
} catch (IllegalAccessException e3) {
e3.printStackTrace();
} catch (SecurityException e4) {
e4.printStackTrace();
} catch (IllegalArgumentException e6) {
e6.printStackTrace();
}

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

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;

public class TestA implements ILogic{

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


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 {

/*
* HotDeployをかけたい特定のクラスだけ読み込みこむようにします。
*/
if (name.startsWith("hotdeploy.TestA")) {
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 interface ILogic {

public void logic();

}

: