注釈付き

公開日: 2026-07-29 10:56 更新日: 2026-07-29 10:56 1937文字 10 min read ... ページビュー

本稿では、Javaアノテーションの基本概念、メタアノテーション、カスタムアノテーションの定義と使用、パーミッションチェックサムデータベースマッピングへのアノテーションの実用的な応用について紹介する。リフレクションメカニズムにより、アノテーションは解析されてビジネスロジックを実装することができますが、本質的にはメタデータであり、実際の機能はフレームワークやプログラムによって実装されます。注釈はランタイム保持ポリシー(RUNTIME)とセキュリティ検証との連携が必要であり、また実際のプロジェクトでORM機能の重複実装を避けるべきであることを強調し、成熟したフレームワークの使用を推奨した。

Java Annotation 注釈

注记の概要

アノテーションはJava 5で導入されたメタデータメカニズムです。クラス、メソッド、フィールド、コンストラクタ、パラメータなどのプログラム要素に説明や設定情報を追加できます。

アノテーション自体は通常、ビジネスロジックを直接実行するのではなく、コンパイラ、JVM、リフレクションコード、フレームワークによって読み込まれ処理されます。一般的な注記には、@Override@Deprecated@SuppressWarningsがある。

注釈の性質

カスタム注記では、@interface宣言が使用されます。コンパイルされると、アノテーション型は本質的にjava.lang.annotation.Annotationインタフェースを継承した特別なインタフェースになります。

public @interface MyAnnotation {
    String value() default "";
}

注釈内のメンバー宣言は、パラメータなしメソッドに似ています。メンバ型は、基本型、StringClass、列挙、その他の注釈、およびこれらの型の1 次元配列のみを使用できます。

メタアノテーション

メタ注記は、注記のタイプを修飾する注記です。

@Retention

@Retentionは、注記の保持フェーズを指定するために使用されます。

説明
RetentionPolicy.SOURCEソースコードにのみ存在し、コンパイル後に破棄される
RetentionPolicy.CLASSバイトコードファイルを書き込むが、通常は実行時にリフレクションでは読み取れない
RetentionPolicy.RUNTIME実行時に予約され、リフレクションで読み取ることができる

プログラム実行中に解析する必要がある注釈は、RetentionPolicy.RUNTIMEを使用する必要があります。

@Target

@Targetは、注記が修飾できる位置を制限するために使用される。

適用場所
ElementType.TYPEクラス、インタフェース、列挙型、またはコメント型
ElementType.METHOD方法論は
ElementType.FIELDフィールドフィールド
ElementType.PARAMETERメソッドパラメータ
ElementType.CONSTRUCTORコンストラクター。
ElementType. ANNOTATION_TYPE注記の種類

その他の一般的なメタ注釈

  • @Documented APIドキュメントの生成時に注釈を含めます。
  • @Inherited:クラスレベルのアノテーションをサブクラスに継承できますが、メソッドやフィールドには影響しません。
  • @Repeatable:同じ場所で同じ種類のアノテーションを繰り返し使用できます。

カスタムコールアウトの定義と使用

注记の

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
    String value() default "";

    int age() default 18;

    String[] tags() default {};
}

注记の使用

@MyAnnotation(
        value = "用户类",
        age = 20,
        tags = {"测试", "业务"}
)
public class User {
    @MyAnnotation("打印方法")
    public void print() {
        System.out.println("执行 print() 方法");
    }
}

valueという名前のメンバーが1つだけ割り当てられる場合、メンバー名を省略できます。

@MyAnnotation("用户类")
public class User {
}

リフレクションによるコールアウトの解析

ランタイムコールアウトはリフレクションによって読み取ることができます。一般的な方法としては、isAnnotationPresent()getAnnotation()getDeclaredAnnotations()がある。

import java.lang.reflect.Method;
import java.util.Arrays;

