😊

【初心者向け】Javaアノテーション解説

に公開

はじめに

はじめまして、株式会社BTMの弓削多と申します。
最近現場が変わりまして、「Java + Spring Boot」の環境で開発しております。
今まではPHPを用いての開発を担当していましたので、環境がガラッと変わりました。
毎回アプリケーションをビルドしなくてはいけなかったり、型をしっかりと指定しなくてはいけなかったりと、PHPとは違うことばかりで戸惑っていました。
中でも一番混乱したのが、Javaのアノテーションという概念です。
PHPには同じような概念で「アトリビュート」という機能があるそうですが、私は馴染みがなかったためソースコードの理解が難しかったです。
ソースコードの理解ができないとエンジニアとしては仕事にならないわけなので、めっっっっちゃ困るわけです。
今回はそんな初心者殺しのアノテーションについて解説していきます。

アノテーションとは

まず最初にアノテーションについてざっくり概要を解説していきます。
※本記事では「@~」とするとユーザーへメンションされるため意図的に全角「@」を使用しています。

アノテーションの基本概念

アノテーションとは日本語訳をすると「注釈」という意味で、実際にJavaでもプログラムに意味や設定を付け足すような使われ方をします。
アノテーションはクラスの上部に「@」から始まる一単語を記述します。
使用例を見ていきましょう。
ここではよく使用される「@override」を例に挙げます。

class Parent {
    void greet() {}
}

class Child extends Parent {
    @Override
    void greet() {}
}

「@override」についての詳しい説明は割愛しますが、上記のように簡単にアノテーションを付与することができます。

なぜアノテーションが便利なのか?

アノテーションを使用すると開発効率が大幅に向上します。
開発効率が向上する理由は大きく分けて3つの理由があります。

1. 設定や処理を簡潔に記述できる

アノテーションが導入されたのは2005年にリリースされたJava5[1]からです。

アノテーションが導入される前は代わりにXMLファイルに様々な処理を記述していました。
// xmlの例 <bean id="myService" class="com.example.MyService"/>
アノテーションが利用できるようになったことで以下の処理を省略して記述することができるようになりました。

  • Bean の登録
  • 依存関係の注入
  • ライフサイクル管理
  • AOPやトランザクションなどの横断処理の適用
  • etc

これらの処理を毎回書くのは面倒ですし、コード量が増えることで分かりづらくなります。
一方でアノテーションにはエラー時に発生するスタックトレースの文量が増えることでエラーの発生場所が分かりづらくなるというデメリットも存在します。

// 最上部がエラー内容
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:520)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:744)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:651)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:386)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    //最下部がエラーの発生場所
    at com.example.UserService.createUser(UserService.java:15)

本筋から逸れるので詳しくは説明しませんが、文量が増える理由はアノテーションの処理が複数のメソッドを跨って実行されるからです。
エラーを読み解くポイントはスタックトレースの最上部にあるエラー内容と最下部にあるエラーの発生場所を確認すると良いです。

アノテーションがあることで設定や処理が簡潔になることが理解いただけたと思います。

2. コンパイル時の安全性向上

Javaは実行時にコンパイルする必要があります。
そのコンパイル時にもアノテーションは活躍します。
例えば、先ほど紹介しました「@Override」を使用すると、正しくオーバーライドしているかを判定してくれます。
仮にオーバーライド先のメソッドが存在しない場合や指定方法が誤っている場合はエラーとなりコンパイルに失敗します。
アノテーションを付けずに誤った記述をした場合は「コンパイルは問題なく完了しているが、なぜか動かない」となり、原因を探る工数が発生します。
このように、コンパイル時に現在のコードに問題がないことを保証する意味でもアノテーションは必要となります。

3. コードの意図が明確になる

アノテーションを付けることで作成者以外の人が理解しやすくなります。
実例を見ていきましょう。
以下は「@Service」アノテーションの例です。

// アノテーションなし
public class PaymentProcessor {
    public void processPayment() {
        // 支払い処理のロジック
    }
}
// アノテーションあり
@Service
public class PaymentProcessor {
    public void processPayment() {
        // 支払い処理のロジック
    }
}

「@Service」アノテーションが付与されていることで、この記述がビジネスロジックであることが一目で理解できます。
アノテーションが付与されていることで後から参画したメンバーへの可読性が上がっているのです。

アノテーションを理解するための3つの要素

定義方法

アノテーションは「@interface」を用いて定義します。
一般的に使用されているビルトインのアノテーションも「@interface」を用いて定義されています。
Springの開発で良く用いられる「@Autowired」というアノテーションも以下のように定義されています。
IntelliJではプロジェクトにJDKが設定されていればCtrl + Clickで定義元にジャンプできます。
VSCodeなら「Extension Pack for Java[2]」のインストールをしていれば定義元にジャンプできます。

public @interface Autowired {
    boolean required() default true;
}

アノテーションの意味が分からなければ「@interface」の記述がある部分を探ると良いでしょう。

保持期間

アノテーションは、いつまで効力を発揮するのかがモノによって異なります。
「いつまで」というのはJavaのライフサイクルです。
詳しくは割愛しますが、Javaは「1.ソースコード」をコンパイルして「2.クラスファイル」を作成し、クラスファイルをロードし「3.Javaランタイム環境」を作成します。
大まかに上記のような流れですが、アノテーションは1,2,3のどれかのタイミングまで効力を発揮します。
以下はAutowiredの例ですが、「@Retention」というアノテーションに「RetentionPolicy.RUNTIME」と記述があるので、Autowiredはランタイム環境まで実行されるということです。

// Autowiredの例
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    boolean required() default true;
}

「RUNTIME」の他にも
「SOURCE」→ ソースコードだけに適用され、コンパイル時に破棄される
「CLASS」→ クラスファイル(.class)には残るが、実行時には読み込まれない
の計3種類が存在します。

付与対象

アノテーションは何に付与できるかが決められています。
Class用のアノテーションを関数に付与するとエラーとなります。
「@Target(ElementType.{型の名前})」で指定することができます。

// @Serviceの例
@Target(ElementType.TYPE)
public @interface Service {
    @AliasFor(annotation = Component.class)
    String value() default "";
}

その他の型についてはJava側でEnumとして提供されていますので、こちらを参照してください

代表的なアノテーション

Javaのアノテーション

アノテーション 用途 説明
@Override メソッドのオーバーライド明示 親クラスやインターフェースのメソッドを正しくオーバーライドしていることを示す
@Deprecated 非推奨APIの明示 そのメソッド/クラスの利用を推奨しないことを示す
@SuppressWarnings 警告の抑制 コンパイラの警告を抑制する
@Entity JPAエンティティ データベーステーブルに対応するクラスを定義
@Transactional トランザクション管理 メソッド/クラス単位でトランザクションを開始・終了管理

SpringBootのアノテーション

アノテーション 用途 説明
@SpringBootApplication アプリケーション起動の簡略化 @Configuration + @EnableAutoConfiguration + @ComponentScan をまとめたもの
@Configuration 設定クラス定義 Javaベースの設定を行うクラス
@EnableAutoConfiguration 自動設定有効化 Spring Bootが自動構成を有効にする
@ComponentScan コンポーネント検出 指定パッケージ以下のコンポーネントを自動登録
@Autowired 依存関係の自動注入 SpringコンテナがBeanを自動注入
@Qualifier Beanの識別 同じ型のBeanが複数ある場合にどれを注入するか指定
@Value プロパティ値の注入 application.propertiesapplication.yml から値を取得
@RestController REST APIコントローラ @Controller + @ResponseBody
@RequestMapping エンドポイント定義 URLやHTTPメソッド(GET, POSTなど)とメソッドを紐付け
@RequestParam リクエストパラメータ取得 クエリパラメータを取得
@PathVariable パス変数取得 URL中の /users/{id} のような変数を取得
@RequestBody リクエストボディ取得 JSON/XMLなどをオブジェクトに変換
@Repository DAO層定義 データアクセス層を示し、例外変換を有効化

おわりに

今回はアノテーションについて解説しました。
Javaは便利であるが故に、上級者には感動ものですが初学者には分かりづらい点もあります。
私が初学者の時につまづいたポイントのうちの一つがアノテーションでした。

「ログの関数ってどこに書いてあるの?」
「このリクエストはどこで受け取っているの?」

など、アノテーションを理解しないと「?」が無数に浮かんでしまいます。
読者の「?」を1つ解消できたなら幸いです。

脚注
  1. Java5のリリースノート ↩︎

  2. Extension Pack for Java ↩︎

Discussion