お久しぶりです。生きてます。
最近忙しすぎてサボってました。書くネタはそれなりにあるのですが…
さて、Java11がリリースされましたね。僕も早速検証中ですが、 すぐに壁にぶち当たりました。MyBatisのlazy loadingが動きません。
結論から言うと、org.apache.ibatis.javassist.util.proxy.DefineClassHelperがsun.mics.Unsafeを使っているところを、jdk.internal.misc.Unsafeを使うように切り替える必要があります。 ただこれ、当然ながらソースコードにハードコーディングされているわけでして、なんとかしようと思うと
- 毎度おなじみJavassistで無理やり書き換える
- ソースをコピーして書き換える
のどちらかになるかと。ただ、(1)はあまりお勧めできません。sun.misc.Unsafeとjdk.internal.misc.Unsafeは共通のインタフェースがあるわけではなくて完全に別クラスになり、 するとフィールドをJavassistで強引に入れ替えるだけでは済まず、メソッド定義も全面的に書き換える必要があります。要するにコードの書き直しに等しい。
よって、とりあえず今回はめんどくさいので(2)の作戦を採用します。 適当なソースフォルダにDefineClassHelperのソースをコピーして、以下の箇所を書き換えます。
package org.apache.ibatis.javassist.util.proxy; ... import jdk.internal.misc.Unsafe; // sun.misc.Unsafe→jdk.internal.misc.Unsafeへ置換 ... public class DefineClassHelper { // ...略 static { if (ClassFile.MAJOR_VERSION < 53) { try { final Class<?> cl = Class.forName("java.lang.ClassLoader"); defineClass1 = SecurityActions.getDeclaredMethod(cl, "defineClass", new Class[] { String.class, byte[].class, Integer.TYPE, Integer.TYPE }); defineClass2 = SecurityActions.getDeclaredMethod(cl, "defineClass", new Class[] { String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class }); } catch (final Exception e) { throw new RuntimeException("cannot initialize", e); } } else { try { // 元のコード // final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); // theUnsafe.setAccessible(true); // sunMiscUnsafe = (Unsafe) theUnsafe.get((Object) null); // このようにする。もはやsunMiscUnsafeというフィールド名は不適切だがめんどくさいのでそのまま sunMiscUnsafe = Unsafe.getUnsafe(); } catch (final Throwable e) { } } } }
とりあえずJava11だけで動けばいいならば、必要最小限だけ残すと、
public class DefineClassHelper { private static final Unsafe theUnsafe; public static Class<?> toClass(final String className, final ClassLoader loader, final ProtectionDomain domain, final byte[] bcode) throws CannotCompileException { try { return theUnsafe.defineClass(className, bcode, 0, bcode.length, loader, domain); } catch (final Throwable e) { throw new CannotCompileException(e); } } static Class<?> toPublicClass(final String className, final byte[] bcode) throws CannotCompileException { try { Lookup lookup = MethodHandles.lookup(); lookup = lookup.dropLookupMode(2); return lookup.defineClass(bcode); } catch (final Throwable e) { throw new CannotCompileException(e); } } static { try { theUnsafe = Unsafe.getUnsafe(); } catch (final Throwable e) { throw new ExceptionInInitializerError(e); } } }
です。
そして、jdkの内部クラスを無理やり使うために
--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED
というオプションをjavaおよびjavacに与える必要があります。
現場からは以上です。