← Spring コース
46. AOP プロキシと自己呼び出し
の前後に共通の処理()を挟む の仕組みを と呼びます( はアスペクト指向プログラミングの略)。対象の は、元ののではなく代理()に置き換わります。実測でのクラス名は ReportService$$SpringCGLIB$$0 で、親が ReportService でした。の回の差し込みを、Spring が自動で行っています。
作り方は2通りです。を実装する JDK の動的プロキシと、対象のを実行時に生成する です。 単体の既定は、インターフェースがあれば前者です。 4.1.1 の既定では、インターフェースがあっても CGLIB になりました。リファレンスもこの違いに触れています。 を false にすると前者になりました。
つまずきどころはです。外からの呼び出しは代理を通りますが、本物のメソッドの中で this.inner() と呼ぶと、this は本物なので代理を通らず、アドバイスは走りません。また CGLIB はで作るので、アドバイスの対象になった クラスは起動に失敗し、final メソッドにはアドバイスが効きません。実測では、final メソッドの中でが null に見えました(この書き方での観測です)。
点線の付いた語はタップすると説明が出ます。用語集を開く →
呼び出す側ctx.getBean(ReportService.class) で受け取った参照
指しているのは代理outer() を呼ぶ
代理 ReportService$$SpringCGLIB$$0ReportService のサブクラス。アドバイスを挟む
[advice] before outer の後、本物へ
本物の ReportService.outer()この中の this は本物
this.inner() — 代理を通らない
本物の ReportService.inner()アドバイスは走らない
外から inner() を呼べば代理を通る- ・Spring Framework のリファレンスは「self invocation via an explicit or implicit this reference will bypass the advice」と書いています
- ・同リファレンスの制約: final classes cannot be proxied, because they cannot be extended. / final methods cannot be advised, because they cannot be overridden.
- ・既定の違いについての同リファレンスの文: While the core framework suggests interface-based proxies by default, Spring Boot may - depending on configuration properties - enable class-based proxies by default.
- ・同じ仕組みを使う @Transactional は、後のレッスンで扱います
サンプルコード(フレームワーク環境が必要なため表示のみ)
// l13/proxy/ProxyApp.java(抜粋)— 依存は spring-boot-starter-webmvc と spring-boot-starter-aspectj
package l13.proxy;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Logged {}
// @Logged の付いたメソッドの前後に出力を挟む
@Aspect
@Component
class LoggingAspect {
@Around("@annotation(l13.proxy.Logged)")
Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(" [advice] before " + pjp.getSignature().getName());
try { return pjp.proceed(); }
finally { System.out.println(" [advice] after " + pjp.getSignature().getName()); }
}
}
@Component
class ReportService {
@Logged
public void outer() {
System.out.println(" outer() body, this = " + this.getClass().getSimpleName());
this.inner(); // 自己呼び出し
}
@Logged
public void inner() { System.out.println(" inner() body"); }
}
// main では、コンテナから取り出した ReportService のクラス名と親クラス名を出力し、
// 続けて outer() と inner() をそれぞれ外から呼んでいる
// Spring Boot 4.1.1 / JDK 25 での出力(該当部分。末尾の数字は環境で変わる連番)
//
// ReportService: l13.proxy.ReportService$$SpringCGLIB$$0
// superclass=l13.proxy.ReportService ...
// --- call outer() from outside
// [advice] before outer
// outer() body, this = ReportService
// inner() body ← inner の前後にアドバイスが無い
// [advice] after outer
// --- call inner() from outside
// [advice] before inner
// inner() body
// [advice] after inner
図やコードに出てくる用語(タップで説明)
サンプルコードで身につける(5本) →
学んだ概念を、素朴な例から実務寄りの例まで5つの具体例で確認します。
公式ドキュメントで詳しく ↗