blog

春のAOP +アノテーション+リフレクションを使用して、パラメータのチェックと変更のエレガントな実装

背景: 直接的な解決策: $$$color{red}{しかし、すべてのクエリ時間範囲クエリメソッドは、このようなコードの一部を追加する必要があり、それはすっきりさに影響を与えます}$$color{re...

Jul 30, 2020 · 5 min. read
シェア

背景

フロントエンドには、2つのタイムスタンプ(beginDate,endDate)を時間範囲として渡すtimeコンポーネントがあります 。endDateに1日追加する必要があります}}。

直接の解決策:

Date beginDate=param.getBeginDate();
Date endDate=param.getEndDate();
if ((beginDate != null && endDate != null) && beginDate.equals(endDate)) {
 //開始時刻と終了時刻が同じなら、終了時刻は1日増える。
 c.setTime(beginDate);
 c.add(Calendar.DAY_OF_MONTH, 1);
 Date endTime = c.getTime();
 endDateMap.get(arg.getClass()).set(arg, endTime);
 }

しかし、そうすると、すべての時間範囲クエリーメソッドにこのコードを追加しなければならなくなり、すっきりしません。

したがって、メソッド本体に入り込まないアノテーションを使用して、このエントリーの修正を実装することを検討してください。

カスタム注釈+AOP

まず、そのプロジェクトが

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

次に、時間範囲クエリーメソッドをラベリングするためのカスタムアノテーション。

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeSpanCheck {
 //String beginDateFieldName();
 //String endDateFieldName();
}

もともとは、beginDateとendDateのフィールド名を手動で定義したかったのですが、よくよく考えてみると、ここでフィールド名を変更すると、エラーと一緒に変更される可能性がないので、すでにアノテーションを使って識別を行っているので、もう一度使った方がいいかもしれません!

@Documented
//メソッド@Target上記とは異なり、このアノテーションは変数のアノテーションに使用される。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BeginDate {
}
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EndDate {
}

アノテーションを定義したら、対応するメソッドとフィールドに追加します。

@Component
@Aspect
@Slf4j
public class TimeSpanCheckAop {
 private Calendar c = Calendar.getInstance();
 //フィールドをキャッシュすることで、メソッド呼び出しのたびにリフレクションを実行するオーバーヘッドを避けることができる。
 private Map<Class, Field> beginDateMap = new HashMap<>();
 private Map<Class, Field> endDateMap = new HashMap<>();
 //カット・ポイント、つまりaopを実行する必要がある場所である。
 @Pointcut("execution(* xxx.xxx.service.impl..*(..)) && @annotation(xxx.xxx.service.aop.TimeSpanCheck)")
 public void pointCut() {
 }
 /**
 * メソッドが@TimeSpanCheckbegindateとenddateは同じで、enddateプラス1日である。
 */
 @Around("pointCut()")
 public Object TimeSpanCheck(ProceedingJoinPoint joinPoint) throws Throwable {
 Object[] args = joinPoint.getArgs();
 Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
 TimeSpanCheck timeSpanCheck = method.getAnnotation(TimeSpanCheck.class);
 if (timeSpanCheck == null) {
 return true;
 }
 Object arg = args[0];
 if (beginDateMap.get(arg.getClass()) == null && endDateMap.get(arg.getClass()) == null) {
 //入力変数を繰り返し、ラベルの付いた@BeginDate @EndDateフィールドを作成し、キャッシュ・マップに格納する。
 for (Field field : arg.getClass().getDeclaredFields()) {
 BeginDate beginDate = field.getAnnotation(BeginDate.class);
 EndDate endDate = field.getAnnotation(EndDate.class);
 if (beginDate != null) {
 field.setAccessible(true);
 beginDateMap.put(arg.getClass(), field);
 }
 if (endDate != null) {
 field.setAccessible(true);
 endDateMap.put(arg.getClass(), field);
 }
 }
 }
 //フィールドを取得し、入力パラメーターを変更する
 Field beginDateField = beginDateMap.get(arg.getClass());
 Field endDateField = endDateMap.get(arg.getClass());
 if (beginDateField != null && endDateField != null) {
 Date beginDate = (Date) beginDateField.get(arg);
 Date endDate = (Date) endDateField.get(arg);
 if ((beginDate != null && endDate != null) && beginDate.equals(endDate)) {
 //開始時刻と終了時刻が同じなら、終了時刻は1日増える。
 c.setTime(beginDate);
 c.add(Calendar.DAY_OF_MONTH, 1);
 Date endTime = c.getTime();
 endDateMap.get(arg.getClass()).set(arg, endTime);
 }
 }
 //オリジナルメソッドの実行
 return joinPoint.proceed(args);
 }
}

検証

ご覧のように、このメソッドを実行するとendDateはbeginDate+1日となり、望ましい効果が得られます。

Read next

springboot自動アセンブリ用のカスタムスターター

Spring Bootの自動設定は、その名が示すように、自動設定は、設定の海から解放されることを願っています。そして、自動設定以来、それは3つの問題を解決する必要があります:例えば:プロジェクトを作成するときに、依存関係spring-boot-starter-webの導入は、自動的にポート8080を作成します...

Jul 30, 2020 · 4 min read