ObjectInputStream#resolveClass(ObjectStreamClass): ClassのClassLoaderの選定動作の確認用のプロジェクト
1-run.shで実行できます。 2-result.txtが実行結果です
ObjectInputStream#resolveClass(ObjectStreamClass): ClassのClassLoaderの選定動作の確認用のプロジェクト
1-run.shで実行できます。 2-result.txtが実行結果です
| mkdir out | |
| javac -d out C0Main.java | |
| java -cp out C0Main > 2-result.txt |
| ============ object input stream subclass ============ | |
| OIS: 使用するObjectInputStreamのローダー | |
| called: readObjectを呼ぶクラスのローダー | |
| object: readObjectの戻り値のクラスローダー | |
| ==== overloaded object input stream subclass ==== | |
| OIS: loader1, called: appLoad, object: loader1 | |
| OIS: loader2, called: appLoad, object: loader2 | |
| OIS: appLoad, called: appLoad, object: appLoad | |
| ==== non-overloaded object input stream subclass ==== | |
| OIS: loader1, called: appLoad, object: appLoad | |
| OIS: loader2, called: appLoad, object: appLoad | |
| OIS: appLoad, called: appLoad, object: appLoad | |
| ============ readObject ============ | |
| classReader: SerializeableのreadObject内でOIS.readObjectを呼ぶためのクラスのローダー | |
| called: readObjectを呼ぶクラスのローダー | |
| object: SerializeableのreadObject内でのOIS.readObjectの戻り値のローダー | |
| ======== | |
| classReader: loader1, called: appLoad, object: loader1 | |
| classReader: loader2, called: appLoad, object: loader2 | |
| classReader: appLoad, called: appLoad, object: appLoad |
| import java.io.ByteArrayInputStream; | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.ObjectInputStream; | |
| import java.io.ObjectOutputStream; | |
| import java.io.ObjectStreamClass; | |
| import java.io.Serializable; | |
| import java.net.URL; | |
| import java.net.URLClassLoader; | |
| import java.util.function.Function; | |
| import java.util.function.Supplier; | |
| public class C0Main { | |
| ClassLoader loader1; | |
| ClassLoader loader2; | |
| ClassLoader appLoad; | |
| byte[] bytesClassToSerialize1; | |
| byte[] bytesClassToSerialize2; | |
| public C0Main() throws Throwable { | |
| // prepare | |
| // 1. classpathの取得 | |
| String classPath = getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); | |
| // 2. classLoaderの生成 | |
| loader1 = new URLClassLoader(new URL[]{ new File(classPath).toURI().toURL() }, null); | |
| loader2 = new URLClassLoader(new URL[]{ new File(classPath).toURI().toURL() }, null); | |
| appLoad = C0Main.class.getClassLoader(); | |
| // 3. ちゃんと別のクラスが生成されてることを確認 | |
| if (loader1.loadClass(ClassToSerialize1.class.getName()) | |
| == loader2.loadClass(ClassToSerialize1.class.getName())) | |
| throw new IllegalStateException("bug"); | |
| bytesClassToSerialize1 = serialize(new ClassToSerialize1()); | |
| bytesClassToSerialize2 = serialize(new ClassToSerialize2(ClassToSerialize1.class)); | |
| } | |
| private void runTest() throws Throwable { | |
| System.out.println("============ object input stream subclass ============"); | |
| runTestOISExtend(); | |
| System.out.println("============ readObject ============"); | |
| runTestReadObject(); | |
| } | |
| // OISの継承クラス | |
| private void runTestOISExtend() throws Throwable { | |
| ObjectInputStream stream; | |
| System.out.println("OIS: 使用するObjectInputStreamのローダー"); | |
| System.out.println("called: readObjectを呼ぶクラスのローダー"); | |
| System.out.println("object: readObjectの戻り値のクラスローダー"); | |
| System.out.println("==== overloaded object input stream subclass ===="); | |
| stream = createOIS(loader1, ObjectInputStream1.class, new ByteArrayInputStream(bytesClassToSerialize1)); | |
| printClassLoader("OIS: loader1, called: appLoad", stream.readObject().getClass()); | |
| stream = createOIS(loader2, ObjectInputStream1.class, new ByteArrayInputStream(bytesClassToSerialize1)); | |
| printClassLoader("OIS: loader2, called: appLoad", stream.readObject().getClass()); | |
| stream = createOIS(appLoad, ObjectInputStream1.class, new ByteArrayInputStream(bytesClassToSerialize1)); | |
| printClassLoader("OIS: appLoad, called: appLoad", stream.readObject().getClass()); | |
| System.out.println("==== non-overloaded object input stream subclass ===="); | |
| stream = createOIS(loader1, ObjectInputStream2.class, new ByteArrayInputStream(bytesClassToSerialize1)); | |
| printClassLoader("OIS: loader1, called: appLoad", stream.readObject().getClass()); | |
| stream = createOIS(loader2, ObjectInputStream2.class, new ByteArrayInputStream(bytesClassToSerialize1)); | |
| printClassLoader("OIS: loader2, called: appLoad", stream.readObject().getClass()); | |
| stream = createOIS(appLoad, ObjectInputStream2.class, new ByteArrayInputStream(bytesClassToSerialize1)); | |
| printClassLoader("OIS: appLoad, called: appLoad", stream.readObject().getClass()); | |
| } | |
| // readObjectのオーバーライド | |
| private void runTestReadObject() throws Throwable { | |
| ObjectInputStream stream; | |
| System.out.println("classReader: SerializeableのreadObject内でOIS.readObjectを呼ぶためのクラスのローダー"); | |
| System.out.println("called: readObjectを呼ぶクラスのローダー"); | |
| System.out.println("object: SerializeableのreadObject内でのOIS.readObjectの戻り値のローダー"); | |
| System.out.println("========"); | |
| setClassReader(appLoad, createClassReader(loader1)); | |
| stream = new ObjectInputStream(new ByteArrayInputStream(bytesClassToSerialize2)); | |
| printClassLoader("classReader: loader1, called: appLoad", getTheClass(stream.readObject())); | |
| setClassReader(appLoad, createClassReader(loader2)); | |
| stream = new ObjectInputStream(new ByteArrayInputStream(bytesClassToSerialize2)); | |
| printClassLoader("classReader: loader2, called: appLoad", getTheClass(stream.readObject())); | |
| setClassReader(appLoad, createClassReader(appLoad)); | |
| stream = new ObjectInputStream(new ByteArrayInputStream(bytesClassToSerialize2)); | |
| printClassLoader("classReader: appLoad, called: appLoad", getTheClass(stream.readObject())); | |
| } | |
| public static void main(String[] args) throws Throwable { | |
| new C0Main().runTest(); | |
| } | |
| /** | |
| * OISの継承クラスに関する調査用 | |
| */ | |
| public static class ClassToSerialize1 implements Serializable { } | |
| /** | |
| * readObjectの動作確認用 | |
| * | |
| * デシリアライズ時にtheClassはclassReaderで読んだClassインスタンスになる | |
| */ | |
| public static class ClassToSerialize2 implements Serializable, Supplier<Class<?>> { | |
| public static Function<ObjectInputStream, Class<?>> classReader; | |
| public Class<?> theClass; | |
| public ClassToSerialize2(Class<?> theClass) { | |
| this.theClass = theClass; | |
| } | |
| private void writeObject(java.io.ObjectOutputStream stream) throws IOException { | |
| stream.writeObject(theClass); | |
| } | |
| private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { | |
| theClass = classReader.apply(stream); | |
| } | |
| @Override | |
| public Class<?> get() { | |
| return theClass; | |
| } | |
| } | |
| /** | |
| * 例外処理を除けば以下のコードと同一な{@link Function}オブジェクト | |
| * <pre>{@code | |
| * (objectInputStream) -> (Class<?>) objectInputStream.readObject() | |
| * }</pre> | |
| */ | |
| public static class ClassReader implements Function<ObjectInputStream, Class<?>> { | |
| @Override | |
| public Class<?> apply(ObjectInputStream objectInputStream) { | |
| try { | |
| return (Class<?>) objectInputStream.readObject(); | |
| } catch (IOException | ClassNotFoundException e) { | |
| throw ClassReader.<RuntimeException>thr(e); | |
| } | |
| } | |
| @SuppressWarnings("unchecked") | |
| private static <T extends Throwable> T thr(Exception e) throws T { | |
| throw (T) e; | |
| } | |
| } | |
| /** | |
| * resolveClassをsuperを呼び出すだけの形にオーバーライドしたObjectInputStream | |
| */ | |
| public static class ObjectInputStream1 extends java.io.ObjectInputStream { | |
| public ObjectInputStream1(InputStream in) throws IOException { | |
| super(in); | |
| } | |
| @Override | |
| protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { | |
| return super.resolveClass(desc); | |
| } | |
| } | |
| /** | |
| * なにもオーバーライドしていないObjectInputStream | |
| */ | |
| public static class ObjectInputStream2 extends java.io.ObjectInputStream { | |
| public ObjectInputStream2(InputStream in) throws IOException { | |
| super(in); | |
| } | |
| } | |
| // utils | |
| /** | |
| * {@code readObject}の{@link ClassToSerialize2#theClass}を取得する | |
| */ | |
| @SuppressWarnings("unchecked") | |
| private Class<?> getTheClass(Object readObject) { | |
| return ((Supplier<Class<?>>)readObject).get(); | |
| } | |
| /** | |
| * readObjectのクラスローダーの情報を出力 | |
| */ | |
| private void printClassLoader(String s, Class<?> readObject) { | |
| System.out.print(s); | |
| System.out.print(", object: "); | |
| ClassLoader loader = readObject.getClassLoader(); | |
| if (loader == appLoad) | |
| System.out.print("appLoad"); | |
| else if (loader == loader1) | |
| System.out.print("loader1"); | |
| else if (loader == loader2) | |
| System.out.print("loader2"); | |
| else | |
| System.out.print("unknown"); | |
| System.out.println(); | |
| } | |
| /** | |
| * ObjectOutputStreamでシリアライズ | |
| */ | |
| private byte[] serialize(Object obj) throws Throwable { | |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
| try (ObjectOutputStream stream = new ObjectOutputStream(baos)) { | |
| stream.writeObject(obj); | |
| } | |
| return baos.toByteArray(); | |
| } | |
| /** | |
| * {@code loader}で{@code classDesc}をロードし、{@code stream}を引数にインスタンスを生成する | |
| */ | |
| private ObjectInputStream createOIS(ClassLoader loader, Class<?> classDesc, InputStream stream) throws Throwable { | |
| return (ObjectInputStream) loader.loadClass(classDesc.getName()) | |
| .getConstructor(InputStream.class) | |
| .newInstance(stream); | |
| } | |
| /** | |
| * {@code loader}で{@link ClassReader}をロードする | |
| */ | |
| @SuppressWarnings("unchecked") | |
| private Function<ObjectInputStream, Class<?>> createClassReader(ClassLoader loader) throws Throwable { | |
| return (Function<ObjectInputStream, Class<?>>) loader.loadClass(ClassReader.class.getName()).newInstance(); | |
| } | |
| /** | |
| * {@code loader}の{@link ClassToSerialize2#classReader}をsetする | |
| */ | |
| private void setClassReader( | |
| ClassLoader loader, | |
| Function<ObjectInputStream, Class<?>> reader) throws Throwable { | |
| loader.loadClass(ClassToSerialize2.class.getName()) | |
| .getField("classReader") | |
| .set(null, reader); | |
| } | |
| } |