第三回ありえる社内勉強会 「いわががのLombok」

55
第3回 ありえる社内勉強会 「いわががのLombok」

Transcript of 第三回ありえる社内勉強会 「いわががのLombok」

Page 1: 第三回ありえる社内勉強会 「いわががのLombok」

第3回 ありえる社内勉強会「いわががのLombok」

Page 2: 第三回ありえる社内勉強会 「いわががのLombok」

お前だれよ?

twitter: @kiris いわなが?いわがが?

Page 3: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokって何?

http://projectlombok.org Javaの冗長性を排除する為

のライブラリ 「赤唐辛子」の意味 v0.10.4 MIT license

created by Roel Spilker

Reinier Zwitserloot

Page 4: 第三回ありえる社内勉強会 「いわががのLombok」

Javaの冗長性って?

こういうのとか

class Data { private int value;

public int getValue() { return value; } public void setValue(int value) { this.value = value; }}

Page 5: 第三回ありえる社内勉強会 「いわががのLombok」

Javaの冗長性って?

後、こういうのとか…

InputStream in = new InputStream(args[0]);try { ...} finally { If (in != null) in.close();}

Page 6: 第三回ありえる社内勉強会 「いわががのLombok」

Javaの冗長性って?

他にも、こういうのとか…

Map<String, List<String>> map = new HashMap<String, List<String>>();

...

for(Map.Entry<String, List<String>> entry : map) { ...}

Page 7: 第三回ありえる社内勉強会 「いわががのLombok」

Javaの冗長性って?

……

class MyClass { private static Log log = LogFactory.getLog(MyClass.class);

private final String name;

public MyClass(String name) { if (name == null) { throw new NullPointerException(); } this.name = name; }

@Override public int toString() { return “MyClass(name=”+ this.name +“)”; }

@Override public boolean equals(Object other) {

}

Page 8: 第三回ありえる社内勉強会 「いわががのLombok」

Javaの冗長性って?

 

こうならない為のLombok!続きはWebで!!

Page 9: 第三回ありえる社内勉強会 「いわががのLombok」

冗長の何がいけないの?

生産性が下がる コード量が増えて読みづらくなる バグが入り込む可能性がある 死にたくなる

Page 10: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokの導入

Page 11: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokを入手する

Download lombok.jar http://projectlombok.org/download.html

Maven or Ivy http://projectlombok.org/mavenrepo/index.html

Page 12: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokを使う

Javac Classpathに追加

GWT java -javaagent:lombok.jar=ECJ

Play Framework https://github.com/aaronfreeman/play-lombok#readme

ECJ java -javaagent:lombok.jar=ECJ \

-Xbootclasspath/p:lombok.jar -jar ecj.jar -cp lombok.jar

Page 13: 第三回ありえる社内勉強会 「いわががのLombok」

LombokをIDEでも使う

Eclipse, NetBeans なんかに対応 IDEA IntelliJはまだ未対応

java -jar lombok.jar

Page 14: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokを試してみる

Page 15: 第三回ありえる社内勉強会 「いわががのLombok」

@Data

@Dataの主な機能 全てのフィールドのgetter / setter の生成 toString, equals, hashCodeの生成 finalフィールドを引数にしたコンストラクタの生成

import lombok.Datapublic @Data class DataExample { private final String name; private int count; private List<Object> list;}

Page 16: 第三回ありえる社内勉強会 「いわががのLombok」

結果の確認(delombok)

変換後のコードを出力 java -jar lombok.jar delombok -p ${src}

ファイルとして保存 java -jar lombok.jar delombok -d ${output} ${src}

Ant <delombok verbose="true" encoding="UTF-8" to="$

{output}" from="${src}" /> Maven

https://github.com/awhitford/lombok.maven

Page 17: 第三回ありえる社内勉強会 「いわががのLombok」

@Data(変換後)

public class DataExample { private final String name; private int count; private List<Object> list;

public DataExample(String name) { this.name = name; }

public String getName() { return name; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } @Override public String toString() { ... } @Override public boolean equals(Object other) { ... } @Override public int hashCode() { … }}

Page 18: 第三回ありえる社内勉強会 「いわががのLombok」

Eclipseからも即時反映

その場でアウトラインや補完候補に表示されます

Page 19: 第三回ありえる社内勉強会 「いわががのLombok」

他の機能

@Getter / @Setter

@Getter(lazy=true)

@ToString

@EqualsAndHashCode

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor

@Data

@Cleanup

@Synchronized

@SneakyThrows

@Log

val

@Delegate

http://projectlombok.org/features/index.html

Page 20: 第三回ありえる社内勉強会 「いわががのLombok」

@Getter / @Setter

Getter / Setterの自動生成 @Dataよりも優先

public class GetterSetterExample { @Getter @Setter private String name; @Getter(AccessLevel.PROTECTED) private int age;}

Page 21: 第三回ありえる社内勉強会 「いわががのLombok」

@Getter / @Setter(変換後)

Getter / Setterの自動生成 @Dataよりも優先

public class GetterSetterExample { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } protected int getAge() { return age; }}

Page 22: 第三回ありえる社内勉強会 「いわががのLombok」

@Getter(lazy=true)

いわゆるメモ化 サブルーチン(関数)呼び出しの結果を保持し、再利用するこ

とで、そのサブルーチンの呼び出し毎の再計算を防ぐpublic class GetterLazyExample { @Getter(lazy=true) private final double[] cached = expensive();

private double[] expensive() { ... }}

Page 23: 第三回ありえる社内勉強会 「いわががのLombok」

@Getter(lazy=true)(変換後)

いわゆるメモ化 サブルーチン(関数)呼び出しの結果を保持し、再利用するこ

とで、そのサブルーチンの呼び出し毎の再計算を防ぐpublic class GetterLazyExample { public double[] getCached() { // 本当はthread-safe if (!this.$lombok$lazy1i) { this.$lombok$lazy1v = expensive(); this.$lombok$lazy1i = true; } return this.$lombok$lazy1v; }

private double[] expensive() { ... }}

Page 24: 第三回ありえる社内勉強会 「いわががのLombok」

@Cleanup

リソースの片付けを自動で行なう

public static void main(String[] args) throws IOException { @Cleanup InputStream in = new FileInputStream(args[0]); @Cleanup("release") MyResource resource = new MyResource(); ...}

Page 25: 第三回ありえる社内勉強会 「いわががのLombok」

@Cleanup(変換後)

リソースの片付けを自動で行なう

public static void main(String[] args) throws IOException { InputStream in = new FileInputStream(args[0]); try { MyResource resource = new MyResource(); try { ... } finally { if (resource != null) resource.release(); } } finally { if (in != null) in.close(); }}

Page 26: 第三回ありえる社内勉強会 「いわががのLombok」

@Synchronized

this以外のロックオブジェクトで排他

public class SynchronizedExample { private final Object readLock = new Object();

@Synchronized private int foo() { return 1; } @Syncrhonized("readLock") private int bar() { return 2; }}

Page 27: 第三回ありえる社内勉強会 「いわががのLombok」

@Synchronized(変換後)

this以外のロックオブジェクトで排他

public class SynchronizedExample { private final Object $lock = new Object(); private final Object readLock = new Object();

private int foo() { synchronized($lock) { return 1; } }

private int bar() { synchronized(readLock) { return 2; } }}

Page 28: 第三回ありえる社内勉強会 「いわががのLombok」

val

ローカル変数の型宣言を省略

public static void main(String[] args) { val map = new HashMap<String, List<String>>(); ... for(val entry : map.entrySet()) { ... }}

Page 29: 第三回ありえる社内勉強会 「いわががのLombok」

val(変換後)

ローカル変数の型宣言を省略

public static void main(String[] args) { final Map<String, List<String>> map = new HashMap<String, List<String>>();

...

for(final Map.Entry<String, List<String>> entry : map.entrySet()) { ... }}

Page 30: 第三回ありえる社内勉強会 「いわががのLombok」

etc

@ToString toStringの生成

@EqualsAndHashCode equalsとhachCodeの生成

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor コンストラクタの生成

@SneakyThrows チェック例外を非チェック例外のようにthrowする

@Delegate 移譲処理の生成

http://projectlombok.org/features/index.html

Page 31: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokのメリット

コードの冗長性の排除 生産性が上がる コードの見通しを良くなる バグを埋め込む可能性を減らす 心の平穏

Page 32: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokのデメリット

魔法に見える(not WYSIWYG) Lombokのバグに悩まされる可能性がある リファクタリング機能との衝突 デバッグがややこしくなる

Page 33: 第三回ありえる社内勉強会 「いわががのLombok」

ここまでのまとめ

LombokはJavaの冗長性を排除する Lombokの導入はとても簡単 魔法には代償をともなう

Page 34: 第三回ありえる社内勉強会 「いわががのLombok」

Break time

Page 35: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokの仕組み

Page 36: 第三回ありえる社内勉強会 「いわががのLombok」

ソースコード生成?バイトコード生成?

いいえ、AST変換です

JavaのASTを直接生成・変換してます ソースコード生成と違ってコードが膨れあがりません バイトコード生成と違って同じコンパイル単位のクラスか

らも可視的です

Page 37: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokが保持するAST

JavacとECJの二つのASTを別々に保持 AnnotationHandlerも各AST毎に実装する必要がある 二つのASTを統合するためのプロジェクトも進行中

https://github.com/rzwitserloot/lombok.ast

Page 38: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokの処理の流れ

Page 39: 第三回ありえる社内勉強会 「いわががのLombok」

エントリーポイント

lombok.javac.apt.Processor implements javax.annotation.processing.Processor

lombok.eclipse.TransformEclipseAST EclipseのParserにパッチを当てて実行

https://github.com/rzwitserloot/lombok.patcher OSGi ClassLoaderに注入されて実行される

Page 40: 第三回ありえる社内勉強会 「いわががのLombok」

AnnotationHandlerの読み込み

プラグイン形式の読み込み @ProviderFor(JavacAnnotationHandler.class)

used SPI(http://code.google.com/p/spi/) Service Provider Interfaceのwrapper

Page 41: 第三回ありえる社内勉強会 「いわががのLombok」

ASTの探索

ASTをトラバースしてアノテーションを探索 アノテーションが見付かったら、

対応するAnnotationHandlerのhandleを実行するAnnotationVisitor

Implements JavacASTVisitor 独自のVisitorも定義可能

@ProviderFor(JavacASTVisitor.class) HandleVal

Page 42: 第三回ありえる社内勉強会 「いわががのLombok」

ASTの変換

各AnnotationHandlerや各ASTVisitorで 変換にはJavacなどの非公開APIを直接使用

com.sun.tools.javac.tree org.eclipse.jdt.internal

Page 43: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokを拡張する

Page 44: 第三回ありえる社内勉強会 「いわががのLombok」

Lombokを拡張するには?

Lombokは外からの拡張を意識して作っているわけではない Lombok本体を模範することで拡張することは出来る

Page 45: 第三回ありえる社内勉強会 「いわががのLombok」

@Perf

メソッドの実行時間を出力

public class PerfExample { @Perf void foo() { ... }}

Page 46: 第三回ありえる社内勉強会 「いわががのLombok」

@Perf(変換後)

メソッドの実行時間を出力

public class PerfExample { void foo() { long $start = System.nanoTime(); try { … } finally { System.out.println(“PerfExample.foo = ”+ System.nanoTime() - $start)); } }}

Page 47: 第三回ありえる社内勉強会 「いわががのLombok」

プロジェクトの作り方

prototype: https://github.com/alexruiz/dw-lombok プロジェクト名などを置換 Ivyの設定を一部変更

ECJのjarが取得出来なかった Lombokの最新(0.10.4)を使いたかった

Page 48: 第三回ありえる社内勉強会 「いわががのLombok」

アノテーションの定義

トリガーとなるPerfアノテーションを作成する

@Target({ElementType.METHOD})@Retention(RetentionPolicy.SOURCE)public @interface Perf {}

Page 49: 第三回ありえる社内勉強会 「いわががのLombok」

AnnotationHandlerの作成

Javac用とECJ用の二つのAnnotationHandlerを作成する

// for javacpackage localhost.javac.handlers;

@ProviderFor(JavacAnnotationHandler.class)public class HandlePerf extends JavacAnnotationHandler<Perf> { @Override public void handle(AnnotationValues<Perf> annotation, JCAnnotation ast, JavacNode annotationNode) { ... }}

Page 50: 第三回ありえる社内勉強会 「いわががのLombok」

AST変換処理の実装

愚直にASTを作るだけの簡単なお仕事

TreeMaker maker = methodNode.getTreeMaker();

// long $start = long t1 = System.nanoTime();Name startName = methodNode.toName("$start");JCExpression nanoTimeMethod = chainDotsString(methodNode, "System.nanoTime");JCExpression nanoTimeApply = maker.Apply(List.<JCExpression>nil(), nanoTimeMethod, List.<JCExpression>nil());

JCVariableDecl startDef = setGeneratedBy(maker.VarDef(maker.Modifiers(0), startName, maker.TypeIdent(getCtcInt(TypeTags.class, "LONG")), nanoTimeApply), ast);

Page 51: 第三回ありえる社内勉強会 「いわががのLombok」

テスト

本体が用意しているテスト・インフラがそのまま使える

@RunWith(DirectoryRunner.class)public class TestWithEcj implements TestParams { @Override public Compiler getCompiler() { return ECJ; } @Override public boolean printErrors() { return true; }

@Override public File getBeforeDirectory() { return new File("test/transform/resource/before"); } @Override public File getAfterDirectory() { return new File("test/transform/resource/after-ecj"); } ...}

Page 52: 第三回ありえる社内勉強会 「いわががのLombok」

拡張されたLombokの実行

jar化してClasspathに追加すれば良い$ ant dist$ javac -cp “.:lib/build/lombok.jar:dist/lombok-perf.jar”\Example.java$ java -cp “.”Example

Page 53: 第三回ありえる社内勉強会 「いわががのLombok」

感想

変換処理はわりと愚直にAST作るだけの簡単なお仕事 本体のコードこそが最高のサンプル

https://github.com/rzwitserloot/lombok/tree/master/src/core/lombok/javac/handlers https://github.com/rzwitserloot/lombok/tree/master/src/core/lombok/eclipse/handlers

Page 54: 第三回ありえる社内勉強会 「いわががのLombok」

まとめ

Lombokは皆さんのJava嫌いをちょっとだけ癒してくれます

きっとScalaプログラマにもなれないJavaプログラマの皆さん

Lombokを手にいれてみませんか?

Page 55: 第三回ありえる社内勉強会 「いわががのLombok」