Java
spring
spring-boot

Springのアノテーションを合成してプロジェクト固有のステレオタイプを作る

More than 1 year has passed since last update.

通常、Java ではアノテーションを継承したり合成したりできませんが、Spring のアノテーションはできます。ややこしい準備も必要なく、アノテーションを書くだけで実現できます。

基本

アノテーションを定義し、その型に合成したいアノテーションを付します。元のアノテーションのパラメータについては、定数であれば通常通りそのパラメータに指定すれば良いですし、また @AliasFor を使って新しいアノテーションのパラメータを合成したアノテーションに引き渡すこともできます。

@Retention(RetentionPolicy.RUNTIME) // 実行時にアノテーションを参照できるようにする
@Validated
@ModelAttribute(binding = true)
public @interface ValidModel {
  @AliasFor(annotation = ModelAttribute.class,  attribute = "name")
  String value() default "";
}

@SpringBootApplication@GetMapping 等そこらかしこで合成アノテーションが使われているので、そのソースを追えば使い方はつかめるかと思います。

チェック例外発生時にトランザクションをロールバックさせる

@Transactional はデフォルトだとチェック例外が送出されてもコミットされるという恐ろしい動作になっていますが、独自アノテーションを定義することで毎回 rollbackFor を付けなくて済むようになります。

@Retention(RetentionPolicy.RUNTIME)
@Transactional(rollbackFor = Exception.class)
public @interface OurTransactional {
  @AliasFor(annotation = Transactional.class, attribute = "readOnly")
  boolean readOnly() default false;
}

DAO や Repository がトランザクションの外で呼ばれたら例外を投げる

DAO をトランザクション境界にすることはないと思いますが、トランザクションの内側かどうかのチェックも下記のようにアノテーションでできます。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Component
@Transactional(propagation = Propagation.MANDATORY)
public @interface OurDao {
  @AliasFor(annotation = Transactional.class, attribute = "readOnly")
  boolean readOnly() default false;
}

Controller をトランザクション境界にする

Play 1 系のように Controller でトランザクションを切るようにするアノテーションはこんな感じ。

@Retention(RetentionPolicy.RUNTIME)
@Controller
@RequestMapping
@Transactional
public @interface OurController {
  @AliasFor(annotation = RequestMapping.class, attribute = "path")
  String value() default "";

  @AliasFor(annotation = Transactional.class, attribute = "readOnly")
  boolean readOnly() default false;
}