Skip to content

Instantly share code, notes, and snippets.

@asufana
Last active February 21, 2016 00:48
Show Gist options
  • Save asufana/a87376f9b6a4b01c1bed to your computer and use it in GitHub Desktop.
Save asufana/a87376f9b6a4b01c1bed to your computer and use it in GitHub Desktop.
AnnotationProcessing入門

Annotation Processing 入門

APT、APTってよく言うけど、いまだにAPTって言葉使ってるの日本人だけっぽいよ。

ということで Annotation Processing 、あるいは JSR269 と呼ぼう。。

Annotation Processing とは

  • ソースのコンパイル前に何かを処理する機構

出来ること

リフレクションコードの置き換え
  • 実行時の動的処理から、実行前の静的処理に
//下記クラスの name フィールドを取得する場合、
public class Hoge extends Entity {
	private String name;
}

//リフレクションでは、フィールド名を文字列で指定
Field field = Hoge.class.getField("name")

//APでは、専用メソッドを持つクラスを用意できる(HogeMetaクラスをAPで生成)
Field field = HogeMeta.getNameField();
ボイラープレートなクラスの自動生成
  • EntityをJson化する際のDTOクラスの自動生成
Hoge hoge = new Hoge();

//エンティティをDTO変換するクラスを生成(HogeDto)
HogeDto dto = HogeDto.create(hoge);
  • Application.confのアクセサクラスとか
String value = Play.configuration.getProperty("adminUserName");

//ではなく、以下のように取得できるクラスを生成(PlayConf)
String value = PlayConf.getAdminUserName();

出来ないこと

既存のコードを変更することはできない
  • 既存クラスにアクセサを追加するなど、コード変更はできない
  • コード変更であれば JavassistcglibByteBuddy などのバイトコード変更や Lombok を利用する必要がある

開発手順

準備

  • Maven インストール
$ brew install homebrew/versions/maven32
  • Java8 maven archetype インストール
$ git clone https://github.com/asufana/my-maven-archetype
$ cd my-maven-archetype
$ mvn install

Mavenプロジェクト作成

  • プロジェクト雛形作成
$ mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=com.github.asufana -DarchetypeArtifactId=my-maven-archetype -DgroupId=com.github.YOURNAME -DartifactId=my-annotation-processor
  • Eclipse 設定ファイル生成
$ cd my-annotation-processor
$ mvn eclipse:eclipse

Eclipse 上で Import > Maven - Existing Maven Projects にて、プロジェクトインポート

pom.xml 修正

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.github.YOURNAME</groupId>
	<artifactId>my-annotation-processor</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>jar</packaging>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
					<!-- disable annotation processing -->
					<compilerArgument>-proc:none</compilerArgument>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-assembly-plugin</artifactId>
				<configuration>
					<descriptorRefs>
						<descriptorRef>jar-with-dependencies</descriptorRef>
					</descriptorRefs>
				</configuration>
				<executions>
					<execution>
						<id>make-assembly</id>
						<phase>package</phase>
						<goals>
							<goal>single</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
	
	<dependencies>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.4</version>
		</dependency>
		<dependency>
			<groupId>com.squareup</groupId>
			<artifactId>javapoet</artifactId>
			<version>1.5.1</version>
		</dependency>
	</dependencies>
</project>

mvn eclipse:eclipse にて依存ライブラリの取得と Eclipse 定義の更新をしておく。

Annotation Processor 処理クラスの実装

src/main/java に com.github.YOURNAME.MyAnnotaionProcessor.java を追加

package com.github.YOURNAME;

import java.io.*;
import java.util.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.tools.Diagnostic.Kind;
import javax.tools.*;
import com.squareup.javapoet.*;

@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {

  private void log(final String msg) {
    final Messager messager = processingEnv.getMessager();
    messager.printMessage(Kind.NOTE, msg);
  }

  @Override
  public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
    if (annotations.size() == 0) {
      return true;
    }
    log("PlayConfAnnotationProcessor");

    /** クラス定義 */
    final TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                      .build();
    final JavaFile javaFile = JavaFile.builder("generated", helloWorld).build();

    /** クラスファイル生成 */
    writeClass("generated/HelloWorld", javaFile);

    return true;
  }

  private void writeClass(final String fqcn, final JavaFile javaFile) {
    try {
      final JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(fqcn);
      try (final Writer writer = sourceFile.openWriter()) {
        javaFile.writeTo(writer);
      }
    }
    catch (final Exception e) {
      log(e.getMessage());
      throw new RuntimeException(e);
    }
  }
}

ServiceLoader を設定

  • src/main/resources ソースフォルダを作成
  • resources 配下に META-INF/services フォルダを追加
  • services 配下に javax.annotation.processing.Processor ファイル作成
  • 上記ファイルに com.github.YOURNAME.MyAnnotationProcessor を記述

Jar 生成

$ mvn clean package

target フォルダに my-annotation-processor-1.0-SNAPSHOT-jar-with-dependencies.jar が生成されていることを確認

Play1 で利用してみる

Play プロジェクト作成

$ cd my-annotation-processor
$ mkdir sample
$ cd sample
$ play new my-annotation-processor-test

Annotation Processor Jar コピー

$ cd my-annotation-processor-test
$ cp ../../target/my-annotation-processor-1.0-SNAPSHOT-jar-with-dependencies.jar my-annotation-processor-test/lib
$ cd my-annotation-processor-test
$ play ec

Eclipse 設定

上で作成したプロジェクトを Eclipse にインポートし、プロジェクト設定を行う

  • Java Compiler > Annotation Processing から Enable Annotatoin Processing を有効化
  • Generated Source directory を app に変更

  • Java Compiler > Annotation Processing > Factory Path から Jar ファイルを追加

  • app/generated に HelloWorld クラスが生成されることを確認

参考


その他おぼえがき

  • APライブラリに必要なライブラリを含める、jarファイル名にjar-with-dependenciesを付加しない
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-assembly-plugin</artifactId>
	<configuration>
		<descriptorRefs>
			<descriptorRef>jar-with-dependencies</descriptorRef>
		</descriptorRefs>
		<appendAssemblyId>false</appendAssemblyId>
	</configuration>
	<executions>
		<execution>
			<id>make-assembly</id>
			<phase>package</phase>
			<goals>
				<goal>single</goal>
			</goals>
		</execution>
	</executions>
</plugin>
  • Playで推移的にライブラリを取得しないようにする
- com.github.asufana -> play1-conf-accessor [1.0,):
    transitive: false
  • AnnotationProcessing自体のコードで、lombokほかAnnotationProcessingを利用するライブラリを使わない方がよい、ServiceLoaderが上書きされて動作しない場合がある
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment