Save 37% off PRO during our Black Friday Sale! »

JavaでBytecode Weaving

358791a9c91e7615ffe62c7c6e02e470?s=47 zaki50
September 10, 2016

JavaでBytecode Weaving

コンパイルで生成されたバイトコードを書き換えてさまざまな機能を実現するBytecode weavingと呼ばれる技術があります。バイトコード自体を書き換えるため、ソースコードをシンプルに保ったままさまざまな付加機能を追加することができます。

本講演では、JavaにおけるBytecode weavingの実装方法とアノテーションプロセッサによるコード生成との比較、またモバイル向けのデータベースであるRealmにおいてBytecode weavingがどのように活用されているかについてお話します。

358791a9c91e7615ffe62c7c6e02e470?s=128

zaki50

September 10, 2016
Tweet

Transcript

  1. Java Ͱ Bytecode Weaving HACKER TACKLE my@realm.io 2016/9/10 ࢁ㟒 ੣

  2. Makoto Yamazaki Realm Inc. / uPhyca Inc. my@realm.io @zaki50

  3. Bytecode weaving? my@realm.io

  4. my@realm.io weaving? ৫Δ ฤΉ ࡞Δ ૊Έ্͛Δ

  5. my@realm.io Bytecode weavingͱ͸ ίϯύΠϧ࣌΍όΠτίʔυͷϩʔυ࣌ʹ όΠτίʔυʹखΛՃ͑Δ͜ͱ όΠτίʔυ͸ɺJava VM ͷ໋ྩηοτͷ͜ͱͰ͢

  6. my@realm.io ΞδΣϯμ RealmͰͷ۩ମྫ ࣮૷ํ๏ ͳʹ͕Ͱ͖Δͷ͔

  7. my@realm.io ͳʹ͕Ͱ͖Δͷ͔ όΠτίʔυͰදݱͰ͖Δ͜ͱͰ͋Ε͹ͳΜͰ΋

  8. my@realm.io ۩ମతʹ͸ʁ ಛఆͷॲཧͷফڈ Ϋϥε/ϝιουͷ௥Ճ ॲཧͷஔ͖׵͑ Կ͔ͷϦετΞοϓ

  9. my@realm.io ۩ମతʹ͸ʁ ϦϦʔεͰlogΛؔ࿈Λ࡟আ Realm retrolambda ServiceLoaderͷ୅ΘΓ

  10. Realm my@realm.io

  11. my@realm.io https://realm.io/

  12. my@realm.io ௚ײతͳϞσϧఆٛ public class Article extends RealmObject {
 @PrimaryKey
 public

    String id;
 public String title;
 public String contents;
 public Date createdAt;
 public Date modifiedAt;
 public User user;
 public RealmList<Comment> comments;
 }
  13. my@realm.io mobile database ? ܞଳ୺຤΍λϒϨοτ಺Ͱಈ͘σʔλϕʔε

  14. ϝϞϦʔ͕ݶΒΕͨঢ়گ my@realm.io CPUೳྗ͕ݶΒΕͨঢ়گ Why mobile database? → ஗Ԇϩʔυ → θϩίϐʔ

  15. my@realm.io θϩίϐʔ/஗Ԇϩʔυ Realm realm = ... // データの取得 RealmResults<Article> articles;

    articles = realm.where(Article.class).findAll(); Article article = articles.first(); article.title; // "Hacker Tackleで発表してきた" // よくある使い方 ListAdapter adapter = new ArticleAdapter(articles); listview.setAdapter(adapter);
  16. my@realm.io θϩίϐʔ/஗Ԇϩʔυ

  17. Ξϊςʔγϣϯϓϩηοα my@realm.io ϑΟʔϧυͷ஋ͷ஗Ԇϩʔυͷ࣮ݱ (Ҏલͷ࣮૷) (JSR 269)

  18. my@realm.io Ҏલͷ࣮૷ public class Article extends RealmObject {
 @PrimaryKey
 private

    String id; // その他のフィールド public String getId() { return id; }
 public void setId(String id) { this.id = id; } // その他のgetter/setter
 }
  19. my@realm.io Ҏલͷ࣮૷ public class ArticleRealmProxy extends Article { private Row

    row; @Override
 public String getId() { return row.get(1); }
 public void setId(String id) { row.set(1, id); }
 } by Ξϊςʔγϣϯϓϩηοα
  20. ଟ͘ͷ੍໿͕ଘࡏ 1. ϑΟʔϧυ΁ͷ௚઀ΞΫηεېࢭ 2. getter/setter͕ඞਢ 3. getter/setter͸஋ͷग़͠ೖΕͷΈ 4. ͦͷଞͷϝιουͷېࢭ(1Λపఈ͢ΔͨΊ) 5.

    ... my@realm.io
  21. Ξϊςʔγϣϯϓϩηοα my@realm.io + Bytecode weaving ੍໿Λͳͨ͘͢Ίʹʂ ϑΟʔϧυͷ஋ͷ஗Ԇϩʔυͷ࣮ݱ

  22. my@realm.io ࠓͷRealm Java public class Article extends RealmObject {
 @PrimaryKey


    public String id;
 public String title;
 public String contents;
 public Date createdAt;
 public Date modifiedAt;
 public User user;
 public RealmList<Comment> comments;
 }
  23. my@realm.io Ҏલͷ࣮૷(࠶ܝ) public class Article extends RealmObject {
 @PrimaryKey
 private

    String id; private String title;
 private String contents;
 private Date createdAt;
 private Date modifiedAt;
 private User user;
 private RealmList<Comment> comments;
 public String getId() { return id; }
 public void setId(String id) { this.id = id; } public String getTitle() { return title; }
 public void setTitle(String title) { this.title = title; } public String getContents() { contents ; } public void setContents(String contents) { contents = contents; } // かききれない
 }
  24. my@realm.io ࠓͷRealm Java public class ArticleRealmProxy extends Article { private

    Row row; public String realmGet$id() { return row.get(1); }
 public void realmSet$id(String id) { row.set(1, id); }
 } by Ξϊςʔγϣϯϓϩηοα
  25. my@realm.io ࠓͷRealm Java public class Article extends RealmObject { @PrimaryKey


    public String id; public String realmGet$id() { return id; }
 public void realmSet$id(String id) { return row.set(1, id); }
 } by bytecode weaving ௥Ճʂ
  26. my@realm.io ࠓͷRealm Java public class Article extends RealmObject {
 @PrimaryKey


    public String id; public RealmList<Comment> comments; public boolean hasComments() { return !comments.isEmpty(); } } by bytecode weaving this.realmGet$comments()
  27. my@realm.io ࠓͷRealm Java RealmResults<Article> results = realm.where(Article.class).findAll();
 Article article =

    results.first();
 String title = article.title; String title = article.realmGet$title(); by bytecode weaving
  28. ଟ͘ͷ੍໿͕ղফʂ 1. ϑΟʔϧυ΁ͷ௚઀ΞΫηεېࢭ 2. getter/setter͕ඞਢ 3. getter/setter͸஋ͷग़͠ೖΕͷΈ 4. ͦͷଞͷϝιουͷېࢭ(1Λపఈ͢ΔͨΊ) 5.

    ... my@realm.io
  29. Transform API my@realm.io Javassist ࣮૷ํ๏

  30. my@realm.io Transform API project.android.registerTransform(new RealmTransformer(project)) in build.gradle $ ./gradlew assemble

    ... :app:compileDebugSources :app:prePackageMarkerForDebug :app:unzipJacocoAgent :app:transformClassesWithJacocoForDebug :app:transformClassesWithRealmTransformerForDebug :app:transformClassesWithDexForDebug ...
  31. my@realm.io Transform API @Beta public abstract class Transform { //

    many other methods here public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { } }
  32. my@realm.io Transform API class RealmTransformer extends Transform { @Override public

    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { Context context = transformInvocation.getContext(); Collection<TransformInput> inputs = transformInvocation.getInputs(); Collection<TransformInput> referencedInputs; referencedInputs = transformInvocation.getReferencedInputs(); TransformOutputProvider outputProvider = transformInvocation.getOutputProvider(); boolean isIncremental = transformInvocation.isIncremental(); ... } }
  33. my@realm.io Javassist ந৅౓ ௿: ASM, Apache commons BCEL ந৅౓ த

    : javassist, CGLIB ந৅౓ ߴ : Retro lambda, AspectJ
  34. my@realm.io Javassist dependencies { compile 'org.javassist:javassist:3.20.0-GA' }

  35. my@realm.io Javassist CtClassΛ͍͡Δ ॻ͖ग़͢/ϩʔυ͢Δ ClassPoolΛ࡞Δ

  36. my@realm.io Realmͷྫ @Override void transform(...) throws IOException, ... { //

    Create and populate the Javassist class pool ClassPool classPool = createClassPool(inputs, referencedInputs) inputModelClasses.each { BytecodeModifier.addRealmAccessors(it) } inputClassNames.each { def ctClass = classPool.getCtClass(it) BytecodeModifier.useRealmAccessors(ctClass, allManagedFields, allModelClasses) ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) } }
  37. my@realm.io CtClassΛ͍͡Δ ॻ͖׵͑Δܥ ৽ͨʹ࡞Δܥ

  38. my@realm.io Ϋϥε/ϑΟʔϧυͷ௥Ճ // フィールド作成 CtClass strClass = pool.get("java.lang.String"); CtField barField

    = new CtField(CtClass.intType, "bar", strClass); barField.setModifiers(Modifier.PUBLIC); // クラス作成 CtClass fooClass = pool.makeClass("Foo"); // クラスへフィールド追加 fooClass.addField(f); // メソッド追加 CtMethod method = CtNewMethod.make(Modifier.PUBLIC, CtClass.booleanType, "isValid", new CtClass[0], new CtClass[0], "{return true;}", fooClass) fooClass.addMethod(method)
  39. my@realm.io ม਺ $0, $1, $2, ... $args $$ $cflow(...) $r

    $w $_ $sig $type $class thisおよび引き数 引き数の値の配列(Object[]型) $1,$2,... に展開される useCflow(String)で宣言した変数の値。 再帰呼び出しごとに++される 返り値の型(キャスト用) primitiveに対するキャストの形で書くとラッパー型に展開 Integer i = ($w)1; メソッドの返り値。insertAfter()などで使う メソッドで宣言された引き数のClassオブジェクトの配列 メソッドで宣言された返り値のClassオブジェクト 現在処理対象としている型のClassオブジェクト https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html
  40. my@realm.io ϑΟʔϧυΞΫηεͷॻ͖׵͑ public class FooEditor extends ExprEditor { void edit(FieldAccess

    fieldAccess) { if (fieldAccess.isReader()) { fieldAccess.replace("$_ = ($0 == this) ? $0.foo() : $0.foo;"); } } } CtBehavior someMethod = ...; someMethod.instrument(new FooEditor());
  41. my@realm.io ม਺(ϑΟʔϧυΞΫηε) $0 $1 $_ $r $class $type フィールドの属するインスタンス 書き込みアクセスの際に代入されようとしている値

    読込みアクセスの際に、読み込んだ値が入る予定の変数 読込みアクセスの際のフィールドの型 フィールドが属するクラスのClassオブジェクト フィールドの型 https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html
  42. my@realm.io ϝιουΞΫηεͷॻ͖׵͑ public class FooEditor extends ExprEditor { void edit(MethodCall

    m) { m("$_ = ($0 == this) ? $0.foo() : $0.foo;"); } } CtBehavior someMethod = ...; someMethod.instrument(new FooEditor());
  43. my@realm.io ม਺(ϝιουݺͼग़͠) $0 $1, $2, ... $_ $r $class $sig

    $type メソッドのレシーバー 引き数の値 メソッドの返り値を入れる変数 返り値の型 メソッドが属するクラスのClassオブジェクト メソッドで宣言された引き数のClassオブジェクトの配列 返り値の型 https://jboss-javassist.github.io/javassist/tutorial/tutorial2.html
  44. my@realm.io ͦͷଞͷॻ͖׵͑ ίϯετϥΫλݺͼग़͠ instanceof, cast new, new[] ྫ֎ͷcatch

  45. my@realm.io RealmͰͷ࣮ࡍͷίʔυ inputModelClasses.each { BytecodeModifier.addRealmAccessors(it) } inputClassNames.each { def ctClass

    = classPool.getCtClass(it) BytecodeModifier.useRealmAccessors(ctClass, allManagedFields, allModelClasses) ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) }
  46. my@realm.io RealmͰͷ࣮ࡍͷίʔυ public static void addRealmAccessors(CtClass clazz) {
 def methods

    = clazz.getDeclaredMethods()*.name
 clazz.declaredFields.each { CtField field ->
 if (!Modifier.isStatic(field.getModifiers()) && !field.hasAnnotation(Ignore.class)) {
 if (!methods.contains("realmGet\$${field.name}")) {
 clazz.addMethod(CtNewMethod.getter("realmGet\$${field.name}", field))
 }
 if (!methods.contains("realmSet\$${field.name}")) {
 clazz.addMethod(CtNewMethod.setter("realmSet\$${field.name}", field))
 }
 }
 }
 }
  47. my@realm.io RealmͰͷ࣮ࡍͷίʔυ public static void useRealmAccessors(CtClass clazz, List<CtField> managedFields, List<CtClass>

    modelClasses) {
 clazz.getDeclaredBehaviors().each { behavior ->
 if (( behavior instanceof CtMethod &&
 !behavior.name.startsWith('realmGet$') &&
 !behavior.name.startsWith('realmSet$')
 ) || (
 behavior instanceof CtConstructor &&
 !modelClasses.contains(clazz)
 )) {
 behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior))
 }
 }
 }
  48. my@realm.io RealmͰͷ࣮ࡍͷίʔυ private static class FieldAccessToAccessorConverter extends ExprEditor {
 final

    List<CtField> managedFields
 final CtClass ctClass
 final CtBehavior behavior
 
 @Override
 void edit(FieldAccess fieldAccess) throws CannotCompileException {
 def isRealmFieldAccess = managedFields.find {
 fieldAccess.className.equals(it.declaringClass.name) && fieldAccess.fieldName.equals(it.name)
 }
 if (isRealmFieldAccess != null) {
 def fieldName = fieldAccess.fieldName
 if (fieldAccess.isReader()) {
 fieldAccess.replace('$_ = $0.realmGet$' + fieldName + '();')
 } else if (fieldAccess.isWriter()) {
 fieldAccess.replace('$0.realmSet$' + fieldName + '($1);')
 }
 }
 }
 }
  49. @RealmClass public class Dog implements RealmModel { public String name;

    public int age; } Realm realm = Realm.getDefaultInstance(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.name = "Rex"; dog.age = 1; realm.commitTransaction(); RealmResults<Dog> pups = realm.where(Dog.class) .lessThan("age", 2) .findAll(); my@realm.io