経緯
機能フラグの実装を考えた際に、AOPで手軽にやりたいと思い、アノテーションを目印にいろいろやった際にハマったことを書く
環境
- Windows11 pro
- Java17
- Spring 3.2.4
悩んだ点
- aseptを正しく書いているのに、全然処理が動かない
- 起動時に変なエラーが出てくる。しかもAOPとは関係なさそうなエラー
詳細
正解の実装
gradleに、依存関係を追加
implementation 'org.springframework.boot:spring-boot-starter-aop'
チェック用に作ったアノテーション↓
チェックに必要な情報を受け取るようにvalueを持つようにしてある。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface FunctionCtrlAspect { String value(); }
チェック処理の実装↓
FunctionCtrlAspectのアノテーションが付与されたクラスで、publicのメソッドが呼ばれた時にvalidateFunctionCtrlを実行するように対応している。
ロジック内容は、チェック処理なので、説明は省略する。
@Aspect @Component public class FunctionCtrlValidator { @Autowired private FunctionCtrlRepository functionCtrlRepository; @Before("@within(functionCtrlAspect) && execution(public * *.*(..))") public void validateFunctionCtrl(JoinPoint joinPoint, FunctionCtrlAspect functionCtrlAspect) { String flg = functionCtrlRepository.getFlg(functionCtrlAspect.value()); if (FunctionCtrlRepository.FunctionCtrlFlg.OFF.getCode().equals(flg)) { throw new FunctionCtrlException(); } } }
利用箇所の定義内容↓
@RequestMapping("/circulation") @Controller @FunctionCtrlAspect("circulation") public class CirculationController { // 内部の処理省略 }
悩んでた箇所の詳細
aseptを正しく書いているのに、全然処理が動かない
最初は、下記の実装で動かしてた。
@Before("@annotation(functionCtrlAspect) && execution(public * *(..))") public void validateFunctionCtrl(JoinPoint joinPoint, FunctionCtrlAspect functionCtrlAspect) { String flg = functionCtrlRepository.getFlg(functionCtrlAspect.value()); if (FunctionCtrlRepository.FunctionCtrlFlg.OFF.getCode().equals(flg)) { throw new FunctionCtrlException(); } }
なぜか処理が通らなくて、すごいイライラした。。。
理由としては、@annotationが、メソッドに対してチェックしているため。
利用想定が、クラスに付与させる想定だったので、@annotationの条件に当てはまらなかった。
なので、@withinを利用するしてクラスのアノテーションをチェックするように変更して突破できた。
調査方法として、どこかの条件で紐づけが出来てないと思ったので、条件を変えて確認してた。
具体的にやった方法は、以下。
- 全メソッドに付与させるように条件を設定して、処理が通るか確認
- publicメソッドに付与させるように条件を修正して、処理が通るか確認
- アノテーションの条件を付与して、処理が通るか確認
広い適用条件から、段々と狭めて、何が問題なのか判断していった。
いきなり正解だと思っているものを書いていても迷うだけだったことに気づいてからは、少しずつ着実に確認できる方法にシフトしたのが、最終的なゴールに近づけた要員だと思ってる。
起動時に変なエラーが出てくる。しかもAOPとは関係なさそうなエラー
原因は、AOPのアノテーションのクラス名が間違っていたので、そんなクラスのインスタンスはないって怒られていた。
怒られていたときのソースを確保するのを忘れたので、ソース内容は省略
発生していたエラーは、以下の通り
org.springframework.context.ApplicationContextException: Unable to start web server // 省略 Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tomcatServletWebServerFactory' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration$EmbeddedTomcat.class]: BeanPostProcessor before instantiation of bean failed // 省略 Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration': Pointcut is not well-formed: expecting ')' at character position 75 @annotation(com.galewings.annotation.FunctionCtrlAspect(#functionCtrlAspect.value())) && execution(public * *(..)) // 省略 Caused by: java.lang.IllegalArgumentException: Pointcut is not well-formed: expecting ')' at character position 75 @annotation(com.galewings.annotation.FunctionCtrlAspect(#functionCtrlAspect.value())) && execution(public * *(..)) // 省略
参考情報
AOPでコントローラメソッド呼出前にエラー画面に遷移してみた|ITエンジニアとして経験・学習したこと
AspectJ の機嫌の取り方の本質 - あなたの `@Transactional` はどうして無視されてしまうのか #Java - Qiita
【Java】 独自アノテーションの作成 #Java - Qiita
Spring Bootで、独自アノテーションを目印にAOPを行う方法 - エキサイト TechBlog.
あと、GeminiとかchatGPTも利用した。
正解がわからないから、ググるのも厳しくて、大筋のあたりやヒントを得るために利用した。
まぁ、回答が正解だったかと言われると違ったが、解決のヒントにはなった。
感想
なんとかやりたいことが実現できた。。。
問題に直面した時、どうやって解決していくのか、エンジニアとしての真価が問われるなって感じた。
いきなり正解を的中させようとして泥沼にハマりかけるのは、よくあることだと思ってる。
そうなりそうってのに気づいて、別の解法を探る方法にシフトできたのが良かった。
遠回りでも、確実な方法から着実にゴールに向かうことの重要性を痛感した。
童話の「うさぎとかめ」だよなぁ。もしくは、急がば回れ。
目先の最速の方法にこだわるあまり、迷走するってことは、これまでも何回か経験している。
プライドが他の方法をとることを拒絶するんだよなぁ。。。
それを捨てて、自分の状況や習熟度を客観的に見てやり方を変えられる奴は、すごいのだと思った。
結構、調べた内容に踊らされがちだった。。。
ちゃんと自分で調べて裏取りをキッチリするべきだったと思った。
忍耐力って大切だよなぁ。
問題が起きた時に、すぐにカーッとなってしまうと、解決するものも解決できない。
冷静に淡々と状況を俯瞰して、とるべき戦略を変えられる人間に、俺はなりたい。