すばらしい!Spring5 AOP はデフォルトで Cglib を使用しますか? 現象からソースコードの深度分析まで

Spring5 AOP はデフォルトで Cglib を使用しますか? 私がこの発言を初めて聞いたのは、WeChat グループでのことでした。
グループチャット

本物か偽物か?ドキュメントを確認してください

初めてこの声明を見たとき、私は懐疑的でした。

Spring 5 より前の AOP バージョンではデフォルトで JDK 動的プロキシが使用されることは誰もが知っていますが、Spring 5 バージョンが変更されたというのは本当ですか? そこで、Spring Framework 5.x ドキュメントを開いて再度確認しました。

ドキュメントアドレス: https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/core.html#aop

Spring Framework 5.x ドキュメント
単純に翻訳してください。Spring AOP はデフォルトで JDK 動的プロキシを使用し、オブジェクトがインターフェイスを実装していない場合は CGLIB プロキシを使用します。もちろん、CGLIB プロキシの使用を強制することも可能です。

何?ドキュメントが間違っていますか?

正式な文書をグループに送った後、このクラスメートから次のような返信が届きました。
ドキュメントが間違っていますか?  !

SpringBoot 2.x コードサンプル

この学生は、文書の書き方が間違っていることを証明するために、デモも作成しました。次に、このデモ プログラムを再現してみましょう。

動作環境:SpringBoot 2.2.0.RELEASE版、内蔵Spring Frameworkのバージョンは5.2.0.RELEASE版。同時に、Spring AOP を自動的にアセンブルするために spring-boot-starter-aop 依存関係を追加します。

public interface UserService {
    
    
    void work();
}

@Service
public class UserServiceImpl implements UserService {
    
    

    @Override
    public void work() {
    
    
        System.out.println("开始干活...coding...");
    }
}
@Component
@Aspect
public class UserServiceAspect {
    
    
    @Before("execution(* com.me.aop.UserService.work(..))")
    public void logBefore(JoinPoint joinPoint) {
    
    
        System.out.println("UserServiceAspect.....()");
    }
}

Cglib プロキシはデフォルトで使用されますか?
UserServiceImplインターフェイスが実装されておりUserServiceメソッドの事前に強化されたインターセプトUserServiceAspectが同時に使用されます。UserService#work

実行結果から判断すると、ここでは JDK 動的プロキシの代わりに CGLIB プロキシが実際に使用されています。

それは本当にドキュメントのタイプミスでしょうか?

@EnableAspectJAutoProxy ソース コード アノテーション

Spring Framework では、@EnableAspectJAutoProxyアノテーションを使用して Spring AOP 関連の機能を有効にします。

Spring Framework 5.2.0.RELEASE バージョンの@EnableAspectJAutoProxyアノテーションのソース コードは次のとおりです。
@EnableAspectJAutoProxy ソース コード
ソース コード アノテーションを通じて、次のことがわかります。 Spring Framework 5.2.0.RELEASE バージョンでは、proxyTargetClassデフォルト値は依然として false であり、JDK は動的プロキシは引き続きデフォルトで使用されます。

ドキュメントとソースコードのコメントは間違っていますか?

@EnableAspectJAutoProxy の proxyTargetClass が無効ですか?

次に、@EnableAspectJAutoProxyJDK 動的プロキシの使用を強制しようとしました。

動作環境:SpringBoot 2.2.0.RELEASE版、内蔵Spring Frameworkのバージョンは5.2.0.RELEASE版。

proxyTargetClassの設定が無効ですか?
検出を実行しても、CGLIB プロキシは引き続き使用されます。@EnableAspectJAutoProxy の proxyTargetClass 設定が無効ですか?

Spring フレームワーク 5.x

考えを整理する

  1. Spring 5 では AOP が開始され、デフォルトで CGLIB が使用されると言う人もいます。
  2. Spring Framework 5.x のドキュメントと@EnableAspectJAutoProxyソース コードのコメントの両方に、デフォルトで JDK 動的プロキシを使用することが記載されています。
  3. プログラムを実行した結果、インターフェイスが継承されて に設定されていてもproxyTargetClassfalseプログラムは依然として CGLIB プロキシを使用していることがわかります。

ちょっと待って、何か見落としていませんか?

サンプルプログラムは SpringBoot を使用して実行されていますが、SpringBoot の代わりに Spring Framework のみを使用する場合はどうなるでしょうか。

動作環境:Spring Framework 5.2.0.RELEASE版。UserServiceImpl クラスと
UserServiceAspect クラスは上記と同じなので、ここでは繰り返しません。

Spring フレームワーク 5.x
ここに画像の説明を挿入
実行結果は次のことを示しています。 Spring Framework 5.x バージョンでは、クラスがインターフェイスを実装している場合、AOP は引き続きデフォルトで JDK 動的プロキシを使用します。

アイデアを並べ替える

  1. Spring5 AOP は依然としてデフォルトで JDK 動的プロキシを使用しており、公式ドキュメントとソース コードのコメントは正しいです。
  2. SpringBoot 2.x バージョンでは、AOP はデフォルトで cglib を使用し、proxyTargetClass を通じて変更することはできません。
  3. SpringBoot 2.x バージョンではいくつかの変更が加えられましたか?

SpringBoot 2.x をもう一度探索する

上記の分析の結果、SpringBoot2.x バージョンでは Spring AOP の関連構成が変更されている可能性が非常に高いです。次に、ソース コード分析の波に乗り、内部で何が起こっているかを確認します。

ソースコード分析

ソース コード分析では、適切なエントリを見つけることが非常に重要です。今度は入り口はどこですか?
@SpringBootApplicationは、自動配線を多用する複合アノテーションです@EnableAutoConfiguration

EnableAutoConfigurationアノテーションにフラグが設定されている複合アノテーションでもあります@Import注釈の詳しい使用方法については@Import、著者の以前の記事を参照してください: https://mp.weixin.qq.com/s/7arh4sVH1mlHE0GVVbZ84Q

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    
    

AutoConfigurationImportSelectorDeferredImportSelectorインターフェースを実装しました。

Spring Framework 4.x バージョンでは、これは空のインターフェイスであり、ImportSelectorインターフェイスを継承するだけです。5.x バージョンでは、DeferredImportSelectorインターフェイスが拡張され、メソッドが追加されましたgetImportGroup。クラス
AutoConfigurationImportSelector#getImportGroup
はこのメソッドで返されますAutoConfigurationGroupこれはAutoConfigurationImportSelectorの内部クラスであり、DeferredImportSelector.Groupインターフェイスを実装します。

SpringBoot 2.x バージョンでは、AutoConfigurationImportSelector.AutoConfigurationGroup#process自動構成クラスはメソッドを通じてインポートされます。
インポート構成クラス
ブレークポイントのデバッグを通じて、AOP に関連する自動構成が を介して構成されていることがわかりますorg.springframework.boot.autoconfigure.aop.AopAutoConfiguration
AopAutoConfiguration ソース コード

真実が出てきた

これを見ると、真実が明らかになったと言えます。SpringBoot2.x 版では、AopAutoConfigurationAOP が自動的にアセンブルされます。

デフォルトでは、そのような構成項目は絶対にありませんspring.aop.proxy-target-class現時点では、SpringBoot 2.x バージョンでは Cglib がデフォルトで使用されます。

SpringBoot 2.x で AOP 実装を変更する方法

spring.aop.proxy-target-classまた、ソース コードを通じて、SpringBoot 2.x の AOP 実装を変更する必要がある場合は、この構成項目を通じて変更する必要があることもわかります。

#在application.properties文件中通过spring.aop.proxy-target-class来配置
spring.aop.proxy-target-class=false

spring-configuration-metadata.json
spring-configuration-metadata.jsonここでファイルの役割についても説明します。application.propertiesまたはapplication.ymlファイルを使用する場合、IDEA はこれらのファイルの情報を読み取ることでコード ヒントを提供しますが、SpringBoot フレームワーク自体はこの構成ファイルを読み取りません。

SpringBoot 1.5.x についてはどうですか

スリングブート 1.5.x
SpringBoot 1.5.x バージョンでは、JDK 動的プロキシが引き続きデフォルトで使用されていることがわかります。

SpringBoot 2.x がデフォルトで Cglib を使用する理由

SpringBoot 2.x がデフォルトで Cglib を使用して AOP を実装するのはなぜですか? そうすることでどのようなメリットがあるのでしょうか? 著者はインターネットからいくつかの情報を見つけました。まず問題を見てみましょう。

Spring Boot の問題 #5423

@EnableTransactionManagement(proxyTargetClass = true) を使用します #5423
https://github.com/spring-projects/spring-boot/issues/5423

今回の号ではこんな疑問が投げかけられています。
問題


翻訳すると、ユーザーがインターフェイスを使用しない場合の厄介なプロキシの問題を防ぐために、@EnableTransactionManagement(proxyTargetClass = true) を使用する必要があります。

この「インターフェイスを使用していない場合の厄介なプロキシ問題」とは何ですか? ちょっと考えてみましょう。

厄介なプロキシの問題

今回使用する必要があるUserServiceImplandクラスがあるとしますSpring では通常、次のようなコードを記述するのが通例です。UserServiceUserContollerUserService

@Autowired
UserService userService;

この場合、JDK 動的プロキシも CGLIB も問題を引き起こしません。

しかし、コードが次のような場合はどうなるでしょうか。

@Autowired
UserServiceImpl userService;

このとき、JDK 動的プロキシを使用すると、起動時にエラーが報告されます。JDK
起動エラー
動的プロキシはインターフェイスに基づいているため、プロキシによって生成されたオブジェクトはインターフェイス変数にのみ割り当てることができます。

CGLIB にはこの問題はありません。CGLIB はサブクラスを生成することで実現されるため、プロキシ オブジェクトがインターフェイスに割り当てられている場合でも、実装クラスに割り当てられている場合でも、両方がプロキシ オブジェクトの親クラスになります。

このため、SpringBoot はバージョン 2.x で AOP のデフォルト実装を CGLIB に変更しました。

詳細については、読者自身で上記の問題を参照してください。

要約する

  1. Spring 5.x の AOP は引き続きデフォルトで JDK 動的プロキシを使用します。
  2. SpringBoot 2.x 以降、JDK 動的プロキシの使用によって発生する可能性のある型変換例外を解決するために、CGLIB がデフォルトで使用されます。
  3. SpringBoot 2.x では、デフォルトで JDK 動的プロキシを使用する必要がある場合、構成項目 spring.aop.proxy-target-class=false を使用して変更できますが、proxyTargetClass 構成は無効です。

参考文献

問題:デフォルトの CGLib プロキシ設定のデフォルトは、コア フレームワークのアノテーション (@EnableTransactionManagement、@EnableAspectJAutoProxy) を使用してオーバーライドできません #12194
https://github.com/spring-projects/spring-boot/issues/12194

この号では、proxyTargetClass設定の無効化の問題についても説明しました。議論には以下が含ま@EnableAspectJAutoProxy@EnableCachingます@EnableTransactionManagement興味のある読者は、今号の内容をご自身で参照してください。

参考文献

https://www.cnblogs.com/coderxiaohei/p/11758239.html

おすすめ

転載: blog.csdn.net/sdujava2011/article/details/131432976