小学校
AOPとは何ですか?
アスペクト指向プログラミング (AOP) はアスペクト指向プログラミングです。
簡単に言えば、AOP は複数のオブジェクトにわたる動作をモジュール式に定義できるようにするプログラミング パラダイムです。AOP は、アプリケーションの問題を分離するのに役立ち、コードをより明確にし、保守と拡張を容易にします。
Vernacular: ロギング、トランザクションのオープン/コミット/ロールバックなど、メソッドの実行の前後に指定されたコードを実行します。
なぜ AOP なのか?
AOP は、コード内の高結合の問題を解決するのに役立ち、コードをよりモジュール化して保守しやすくします。
具体的には、AOP はコードを変更することなく、実行時に共通機能 (ロギング、パフォーマンス分析、トランザクション管理など) を複数のモジュールに動的に適用できます。これにより、コードの重複とネストが回避され、コードの再利用性と保守性が向上します。
さらに、複雑なビジネス シナリオでは、複数のモジュールがいくつかの共通機能を共有する必要がある場合がありますが、AOP はこれらの機能をモジュールから分離して、より適切に編成して再利用することができます。
一般に、AOP を使用すると、コードの分離と集約をより適切に実現できるため、より効率的で信頼性の高いコードを取得できます。
Vernacular: 元のメソッドの機能を強化し、共通の機能を分離し、透過的かつサイレントに動作します。
たとえば、トランザクション アスペクトのアスペクト:
元のメソッドの機能を強化します。元のメソッドは、データベース接続のデフォルトの戦略を使用してトランザクションを自動的に送信します。アスペクトでは、メソッド内で同じトランザクションが保証されます。一般的な機能は分離されます。多くのメソッドがトランザクションを実行する必要がある
制御、アスペクトを使用すると、各メソッドに同じコードを数行追加する必要がなく、
透過的でサイレントな操作: メソッド自体がトランザクションをどのように開いたかを知る必要がありますか? さらにログを印刷する時期を知りたいですか?
- 疑似コード: AOP を使用する前に、各メソッドは CV を使用してメソッド実行ログを 1 回出力する必要があります。
@Override
public UserPO findByUsername(@AutoTrim String username) {
log.info("execute findByUsername by username={}", username);
Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("username"), username));
Assert.isTrue(opt.isPresent(), "没有找到用户");
UserPO userPO = opt.get();
log.info("execute findByUsername by username={}; return {} ", username, userPO);
return userPO;
}
@Override
public UserPO findByEmail(@AutoTrim String email) {
log.info("execute findByEmail by email={}", email);
Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("email"), email));
Assert.isTrue(opt.isPresent(), "没有找到用户");
UserPO userPO = opt.get();
log.info("execute findByEmail by email={}; return {} ", email, userPO);
return userPO;
}
- 疑似コード: AOP を使用した後は、メソッド実行ログの出力を気にする必要はありません。
@Override
public UserPO findByUsername(@AutoTrim String username) {
Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("username"), username));
Assert.isTrue(opt.isPresent(), "没有找到用户");
return opt.get();
}
@Override
public UserPO findByEmail(@AutoTrim String email) {
Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("email"), email));
Assert.isTrue(opt.isPresent(), "没有找到用户");
return opt.get();
}
コードデモ [パート 1] の
デモの内容は次のとおりです。
- AOP が使用されていないことを示す前に、
UserServiceImpl
挿入された Bean を使用し、インターフェースをテストしてログ出力を表示します。 - AOP の使用法を示した後、
UserServiceAopImpl
挿入された Bean を使用して、ログ出力を表示するインターフェースをテストします。 - セクションを表示し、非プロジェクトのクラスメソッドの実行ログを印刷するには、
Pointcut
に切り替えますlogPointcut2()
。 - 構成の柔軟な使用方法を示し、ログを開き、
@ConditionalOnProperty
yml ファイルを開き、変更するための側面を制御します。たとえば、テスト環境ではログを開き、運用環境ではログを閉じる必要がありますが、これは構成によって柔軟に制御できます。
PS: ここではシリーズベースについては考慮しません
XmlApplicationContext
。
AOP を実装するにはどうすればよいですか?
AOP の実現は、ポリモーフィズムと動的プロキシに依存しています。
理解を深めるために、まず分析用の静的プロキシ クラスの例を示します。
コードデモ [part2] の
デモ内容は次のとおりです。
- ログを印刷するための静的プロキシを表示し、
UserServiceStaticAopImpl
挿入された Bean を使用し、ログの印刷を表示するインターフェイスをテストします。 - さまざまな通知タイプの静的プロキシを表示し、
UserServiceStaticAopAnyImpl
挿入された Bean を使用し、ログ出力を表示するインターフェイスをテストします。
PS: ここではシリーズベースについては考慮しません
XmlApplicationContext
。
AOP コードの構造と中心となる概念
静的プロキシの AOP 構造 (シンプルでわかりやすいバージョン):
側面
アスペクト: 複数のクラスにまたがり、異なるクラスの同様のメソッドに対応する問題点。データを保存するときにログを追加すると、新しいアスペクトを作成してロギング ロジックを構成できます。
Vernacular: モジュラー アスペクト プログラムは、アスペクト関数を実装するクラス、
つまり @Aspect によって定義される Bean クラス、または Spring XML 設定の aop:aspect タグとして理解することもできます。
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
結合ポイント
接続ポイント: メソッド呼び出しや例外処理など、アプリケーションの実行における特定の場所を指します。
これは、カットされるオブジェクトのタイプとして理解できます。たとえば、カットを追加するターゲットはコンストラクター、メソッド、または属性の割り当てです。AspectJ には多くのタイプがあります。詳細については、「AspectJ 結合ポイント」を参照してください。Spring AOP
では1 つだけ、これはメソッドの実行です。
アドバイス
アドバイス: アスペクトの結合点で実行されるコードを指します。アドバイスには「事前アドバイス」「事後アドバイス」「復帰アドバイス」「例外アドバイス」「サラウンドアドバイス」などがあり、その中でも「サラウンドアドバイス」はターゲットの実行を完全に制御することができます。方法。
ポイントカット
Pointcut : 1 つ以上の結合ポイントを指し、通常は正規表現で定義され、どのメソッドがインターセプトされるかを記述します。
参考:
結合ポイントとポイントカット
Spring の AOP AspectJ エントリ ポイント文法の詳細な
例:within(com.supalle.springaop..*.UserServiceAopImpl)
execution(* com.supalle.springaop.*.*(..))
within(com.supalle.springaop.TestController) && @annotation(com.supalle.springaop.Log)
他の
- 序章
- 目標
- AOPプロキシ
- 機織り
参照: AOP の概念
Spring AOPの一般的な方法
コードデモはこちら [part3]
@側面
XmlApplicationContext
開く必要があるアプリケーションを<aop:aspectj-autoproxy />
AnnotationConfigApplicationContext
開く必要があります@EnableAspectJAutoProxy
。SpringBoot 環境は使用できません。SpringBootorg.springframework.boot.autoconfigure.aop.AopAutoConfiguration
環境ではデフォルトで有効になっています。
package com.supalle.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* 日志切面
*
* @author supalle
* @see {@link https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts}
*/
@Slf4j
@Aspect
@Component
//@ConditionalOnProperty(value = "log-execution", havingValue = "true")
public class LogAspect {
@Pointcut("within(com.supalle.springaop..*.UserServiceAopImpl)")
// the pointcut within *AopImpl
private void logPointcut() {
}
@Pointcut("within(com.supalle.springaop..*.UserServiceAopImpl) || within(com.zaxxer.hikari.HikariDataSource)")
private void logPointcut2() {
}
@Before("logPointcut()")
public void beforeLog(JoinPoint joinPoint) {
log.info("Before Advice 1 execute {}", joinPoint.getSignature().getName());
}
// 同一个Aspect内,@Order排序无效,需要靠方法名排序,比如把当前方法改为beforeLog2,就能运行在beforeLog前面
@Before("logPointcut()")
public void beforeLog2(JoinPoint joinPoint) {
log.info("Before Advice 2 execute {}", joinPoint.getSignature().getName());
}
@AfterReturning(value = "logPointcut()", returning = "returnValue")
public void afterReturningLog(JoinPoint joinPoint, Object returnValue) {
log.info("AfterReturning Advice execute {}; {} ", joinPoint.getSignature().getName(), returnValue);
}
@AfterThrowing(value = "logPointcut()", throwing = "throwable")
public void afterThrowingLog(JoinPoint joinPoint, Throwable throwable) {
log.info("AfterThrowing Advice execute {}; {}", joinPoint.getSignature().getName(), throwable.getMessage());
}
@After("logPointcut()")
public void afterLog(JoinPoint joinPoint) {
log.info("After Advice execute {}", joinPoint.getSignature().getName());
}
@Around("logPointcut()")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
Signature joinPointSignature = joinPoint.getSignature();
String name = joinPointSignature.getName();
Logger logger = LoggerFactory.getLogger(joinPointSignature.getDeclaringTypeName());
Object[] args = joinPoint.getArgs();
String[] argNames = ((MethodSignature) joinPointSignature).getParameterNames();
String argString = "";
if (args != null && args.length > 0) {
argString = " by " + IntStream.range(0, args.length)
.mapToObj(index -> argNames[index] + "=" + args[index])
.collect(Collectors.joining(" , "));
}
logger.info("Around Advice execute {}{}", name, argString);
Object returnValue = joinPoint.proceed();
logger.info("Around Advice execute {}{}; return {} ", name, argString, returnValue);
return returnValue;
}
}
XML設定
少し
アドバイザー
org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors
AutoProxy のサポートにも属するコンテナ内のすべての Advisor タイプ Bean を検索します。
LogBeforeAdvice
: Adviceを定義して@Component
Beanとして登録する
package com.supalle.springaop.advice;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry的支持
*/
@Slf4j
@Component
public class LogBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
log.info("LogBeforeAdvice execute {}", method.getName());
}
}
LogBeforeAdvisor
: Advisor を定義し、@Component
Bean として登録します
package com.supalle.springaop.advisor;
import com.supalle.springaop.advice.LogBeforeAdvice;
import lombok.RequiredArgsConstructor;
import org.aopalliance.aop.Advice;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class LogBeforeAdvisor extends AbstractPointcutAdvisor {
private final LogBeforeAdvice advice;
@Override
public Pointcut getPointcut() {
Pointcut pointcut = new Pointcut() {
@Override
public ClassFilter getClassFilter() {
return clazz -> "UserServiceAopImpl".equals(clazz.getSimpleName());
}
@Override
public MethodMatcher getMethodMatcher() {
return MethodMatcher.TRUE;
}
};
return pointcut;
}
@Override
public Advice getAdvice() {
return advice;
}
}
コードを実行した後、インターフェイスをデバッグし、コンソールの出力を確認します。
: LogBeforeAdvice execute findByUsername
プログラマーが作成した
あまり使用されません。@AspectJ プロキシのプログラムによる作成を参照してください。
高度
Spring AOP と動的プロキシ
動的プロキシ
ダイナミックプロキシとは、プログラムの実行中にプロキシクラスを動的に生成する技術を指します。つまり、プロキシクラスのソースコードを手動で記述する必要はなく、プログラム実行中にリフレクションなどの仕組みを通じてプロキシクラスを動的に生成します。
動的プロキシ モードは、コードの繰り返しを減らし、コードの保守性とスケーラビリティを向上させるのに役立ちます。通常、インターフェイスを実装してプロキシ オブジェクトを作成しますが、クラスがインターフェイスを実装していない場合でも、動的プロキシを通じてプロキシ オブジェクトを作成できます。動的プロキシ モードは、ロギング、セキュリティ、トランザクション、その他の機能など、いくつかの横断的な問題の処理に適しています。Java には、動的プロキシ モードの主な実装が 2 つあります。
JDK ベースの動的プロキシ (JDK 動的プロキシ): JDK は、指定されたセットを実装するプロキシ クラスを動的に作成できる java.lang.reflect.Proxy クラスを提供します。インターフェース 。プロキシされるオブジェクトは少なくとも 1 つのインターフェイスを実装する必要があり、プロキシ オブジェクトは Proxy クラスの静的メソッド newProxyInstance を通じて作成されます。
CGLIB ベースの動的プロキシ: CGLIB (コード生成ライブラリ) は、ASM (Java バイトコード操作フレームワーク) に基づく高性能バイトコード生成ライブラリであり、実行時に動的にバイトコードを生成し、対応するプロキシ クラスを生成できます。プロキシ オブジェクトにはデフォルトのコンストラクタが必要であり、プロキシ オブジェクトは CGLIB ライブラリによって提供されるプロキシ ファクトリ (Enhancer クラス) を通じて作成されます。
つまり、動的プロキシ モードは、プログラムの実行中にプロキシ クラスを動的に生成して、機能を強化し、処理ロジックを追加するという目的を達成するのに役立ちます。
春のAOP
Spring AOP はダイナミック プロキシのランディング実装です。最下層は JDK プロキシと Cglib&ASM 戦略を通じてプロキシ クラスを動的に生成します。AOP が
Spring ネイティブ アプリケーションに実装されている場合、バイトコードを動的に生成できないため、最初にそれを読みました。したがって、AOT のコンパイル中に、アプリケーション内のすべてのクラスに対してプロキシ クラスが生成され、実際の動作中にこのクラスが動的プロキシを必要とする場合、プロキシ クラスがロードされて直接使用されます。(時間が経ち、実装方法が変わったのかは分かりません)
AOT (Ahead-of-Time) コンパイラ: プリコンパイラ;
JIT (Just-In-Time) コンパイラ: ジャストインタイム コンパイラ;
Spring AOPの構造とコアクラス
Spring に対応するクラスがある前述のAOP の基本概念に加えて、特定の AOP 実装用の Spring AOP API には他にもいくつかのコア クラスがあります。
- Advised: プロキシ クラスのトップレベル インターフェイス。委託されたオブジェクトとその情報、およびアプリケーション アドバイザのコレクションが含まれます。
- アドバイザ:
Pointcut
+Advice
;の組み合わせ - AopProxy: AOP 固有のプロキシ実装。JDK プロキシまたは Cglib プロキシの場合があります。
- JdkDynamicAopProxy
- オブジェクトCglibAopProxy
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
UML を使用して、次のように構造図を描画します。
例としてUserService
、Spring AOP によって生成されるプロキシ クラスの一般的な構造は次のとおりです。
Spring AOP 呼び出しチェーン
よく考えてみると、上で例示した静的プロキシによる AOP 実装のコードは、特にシーケンス部分で十分に健全ではありません。Advice
コード
のデモ [その 2] を次に示します。beforeAspects
アスペクトに System.out.println(1/0) を追加します。例外をスローする
Spring 公式ドキュメントの原文を引用します。
アドバイス: 特定の結合ポイントでアスペクトによって実行されるアクション。さまざまな種類のアドバイスには、「前後」、「前」、「後」のアドバイスが含まれます。(アドバイスのタイプについては後で説明します。) Spring を含む多くの AOP フレームワークは、アドバイスをインターセプターとしてモデル化し、結合ポイントの周囲にインターセプターのチェーンを維持します。
「Spring を含む多くの AOP フレームワークは、アドバイスをインターセプターとしてモデル化し、結合ポイントの周りにインターセプターのチェーンを維持します。」という文を翻訳すると、「Spring を含む多くの AOP フレームワークは、アドバイスをインターセプターとしてモデル化し、結合ポイントの周りにインターセプターのチェーンを維持します。」インターセプターのようなもの
を使用すると、実行順序をjavax.servlet.Filter
微調整できます。Advice
見る:
org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice
タイミング図はおおよそ次のとおりです。
Advisor/アドバイスの実行順序
呼び出しチェーンの順序は、Advised のアドバイザー コレクションの順序に基づいており、主な並べ替え戦略は次のとおりです。
Advisor
の値order
。値がない場合はその値になります。Advice
値order
がまだない場合はnull
、最も低い優先順位とほぼ同じになります。- 同じクラス
@Aspect
で複数のアドバイスが定義されている場合は、最初にAround
、Before
、After
、AfterReturning
、AfterThrowing
、次にメソッド名で並べ替えます。 - 同じ
Advice
クラスが複数のAdvice
型を実装する場合、MethodInterceptor
、BeforeAdvice
、AfterReturningAdvice
;でAfterThrowsAdvice
ソートします。
AbstractAdvisingBeanPostProcessor
ポストプロセッサーによって追加されるとAdvisor
、ポストプロセッサーはbeforeExistingAdvisors
属性を通じて既存のすべてのプロセッサーよりも前にソートするかどうかを制御できますAdvisor
。
- Spring 公式ドキュメント: アドバイスの順序付け
- 並べ替えアドバイザー:
org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#sortAdvisors
- 同じアスペクトでアドバイスを並べ替える:
org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisorMethods
PS: Advisor/Advice が Ordered インターフェイスを実装している場合、@Order アノテーションは有効になりません。
Spring AOP に Advisor を追加する方法
ProxyConfig
いくつかのファミリーがあり、そのすべてを追加してAdvisor
AOP プロキシ クラスを作成できます。
ProxyProcessorSupport家族
抽象アドバイスBeanPostProcessor
AbstractAutoProxyCreator
AdvisedSupport家族
プロキシファクトリービーン
抽象シングルトンプロキシファクトリーBean ファミリー
ScopedProxyFactoryBean家族
共通のアドバイザーとアドバイス
AsyncAnnotationAdvisor と AsyncExecutionInterceptor
並べ替え: Ordered=Ordered.HIGHEST_PRECEDENCE; // Integer.MIN_VALUE; メソッドはハードコーディングされており、継承のみ変更可能 注
:ポストプロセッサAsyncAnnotationAdvisor
で作成および照合されます, beforeExistingAdvisors=true; 既存のすべての Advisor の前に並べ替えられますAsyncAnnotationBeanPostProcessor
。
Spring の AOP による非同期実行のサポート。
AsyncAnnotationAdvisor と AnnotationAsyncExecutionInterceptor
並べ替え: Ordered=Ordered.HIGHEST_PRECEDENCE; // 親クラスから継承
AsyncExecutionInterceptor
AOP によるSpring アノテーション@Async
の非同期実行のサポート。
RetryConfiguration & AnnotationAwareRetryOperationsInterceptor
並べ替え: Ordered=Ordered.LOWEST_PRECEDENCE; // 親クラスから継承され
AbstractPointcutAdvisor
、設定可能;
注:RetryConfiguration
実装では、並べ替えは同じ順序のIntroductionAdvisor
未実現アドバイザーよりも優先されます。IntroductionAdvisor
AOP によるSpring アノテーション@Retryable
メソッドの失敗の再試行のサポート。
BeanFactoryCacheOperationSourceAdvisor と CacheInterceptor
並べ替え: Ordered=Ordered.LOWEST_PRECEDENCE; // 親クラスから継承され
AbstractPointcutAdvisor
、設定でき、
デフォルト@EnableCaching
属性order
もLOWEST_PRECEDENCE
アノテーションで直接変更できます。
Spring アノテーション キャッシュなどの AOP サポート@EnableCaching
。@Cacheable
BeanFactoryTransactionAttributeSourceAdvisor および TransactionInterceptor
並べ替え: Ordered=Ordered.LOWEST_PRECEDENCE; // 親クラスから継承され
AbstractPointcutAdvisor
、設定でき、
デフォルト@EnableTransactionManagement
属性order
もLOWEST_PRECEDENCE
アノテーションで直接変更できます。
@Transactional
AOP によるSpring アノテーション トランザクションのサポート。
高度
Spring が AOP プロキシを自動的に作成する場合
Spring AOP プロキシ クラス作成のタイミングは、主に Bean が参照される前にそれに依存することで実現されBeanPostProcessor
、
これはProxyProcessorSupport
ファミリーのAbstractAdvisingBeanPostProcessor
合計に依存しますAbstractAutoProxyCreator
。
AbstractAdvisingBeanPostProcessor
Advisor
AOP アスペクトを単一のアスペクトに追加します。AbstractAutoProxyCreator
満足したすべての Bean にPointcut
アスペクトを追加します。
ほとんどの場合、postProcessAfterInitialization()
Bean の AOP プロキシ オブジェクトはフック メソッドで作成されますが、カスタマイズする場合などの特殊なケースもあり、TargetSource
プロキシpostProcessBeforeInstantiation()
オブジェクトはフック メソッドで作成
さgetEarlyBeanReference()
れ、AOP プロキシ オブジェクトが作成されます。あらかじめ;
詳細については、ソース コードを参照してください。
org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
もう 1 つの注意:postProcessBeforeInstantiation()
フックが実行されると、Bean はオブジェクトの作成、プロパティの入力、依存関係の注入、および初期化メソッドの実行を完了します。
ProxyFactoryBean
、 、などの他の自動的に作成される派生クラスもAbstractSingletonProxyFactoryBean
、ScopedProxyFactoryBean
Bean の初期化時に自動的に作成されます。
BeanPostProcessor が AOP 機能を使用できない理由
Spring IOC コンテナはそれらすべてをスキャンした後BeanDefinition
、最初にすべてを順番に初期化しBeanPostProcessor
、次に他の共通のものを初期化しますBean
。また、自動 AOP を処理するAbstractAdvisingBeanPostProcessor
これらAbstractAutoProxyCreator
2 つのポストプロセッサはソートされている Ordered=LOWEST_PRECEDENCE
ため、これら 2 つのポストプロセッサではどれも以前に初期化されたBean
ものは、それらによって自動的に AOP プロキシされることができます。
BeanPostProcessor を使用した Bean のカスタマイズ
Spring官方ヒント: BeanPostProcessor インターフェースを実装する
BeanPostProcessor インスタンスと AOP 自動プロキシ
クラスは特別であり、コンテナによって異なる方法で扱われます。すべての BeanPostProcessor インスタンスとそれらが直接参照する Bean は、ApplicationContext の特別な起動フェーズの一部として、起動時にインスタンス化されます。次に、すべての BeanPostProcessor インスタンスがソートされた方法で登録され、コンテナ内の以降のすべての Bean に適用されます。AOP 自動プロキシは BeanPostProcessor 自体として実装されるため、BeanPostProcessor インスタンスも、BeanPostProcessor インスタンスが直接参照する Bean も自動プロキシの対象にはならず、したがって、それらに組み込まれた側面はありません
。org.springframework.context.support.PostProcessorRegistrationDelegate#registerBeanPostProcessors
Spring は AOP プロキシ クラスの繰り返し作成をどのように防ぐか
- まず
AbstractAutoProxyCreator
、Map
AOPプロキシクラスの作成情報を格納するオブジェクトは、targetSourcedBeans
、advisedBeans
、 の3つですがearlyProxyReferences
、この3つにBeanプロキシオブジェクトが存在する場合、重複して作成されることはありません。 AbstractAdvisingBeanPostProcessor
bean instanceof Advised
すでに AOP プロキシ オブジェクトである場合は、新しいプロキシ オブジェクトは作成されず、このプロキシ オブジェクトに直接追加されると判断されますAdvisor
。
Spring AOP と Spring 3 レベルキャッシュとよく呼ばれるキャッシュとの関係
Spring の 3 レベル キャッシュとは、org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
クラス内の次の 3 つの Map-type 属性を指します。
Map<String, Object> singletonObjects; // 存放初始化完成的单例Bean
Map<String, Object> earlySingletonObjects; // 存放getEarlyBeanReference()钩子创建的早期Bean
Map<String, ObjectFactory<?>> singletonFactories; // 存放earlySingletonObject的工厂实例
getEarlyBeanReference
IOC はデフォルトで循環参照を許可しますが、依存関係の注入中、Spring は AOP プロキシの作成動作を進めたくないため、フックと はsingletonFactories
循環earlySingletonObjects
参照をサポートするように設計されています。
インスタンス化後、後続の依存関係注入の他のBean
参照用にファクトリ インスタンスが最初に作成されます。循環依存関係がある場合、呼び出されたファクトリのメソッドが注入用に構築され、これは将来の使用に備えてライブラリに保存されます。循環依存関係は複数回あり、最終的に、現在の Bean の初期化が完了した後、初期段階が削除され、最後の段階が挿入されて、正式に公開される Bean インスタンスになります。earlySingletonObject
Bean
Bean
earlySingletonObject
Bean
Bean
earlySingletonObjects
Bean
Bean
singletonFactories
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
拡大
ASM/Cglib 動的バイトコード生成
長すぎる、少し
Springキャッシュの無効化とAOPの関係
Spring キャッシュ アスペクトは主に AOP の実装に依存しており、オブジェクト内で this.doSomething() が呼び出された場合、プロキシ オブジェクトを経由しないためアスペクトをキャッシュする機能はありません。
Springトランザクションの失敗とAOPの関係
Spring のトランザクション アスペクトもキャッシュ アスペクトと同様に主に AOP の実装に依存しており、オブジェクト内で this.doSomething() を呼び出す際にはプロキシ オブジェクトを経由しないためトランザクション アスペクトの機能を持ちません。
Synchronized および @Transactional を使用する場合の問題
トランザクションアスペクトはメソッドを実行する前にトランザクションをオープンし、ロックを追加するため、ロックされたコードが実行された後、トランザクションが送信されるため、同期されたコードブロックの実行はトランザクション内で実行されると推測できます
。コード ブロックが実行されるとき、トランザクションはコミットされておらず、他のスレッドが同期されたコード ブロックに入った後、読み取られたインベントリ データは最新ではありません。
上記の方法を解決するには、update メソッドの前に synchronizedQ を追加して、トランザクションを開く前にロックすることで、スレッドの同期を保証することが比較的簡単です。
他の開発言語でも AOP を実装できます
JavaScript のプロキシは AOP を実装します
考え
AOPの利点
- コードの結合の削減: AOP は、横断的なロジックをビジネス ロジックから分離できるため、コードの重複と冗長性が回避され、コードの保守性と可読性が向上します。
- コードの再利用性の向上: AOP は、一般的な横断的なロジックをアスペクトとして抽象化できるため、さまざまなビジネス ロジックで再利用できるため、コードの再利用性が向上します。
- システムのスケーラビリティの向上: AOP は、横断的なロジックをビジネス ロジックに動的に組み込むことができるため、新しい機能を簡単に追加でき、システムのスケーラビリティが向上します。
AOPの欠点
- AOP はシステムの複雑さを増大させます。AOP は追加の構成とコードを必要とするため、システムの複雑さが増大し、システムの可読性と保守性が低下します。
- AOP はシステムのパフォーマンスに影響を与える可能性があります: AOP は実行時に追加の処理を必要とするため、特に大規模なアプリケーションでは、システムのパフォーマンスに影響を与える可能性があります。
- AOP は新しい問題を引き起こす可能性があります: AOP は、デッドロックや同時実行の問題など、特別な注意を必要とする新しい問題を引き起こす可能性があります。
AOP の使用に関する推奨事項
- ビジネスに関連するコードをアスペクトに含めないでください。
- スライスの順番に注意してください
- この呼び出しにより AOP が失敗する可能性があることに注意してください