public class AnnotationTest {
    public static void main(String[] args) throws NoSuchMethodException {
        Class<User> userClass = User.class;

        if (userClass.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation classAnnotation = userClass.getAnnotation(MyAnnotation.class);

            System.out.println("value:" + classAnnotation.value());
            System.out.println("age:" + classAnnotation.age());
            System.out.println("tags:" + Arrays.toString(classAnnotation.tags()));
        }

        Method printMethod = userClass.getDeclaredMethod("print");
        MyAnnotation methodAnnotation = printMethod.getAnnotation(MyAnnotation.class);

        if (methodAnnotation != null) {
            System.out.println("方法注解:" + methodAnnotation.value());
        }
    }
}

注釈はメタデータのみを保持します。実際の機能はコンパイラ、リフレクション、フレームワークによって実装される必要があります。

カスタム権限検証

アクセス権注記の定義

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequirePermission {
    String value();
}

ビジネスメソッドでのコールアウトの使用

public class OrderService {
    @RequirePermission("order:delete")
    public void deleteOrder(Long id) {
        System.out.println("删除订单:" + id);
    }
}

コールアウトの解析とメソッドの実行

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class PermissionInterceptor {
    private static final String CURRENT_PERMISSION = "order:delete";

    public static void invokeMethod(
            Object target,
            Method method,
            Object... arguments
    ) throws InvocationTargetException, IllegalAccessException {
        RequirePermission annotation = method.getAnnotation(RequirePermission.class);

        if (annotation != null) {
            String requiredPermission = annotation.value();

            if (!requiredPermission.equals(CURRENT_PERMISSION)) {
                throw new SecurityException(
                        "权限不足,需要权限:" + requiredPermission
                );
            }
        }

        method.invoke(target, arguments);
    }

    public static void main(String[] args) throws ReflectiveOperationException {
        OrderService orderService = new OrderService();
        Method method = OrderService.class.getMethod("deleteOrder", Long.class);

        invokeMethod(orderService, method, 10L);
    }
}

この例では、アノテーション+リフレクションの基本的な考え方を示しています。実際のプロジェクトでのユーザID、ロール、パーミッションセット、例外処理は、通常、セキュリティフレームワークによって管理されます。

コールアウトを使用したデータベーステーブルのマッピング

全体的な考え方

クラス·レベルの注釈を使用してテーブル名を記録し、フィールド·レベルの注釈を使用してカラム名を記録し、リフレクションを使用してSQLを生成し、オブジェクト·マッピングを完了できます。

テーブル名の注釈

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

フィールドの注記

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String value();

    boolean primaryKey() default false;
}

エンティティークラスバインド注釈

@Table("book")
public class MyBook {
    @Column(value = "id", primaryKey = true)
    private Integer bookId;

    @Column("isbn")
    private Integer isbn;

    @Column("name")
    private String name;

    @Column("price")
    private Integer price;

    public MyBook() {
    }

    public MyBook(Integer bookId, Integer isbn, String name, Integer price) {
        this.bookId = bookId;
        this.isbn = isbn;
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "MyBook{" +
                "bookId=" + bookId +
                ", isbn=" + isbn +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

JDBC 接続ツール

接続オブジェクトは、グローバルシングルトンとして長期再利用しないでください。各データベース操作は接続を取得し、操作終了後にリソースをシャットダウンするため、同時使用や異常復旧が容易になります。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public final class DBUtil {
    private static final String URL =
            "jdbc:mysql://localhost:3306/java2506?serverTimezone=UTC&characterEncoding=utf8";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "1234";

    private DBUtil() {
    }

    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USERNAME, PASSWORD);
    }
}

注釈解析と一般的なJDBCツール

新規オブジェクトの追加

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class AnnotationJdbcUtil {
    public static int insert(Object entity) {
        Class<?> entityClass = entity.getClass();
        Table table = entityClass.getAnnotation(Table.class);

        if (table == null) {
            throw new IllegalArgumentException("实体类缺少 @Table 注解");
        }

        List<String> columns = new ArrayList<>();
        List<Object> values = new ArrayList<>();

        for (Field field : entityClass.getDeclaredFields()) {
            Column column = field.getAnnotation(Column.class);

            if (column == null) {
                continue;
            }

            field.setAccessible(true);
            columns.add(column.value());

            try {
                values.add(field.get(entity));
            } catch (IllegalAccessException e) {
                throw new RuntimeException("读取字段失败:" + field.getName(), e);
            }
        }

        String placeholders = String.join(",", java.util.Collections.nCopies(
                columns.size(),
                "?"
        ));

        String sql = "insert into " + table.value() +
                "(" + String.join(",", columns) + ") values(" +
                placeholders + ")";

        try (Connection connection = DBUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)) {

            for (int index = 0; index < values.size(); index++) {
                statement.setObject(index + 1, values.get(index));
            }

            return statement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException("执行新增操作失败", e);
        }
    }
}

プライマリ·キーに基づくオブジェクトのクエリー

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class AnnotationJdbcQueryUtil {
    public static <T> T getById(Class<T> entityClass, Object id) {
        Table table = entityClass.getAnnotation(Table.class);

        if (table == null) {
            throw new IllegalArgumentException("实体类缺少 @Table 注解");
        }

        List<Field> mappedFields = new ArrayList<>();
        List<String> columnNames = new ArrayList<>();
        String primaryKeyColumn = null;

        for (Field field : entityClass.getDeclaredFields()) {
            Column column = field.getAnnotation(Column.class);

            if (column == null) {
                continue;
            }

            mappedFields.add(field);
            columnNames.add(column.value());

            if (column.primaryKey()) {
                primaryKeyColumn = column.value();
            }
        }

        if (primaryKeyColumn == null) {
            throw new IllegalArgumentException("实体类未标记主键字段");
        }

        String sql = "select " + String.join(",", columnNames) +
                " from " + table.value() +
                " where " + primaryKeyColumn + " = ?";

        try (Connection connection = DBUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)) {

            statement.setObject(1, id);

            try (ResultSet resultSet = statement.executeQuery()) {
                if (!resultSet.next()) {
                    return null;
                }

                T entity = entityClass.getDeclaredConstructor().newInstance();

                for (Field field : mappedFields) {
                    Column column = field.getAnnotation(Column.class);
                    field.setAccessible(true);
                    field.set(entity, resultSet.getObject(column.value()));
                }

                return entity;
            }
        } catch (SQLException | ReflectiveOperationException e) {
            throw new RuntimeException("查询对象失败", e);
        }
    }
}

テストコールのテスト

public class JdbcAnnotationTest {
    public static void main(String[] args) {
        MyBook book = new MyBook(
                6,
                1006,
                "Java 反射与注解程序设计",
                66
        );

        int affectedRows = AnnotationJdbcUtil.insert(book);
        System.out.println("受影响行数:" + affectedRows);

        MyBook result = AnnotationJdbcQueryUtil.getById(MyBook.class, 6);
        System.out.println("查询结果:" + result);
    }
}

使用上の注意事項

  • アノテーションは実際の機能を生成するためにパーサと連携する必要がある。
  • 実行時にリフレクションによって読み取られる注記は、RetentionPolicy.RUNTIMEを使用する必要があります。
  • テーブル名とカラム名は、PreparedStatementのプレースホルダを介して渡すことはできません。したがって、マッピングされた値は信頼できる注釈から取得する必要があり、ユーザー入力を直接使用することはできません。
  • 汎用マッピングツールは、型変換、null 値、継承フィールド、自己インクリメントプライマリキー、トランザクションなどの問題も扱う必要があります。
  • 本番プロジェクトは通常、成熟した持続性層フレームワークを使用し、ORM 機能の完全な再実装は推奨されません。

気に入ったならばコメントを残してくださいね~

... ページビュー
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka