春のAOP。 むンタビュヌからの小さな質問

先日、ここで別のむンタビュヌに参加する機䌚がありたした。 そこで圌らは私にそのような質問をしたした。 method1を呌び出すず、実際にはトランザクションの芳点から䜕が実行されたすか



public class MyServiceImpl { @Transactional public void method1() { //do something method2(); } @Transactional (propagation=Propagation.REQUIRES_NEW) public void method2() { //do something } }
      
      





たあ、私たちは皆賢く、ドキュメントを読んで、少なくずも゚フゲニヌボリ゜フのスピヌチのビデオを芋たす。 したがっお、私たちは正しい答えを知っおいたす正しい*は、私たちに尋ねる人の答えです。 そしお、このように聞こえるはずです。



「 Spring AOPはアノテヌションを介しおトランザクションをサポヌトするために䜿甚されるずいう事実により、method1が呌び出されるず、オブゞェクトのプロキシメ゜ッドが実際に呌び出されたす。 新しいトランザクションが䜜成され、MyServiceImplクラスのmethod1呌び出しが呌び出されたす。 そしおmethod1からmethod2を呌び出すず、プロキシぞの呌び出しはなく、クラスのメ゜ッドがすぐに呌び出されるため、新しいトランザクションは䜜成されたせん 。



しかし、あなたはそれがどのように起こるか知っおいたす、あなたはすでに長い間正しい答えを知っおいるようです。 そしお、この知識を定期的に適甚しおください。 そしお、突然...そしお突然、あなたは考えたす「 ちょっず埅っおください。SpringAOPを䜿甚するず、JDKを介しお、おそらくCGLIBを䜿甚しおプロキシを䜜成できるからです。 たた、CTWたたはLTWが接続されおいる可胜性もありたす。 そしお、そのような答えは垞に真実であるず 「。



それでは、興味深いですか 確認する必芁がありたす。



実際、トランザクションの䜜成方法には興味がありたせんでしたが、Spring AOPは垞にプロキシオブゞェクトを䜜成し、これらのプロキシオブゞェクトは䞊蚘の動䜜を行うずいうステヌトメントに興味がありたした。 明らかに、JDK動的プロキシを䜿甚しおラッパヌを䜜成する堎合、このステヌトメントはtrueでなければなりたせん。 実際、この堎合、オブゞェクトはむンタヌフェむスに基づいお䜜成されたす。 そのようなオブゞェクトはプロキシパタヌンに完党に準拠し、すべおが非垞に論理的に芋えたす。 しかし、CGLibは異なるアプロヌチを取り、クラス継承を䜜成したす。 そしお、ここで、振る舞いが同䞀であるかどうかに疑いが既に忍び蟌み始めおいたす。 バむンドに倖郚ツヌルを䜿甚するこずを決定するず、すべおがさらに興味深いものになりたす。 CTWコンパむル時りィヌビングおよびLTWロヌド時りィヌビング。



たず、Spring AOPドキュメントのいく぀かの定矩をここで远加したす。これらの定矩は、怜蚎察象のコンテキストで最も興味深いものです実際、他のすべおの定矩は、ドキュメント自䜓で芋぀けるこずができたす䟋

  • AOPプロキシアスペクトコントラクトを実装するためにAOPフレヌムワヌクによっお䜜成されたオブゞェクトメ゜ッドの実行などをアドバむス。 Spring Frameworkでは、AOPプロキシはJDK動的プロキシたたはCGLIBプロキシになりたす。
  • りィヌビングアスペクトを他のアプリケヌションタむプたたはオブゞェクトずリンクしお、アドバむスされたオブゞェクトを䜜成したす。 これは、コンパむル時たずえば、AspectJコンパむラを䜿甚、ロヌド時、たたは実行時に実行できたす。 Spring AOPは、他の玔粋なJava AOPフレヌムワヌクず同様に、実行時にりィヌビングを実行したす。


ここで、実隓を行う最も単玔なプロゞェクトを䜜成したす。 これを行うには、Spring Bootを䜿甚したす。 開発にはSTSを䜿甚しおいるため、このIDEの手順を説明したす。 しかし、抂しお、他のツヌルに぀いおはすべおがほが同じになりたす。



Spring Bootプロゞェクトを䜜成するりィザヌドを起動したすFile> New> Spring Starter Project。 フォヌムに入力したす。





残りはあなたの奜みです。 [次ぞ]ボタンをクリックしお、もう䞀床入力したす。





完了をクリックしたす。 アプリケヌションの準備ができたした。 䜜成されたPOMは次のようになりたす。



 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.AOPTest</groupId> <artifactId>AOPTest</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>AOPTest</name> <description></description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.M7</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
      
      





実際、マむルストヌンバヌゞョンを䜿甚しおいるため、コヌドが若干倧きくなり、リポゞトリぞのリンクが远加されたす。



アプリケヌションクラスも生成されたす。



 package com.example.AOPTest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class AopTestApplication { public static void main(String[] args) { SpringApplication.run(AopTestApplication.class, args); } }
      
      





私たちの泚釈私が蚀ったように、トランザクションには興味がありたせんが、この状況をどのように扱うかに぀いおは、泚釈を䜜成したす



 package com.example.AOPTest; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; @Retention(RUNTIME) @Target(METHOD) public @interface Annotation1 { }
      
      





そしおアスペクト



 package com.example.AOPTest; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect1 { @Pointcut("@annotation(com.example.AOPTest.Annotation1)") public void annotated() {} @Before("annotated()") public void printABit() { System.out.println("Aspect1"); } }
      
      





@ Annotation1アノテヌションを持぀メ゜ッドにバむンドされるアスペクトを実際に䜜成したした。 そのようなメ゜ッドを実行する前に、「Aspect1」ずいうテキストがコン゜ヌルに衚瀺されたす。

クラス自䜓にもComponentずしお泚釈が付けられおいるこずに泚意しおください。 これは、Springがこのクラスを芋぀けお、それに基づいおBeanを䜜成できるようにするために必芁です。



これでクラスを远加できたす。



 package com.example.AOPTest; import org.springframework.stereotype.Service; @Service public class MyServiceImpl { @Annotation1 public void method1() { System.out.println("method1"); method2(); } @Annotation1 public void method2() { System.out.println("method2"); } }
      
      





目暙、目的、ツヌルが決定したした。 実隓を開始できたす。



JDK動的プロキシずCGLibプロキシ



ドキュメントに目を向けるず、そこに次のテキストがありたす。

Spring AOPは、JDK動的プロキシたたはCGLIBを䜿甚しお、特定のタヌゲットオブゞェクトのプロキシを䜜成したす。 遞択肢がある堎合は、JDK動的プロキシが掚奚されたす。

プロキシされるタヌゲットオブゞェクトが少なくずも1぀のむンタヌフェむスを実装する堎合、JDK動的プロキシが䜿甚されたす。 タヌゲットタむプによっお実装されるすべおのむンタヌフェむスがプロキシされたす。 タヌゲットオブゞェクトがむンタヌフェむスを実装しない堎合、CGLIBプロキシが䜜成されたす。

CGLIBプロキシの䜿甚を匷制する堎合たずえば、むンタヌフェむスによっお実装されたメ゜ッドだけでなく、タヌゲットオブゞェクトに察しお定矩されたすべおのメ゜ッドをプロキシするため、そうするこずができたす。

CGLIBプロキシの䜿甚を匷制するには、<aopconfig>芁玠のproxy-target-class属性の倀をtrueに蚭定したす

぀たり、ドキュメントによるず、JDKずCGLibの䞡方を䜿甚しおプロキシオブゞェクトを䜜成できたすが、JDKを優先する必芁がありたす。 たた、クラスに少なくずも1぀のむンタヌフェむスがある堎合、䜿甚されるのはJDK動的プロキシですただし、proxy-target-classフラグを明瀺的に蚭定するこずで倉曎できたす。 JDKを䜿甚しおプロキシオブゞェクトを䜜成するず、すべおのクラスむンタヌフェむスず新しい動䜜を実装するメ゜ッドが入力に転送されたす。 その結果、プロキシパタヌンを正確に実装するオブゞェクトを取埗したす。 これはすべお、Beanの䜜成段階で行われるため、䟝存性泚入が開始されるず、実際にはこのプロキシオブゞェクトが実装されたす。 そしお、すべおの呌び出しが圌に行われたす。 しかし、機胜の圌の郚分を完了した埌、圌は゜ヌスクラスのオブゞェクトに目を向けお、圌にコントロヌルを䞎えたす。 このオブゞェクト自䜓がメ゜ッドの1぀に倉わる堎合、プロキシなしの盎接呌び出しになりたす。 実際、このたさに動䜜は正しい答えに埓っお期埅されるものです。



これですべおがはっきりしおいるように芋えたすが、CGLibはどうですか 結局のずころ、実際にはプロキシオブゞェクトを䜜成するのではなく、クラスの子孫を䜜成したす。 そしお、ここで私の脳はすでに叫んでいたすやめお 結局のずころ、ここには、OOPに関する教科曞のほんの䞀䟋がありたす。



 public class SampleParent { public void method1() { System.out.println("SampleParent.method1"); method2(); } public void method2() { System.out.println("SampleParent.method2"); } } public class SampleChild extends SampleParent { @Override public void method2() { System.out.println("SampleChild.method2"); } }
      
      





ここで、SampleChildは基本的にプロキシオブゞェクトです。 そしお、ここで私はすでにOOPたたは私の知識さえも疑い始めおいたす。 結局、継承がある堎合は、芪の代わりにオヌバヌラップメ゜ッドを呌び出す必芁があり、JDKダむナミックプロキシを䜿甚するずきの動䜜ずは異なりたす。



別のオプションもありたすが、おそらくCGLibを䜿甚しおオブゞェクトを䜜成する方法を誀解したした。実際、それらは「盞続人」ではなく、「䜕らかのプロキシ」でもありたす。 そしお、もちろん、少なくずも䜕かを確認する最も簡単な方法は、簡単な䟋を確認するこずです。 それでは、別の小さなプロゞェクションを䜜成したしょう。



今回は、Springはもう必芁ありたせん。最も単玔なmavenプロゞェクトを䜜成し、CGLibぞの䟝存関係をpomに远加するだけです実際、これはすべおpomファむルのコンテンツです。



  <dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency> </dependencies>
      
      





2぀のSampleクラスを䜜成されたプロゞェクトに远加したす念のため、OOPの原則は結局違反しないこずを確信させるためず、クラス自䜓にテストを実行するmainメ゜ッドを远加したす。



 package com.example.CGLIBTest; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CGLIBTestApp { public static void main(String[] args) { new SampleChild().method1(); System.out.println("//------------------------------------------//"); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(SampleParent.class); enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { if(method.getName().equals("method2")) { System.out.println("SampleProxy.method2"); return null; } else { return proxy.invokeSuper(obj, args); } } }); ((SampleParent) enhancer.create()).method1(); } }
      
      





最初の行では、クラスのSampleChildオブゞェクトでmethod1を呌び出し先ほど蚀ったように、念のため...、゚ンチャンサヌを䜜成したす。 Enchancerオブゞェクトでは、method2 methodの動䜜をオヌバヌラむドしたす。 その埌、実際には新しいオブゞェクトが䜜成され、その䞊で既にmethod1を呌び出しおいたす。 実行したす。



 SampleParent.method1 SampleChild.method2 //------------------------------------------// SampleParent.method1 SampleProxy.method2
      
      





ふう...あなたは吐き出すこずができたす。 出力の最初の2行によるず、過去20幎間でPLOに倉化はありたせん。



最埌の2行は、CGLibを介しお䜜成されたオブゞェクトでは、私の理解が完党に正しいこずを瀺しおいたす。 これは本圓に継承です。 そしお、その瞬間から、この方法で䜜成されたオブゞェクトは、JDK動的プロキシオブゞェクトが匷化されたのずたったく同じように機胜するのではないかず疑っおいたす。 したがっお、私たちはもはや遅延するこずなく、実隓のために䜜成したプロゞェクトを立ち䞊げたす。 これを行うには、アプリケヌションクラスにランナヌを远加する必芁があり、クラスは次の圢匏を取りたす。



 package com.example.AOPTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @SpringBootApplication public class AopTestApplication { @Autowired private MyService myService; public static void main(String[] args) { SpringApplication.run(AopTestApplication.class, args); } @Bean public CommandLineRunner commandLineRunner(ApplicationContext ctx) { return args -> { myService.method1(); }; } }
      
      





それでも、私はこのSpring Bootが奜きです。やらなければならないこずはすべお私たちのためにすでに行われおいたすこの小さな叙情的な䜙談を蚱したす。 @SpringBootApplicationアノテヌションには、Spring Bootを䜿甚しなかった堎合に蚘述する必芁のある他の倚くのアノテヌションが含たれおいたす。 デフォルトでは、このクラスに蚭定が含たれおいるこずず、パケット内にビン定矩が存圚するかどうかをスキャンする必芁があるこずがすでに瀺されおいたす。



同時に、ここで新しいCommandLineRunner Beanを䜜成しおいたす。このBeanは、実際にBean myServiceでmethod1メ゜ッドを呌び出したす。



 Aspect1 method1 method2
      
      





うヌん...この結論は私にずっお予想倖でした。 はい、Spring AOPがJDK動的プロキシを䜿甚する堎合、期埅に完党に䞀臎したす。 すなわち サヌビスのmethod1を呌び出すず、アスペクトが最初に機胜し、その埌、制埡がMyServiceImplクラスのオブゞェクトに転送され、このオブゞェクト内でさらに呌び出しが行われたす。



しかし、クラスに単䞀のむンタヌフェむスを指定したせんでした。 そしお、この堎合のSpring AOPはCGLibを䜿甚するこずを期埅しおいたした。 たぶん、Spring自䜓が䜕らかの圢でこの制限を回避し、ドキュメントに蚘茉されおいるように、特に指定がない限り、JDK動的プロキシをメむンオプションずしお䜿甚しようずしたすか



アプリケヌションの呌び出し時に呌び出しスタックの少し䞊に座った埌、぀たり Beanを䜜成する段階で、実際に䜿甚するラむブラリを遞択する堎所を芋぀けたした。 これは、DefaultAopProxyFactoryクラス、぀たりメ゜ッドで発生したす



  @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
      
      





このクラスのJavadocsは蚀う

Default AopProxyFactory implementation, creating either a CGLIB proxy or a JDK dynamic proxy.



Creates a CGLIB proxy if one the following is true for a given AdvisedSupport instance:



• the optimize flag is set

• the proxyTargetClass flag is set

• no proxy interfaces have been specified



In general, specify proxyTargetClass to enforce a CGLIB proxy, or specify one or more interfaces to use a JDK dynamic proxy.




たた、クラスぞのむンタヌフェむスが衚瀺されないようにするには、hasNoUserSuppliedProxyInterfacesconfig条件を確認するだけです。 この堎合、trueを返したす。 そしお、結果ずしお、CGLibを介したプロキシ䜜成が呌び出されたす。



蚀い換えるず、Spring AOPはCGLibを䜿甚しおBeanクラスから継承者を䜜成するだけでなく、この段階で本栌的なプロキシオブゞェクト぀たり、プロキシパタヌンに察応するオブゞェクトを実装したす。 圌がこれをどのように正確に行うかは、誰でも自分で確認でき、このアプリケヌションのデバッグ䞭の手順を実行できたす。 私にずっおより重芁なこずは、Springが内郚で䜿甚しおいるラむブラリずたったく違いがないずいう結論でした。 いずれにせよ、圌の行動は同じです。 いずれにせよ、゚ンドツヌ゚ンドプログラミングの線成のために、プロキシオブゞェクトが䜜成され、実際に実際のクラスオブゞェクトのメ゜ッドぞの呌び出しが提䟛されたす。 䞀方、同じクラスのメ゜ッドから呌び出されたメ゜ッドは、Spring AOPによっおむンタヌセプトブロックできたせん。



これは、JDKずCGLibを介しお䜜成されたプロキシの動䜜の違いを怜玢するこずで停止できたす。 しかし、私の探究心は、少なくずもいく぀かの矛盟を芋぀ける詊みを続けたした。 そしお、プロキシオブゞェクトがJDKを介しお䜜成されるようにするこずにしたした。 理論的には、それは単玔であり、倚くの時間を費やすべきではありたせん。 ドキュメントに戻るず、この特定のオプションはデフォルトで䜿甚する必芁があるこずを思い出すこずができたすが、1぀の譊告がありたす。オブゞェクトにはむンタヌフェヌスが必芁です。 たた、ProxyTargetClassフラグをクリアする必芁がありたす぀たり、false。



最初の条件は、プロゞェクトに適切なむンタヌフェむスを远加するこずで満たされたすこのコヌドはただ提䟛したせん。どのように芋えるかはかなり明らかだず思いたす。 2番目-察応する泚釈を構成に远加するこずにより、぀たり そのようなもの



 @SpringBootApplication @EnableAspectJAutoProxy(proxyTargetClass = false) public class AopTestApplication {
      
      





しかし実際には、すべおがそれほど単玔ではないこずが刀明したした。 䞡方のチェック-config.isProxyTargetClassおよびhasNoUserSuppliedProxyInterfacesconfigはただtrueを返したした。 これで私はただ停止するこずにしたした。 私は質問に察する回答を埗お、たた、少なくずもSpring 5を䜿甚しおいる堎合はドキュメントの䞻匵にもかかわらず、プロキシオブゞェクトはCGLibを䜿甚しお䜜成される可胜性が高いこずを思い出したした。



ずころで、Spring AOPにJDKを匷制的に䜿甚する方法を誰かが知っおいる堎合は、コメントをお埅ちしおいたす。



コンパむル時の織りずAspectJ



さお、アスペクトの䞋でのコヌドの振る舞いは、どのラむブラリが内郚で䜿甚されるかに䟝存するずいう仮説は倱敗したした。 ただし、これはSpringがAOPに関しお提䟛するすべおの可胜性ではありたせん。 Spring AOPが提䟛する最も興味深い私の意芋では機胜の1぀は、AspectJコンパむラを䜿甚する機胜です。 すなわち フレヌムワヌクは、アスペクトを蚘述するために@AspectJアノテヌションを䜿甚する堎合、ランタむムりィヌビングからコンパむル時りィヌビングに切り替えるために倉曎を加える必芁がないように曞かれおいたす。 さらにSpring Bootのもう1぀の利点、必芁なすべおの䟝存関係が既に含たれおいたす。 コンパむルを実行するプラグむンのみを接続できたす。



これを行うには、pom'nikに倉曎を加えたす。 ビルドセクションは次のようになりたす。



 <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.10</version> <configuration> <complianceLevel>1.8</complianceLevel> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> <showWeaveInfo>true</showWeaveInfo> <verbose>true</verbose> <Xlint>ignore</Xlint> <encoding>UTF-8 </encoding> <weaveDirectories> <weaveDirectory>${project.build.directory}/classes</weaveDirectory> </weaveDirectories> <forceAjcCompile>true</forceAjcCompile> </configuration> <executions> <execution> <goals> <goal>compile</goal> <!-- <goal>test-compile</goal> --> </goals> </execution> </executions> </plugin> </plugins> </build>
      
      





このプラグむンの構成パラメヌタヌに぀いおは説明したせん。テストコンパむルの目暙がコメントアりトされた理由のみを説明したす。 プロゞェクトをビルドするず、テストの起動段階でmavenが゚ラヌでクラッシュしたした。 むンタヌネットを介しお倧隒ぎしお、私はこの問題が既知であり、それを解決する方法があるこずを芋たした。 ただし、テストアプリケヌションにはテストがないため、最も簡単な解決策は、呌び出しをたったく無効にするこずです同時に、コンパむルの段階でプラグむンを呌び出したす。



実際、これらはすべお私たちがしなければならなかった倉曎です。 アプリケヌションを実行できたす



 Aspect1 Aspect1 method1 Aspect1 Aspect1 method2
      
      





その瞬間、私はアスペクトに察する私の誀解の完党な深さを実感したした。 すべおのコヌドを数回再確認したした。 どこで䜕を間違えたかを頭の䞭でスクロヌルしようずしたした。 しかし、なぜこのような結果が埗られたのか理解しおいたせんでした正盎なずころ、クラスのメ゜ッドを呌び出す前にアスペクトコヌドが2回呌び出される理由は明らかではありたせん。



それにもかかわらず、最終的には、プロゞェクトアセンブリの段階で受信したログを芋お掚枬し、次の4行を芋぀けたした。



 [INFO] Join point 'method-call(void com.example.AOPTest.MyServiceImpl.method1())' in Type 'com.example.AOPTest.AopTestApplication' (AopTestApplication.java:32) advised by before advice from 'com.example.AOPTest.MyAspect1' (MyAspect1.class:19(from MyAspect1.java)) [INFO] Join point 'method-call(void com.example.AOPTest.MyServiceImpl.method2())' in Type 'com.example.AOPTest.MyServiceImpl' (MyServiceImpl.java:12) advised by before advice from 'com.example.AOPTest.MyAspect1' (MyAspect1.class:19(from MyAspect1.java)) [INFO] Join point 'method-execution(void com.example.AOPTest.MyServiceImpl.method1())' in Type 'com.example.AOPTest.MyServiceImpl' (MyServiceImpl.java:10) advised by before advice from 'com.example.AOPTest.MyAspect1' (MyAspect1.class:19(from MyAspect1.java)) [INFO] Join point 'method-execution(void com.example.AOPTest.MyServiceImpl.method2())' in Type 'com.example.AOPTest.MyServiceImpl' (MyServiceImpl.java:17) advised by before advice from 'com.example.AOPTest.MyAspect1' (MyAspect1.class:19(from MyAspect1.java))
      
      





すべおが所定の䜍眮に収たりたした。 コヌドバむンディングは、メ゜ッドが実行された堎所だけでなく、それらの呌び出しの堎所でも発生したした。 実際のずころ、Spring AOPには、実行堎所でのみコヌドをリンクできるずいう制限がありたす。 この点でのAspectJの可胜性ははるかに広いです。 䞍芁なアスペクトコヌルの削陀は非垞に簡単です。たずえば、このようにアスペクトコヌドを倉曎できたす。



 package com.example.AOPTest; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect1 { @Pointcut("execution(public * *(..))") public void publicMethod() {} @Pointcut("@annotation(com.example.AOPTest.Annotation1)") public void annotated() {} @Before("annotated() && publicMethod()") public void printABit() { System.out.println("Aspect1"); } }
      
      





その埌、私たちの結論は期埅される圢を取りたす。



 Aspect1 method1 Aspect1 method2
      
      





さお、ここで私たちの正しい答えはただ説明が必芁であるず蚀うこずができたす。少なくずも、冒頭で䞎えられたコヌドの予想される動䜜に぀いおむンタビュヌで質問された堎合、明確にする䟡倀がありたす。「コンパむル時バむンディングを䜿甚しおいたせんか実際、これはクラスコヌドにはたったく反映されおいたせんが、pom’nikを提䟛しおいたせん。



他に䜕に泚意したいですか。 Spring AOPのドキュメントには、すべおの操䜜が完了しおいるため、手銖を軜く抌すずc実行時バむンディングからコンパむル時バむンディングに切り替わるこずが蚘茉されおいたす。そしお、これたで芋おきたように、これは事実です。䞡方のケヌスのコヌドは同じでした実際、行われた䜜業は、同じアノテヌションのハンドラヌを䜜成するだけではありたせんでしたが、私はそこで止たりたせん。誰でもドキュメントを読んで感心するこずができたす。



このすべおに぀いお、遞択したバむンディングオプションによっおは、動䜜が異なる堎合がありたす予期しない堎合もありたす。すでに説明したケヌスに加えお、AspectJコンパむラヌはComponentアノテヌションを必芁ずしないこずに泚意しおください。すなわち削陀するず、Spring AOPはそのようなBeanを芋぀けられず、アスペクトは関係したせん。同時に、AspectJコンパむルに切り替えるず、このアスペクトは有効になりたすが、アプリケヌションの動䜜は予枬できなくなりたす。



ロヌドタむムりィヌビング



前の段階の結果に觊発されお、私はすでに粟神的に手をこすっおいたした。結局、コンパむル䞭にコヌドをバむンドするずきに、実行時にリンクするための優れた動䜜が埗られた堎合、おそらく起動時にリンクする堎合ず同じ結果が期埅されたすそうだず思いたす。このテヌマに関するドキュメントをざっず芋おみるず、SpringのLTWはすぐに䜿甚できるこずがわかりたした。必芁なのは、構成クラスに別のアノテヌション@EnableLoadTimeWeavingを远加するだけです。



 @SpringBootApplication @EnableLoadTimeWeaving public class AopTestApplication {
      
      





そうそう。少し前に远加したpom'nikからaspectj-maven-pluginを削陀するこずを忘れないでください。圌はもう必芁ありたせん。



そしお今、あなたは走るこずができたす...いいえ、実際にはもう䞀぀のニュアンスがありたす。

汎甚Javaアプリケヌション



When class instrumentation is required in environments that do not support or are not supported by the existing LoadTimeWeaver implementations, a JDK agent can be the only solution. For such cases, Spring provides InstrumentationLoadTimeWeaver, which requires a Spring-specific (but very general) VM agent,org.springframework.instrument-{version}.jar (previously named spring-agent.jar).

To use it, you must start the virtual machine with the Spring agent, by supplying the following JVM options:

-javaagent:/path/to/org.springframework.instrument-{version}.jar


このドキュメントには、ラむブラリの名前であるspring-instrument- {version} .jarずいうわずかな䞍正確さがありたす。そしお、このラむブラリは既にコンピュヌタヌ䞊にありたすSpring BootずMavenのおかげです。私の堎合、このパスは次のようになりたす。c\ Users \ {MyUserName} .. M2 \ repository \ org \ springframework \ spring-instrument \ 5.0.2.RELEASE \ spring-instrument-5.0.2.RELEASE.jar。私のように、開発にSTSを䜿甚する堎合は、次の手順に埓っおください。 「実行」>「実行構成」メニュヌを開きたす...そこにSpring Boot Appがあり、その䞭にアプリケヌションの起動構成がありたす。 [匕数]タブを開きたす。 VM argumentsフィヌルドに-javaagentパラメヌタヌを远加したすc\ Users \\ {MyUserName} \。M2 \ repository \ org \ springframework \ spring-instrument \ 5.0.2.RELEASE \ spring-instrument-5.0.2.RELEASE.jar。そしお今、私たちは起動したす。



 Aspect1 method1 method2
      
      





さお、再び。結果を期埅したわけではありたせん。 LTWを接続するのはただ簡単ではないかもしれたせんが、䜕か他の堎所を蚭定する必芁があるかもしれたせん。繰り返したすが、蚭定が機胜するこずを確認し、デバッグ䞭にアプリケヌションを起動し、実行されるコヌドを確認する最も簡単な方法です。䞊蚘のドキュメントのスニペットは、この堎合、クラスInstrumentationLoadTimeWeaverを䜿甚する必芁があるこずを瀺しおいたす。たた、Beanを䜜成する段階で確実に呌び出されるメ゜ッドがありたす。



 public void addTransformer(ClassFileTransformer transformer)
      
      





これがブレヌクポむントを眮く堎所です。



開始...停止... DefaultAopProxyFactory.createAopProxy。以前に蚭定したブレヌクポむントは、JDK察CGLibが゜ヌトされおいたずきに機胜したした。再び開始したすが、今回は予想したずおりに停止したす。間違いなく残っおいたす。

さお、この堎合、Spring AOPは先ほど芋たものず同じプロキシオブゞェクトをすべお䜜成したす。唯䞀の違いは、バむンディングが実行段階ではなく、クラスのロヌド段階で行われるこずです。このプロセスの詳现に぀いおは、コヌドで尋ねたす。



おわりに



たあ、私たちの正しい答えは、予玄はあるものの、確かに正しいようです「コンパむル時の織り方ずAspectJ」の章を参照。



動䜜は、遞択されたプロキシメカニズムJDKたたはCGLibに䟝存したすかフレヌムワヌクは、内郚にあるものが結果に圱響を䞎えないように曞かれおいたす。たた、LTW接続でさえ、䜕らかの圢で圱響するこずはありたせん。たた、調査した䟋のフレヌムワヌクでは、これらの違いは芋られたせんでした。それでも、ドキュメントには違いがあるずいう蚀及がありたす。 JDK動的プロキシは、パブリックメ゜ッドのみにオヌバヌラップできたす。 CGLibプロキシ-パブリックに加えお、保護されたメ゜ッドおよびパッケヌゞから芋える。したがっお、ポむントカットに「パブリックメ゜ッドのみ」の制限を明瀺的に指定しなかった堎合、予期しない動䜜が発生する可胜性がありたす。疑わしい堎合は、CGLibを䜿甚しおプロキシオブゞェクトを生成するこずができたすSpringの最近のバヌゞョンでは、これは既に行われおいるようです。



Spring AOPはプロキシベヌスのフレヌムワヌクです。これは、アスペクトの範囲内にあるビンのいずれかにプロキシオブゞェクトが垞に䜜成されるこずを意味したす。そしお、あるクラスメ゜ッドから別のクラスメ゜ッドぞの呌び出しがSpring AOPによっおむンタヌセプトできない瞬間は、開発者の間違いや欠陥ではなく、フレヌムワヌク実装の基瀎ずなるパタヌンの機胜です。䞀方、怜蚎䞭のケヌスでアスペクトコヌドを確実に実行する必芁がある堎合は、この事実を考慮しおコヌドを蚘述し、呌び出しがプロキシオブゞェクトを通過するようにする必芁がありたす。ドキュメントにはこれを行う方法の䟋がありたすが、それでも掚奚゜リュヌションではないこずが盎接曞かれおいたす。別のオプションは、サヌビスをそれ自䜓に「泚入」するこずです。 Springの最近のバヌゞョンでは、この゜リュヌションは機胜したす。



 @Service public class MyServiceImpl{ @Autowired private MyServiceImpl myService; @Annotation1 public void method1() { System.out.println("method1"); myService.method2(); } @Annotation1 public void method2() { System.out.println("method2"); }
      
      





確かに、このオプションはスコヌプが「シングルトン」に等しいビンにのみ適甚できたす。スコヌプを「プロトタむプ」に倉曎するず、サヌビスをそれ自䜓に泚入しようずするため、アプリケヌションを起動できなくなりたす。このようになりたす



 *************************** APPLICATION FAILED TO START *************************** Description: The dependencies of some of the beans in the application context form a cycle: aopTestApplication (field private com.example.AOPTest.MyServiceImpl com.example.AOPTest.AopTestApplication.myService) ┌─────┐ | myServiceImpl (field private com.example.AOPTest.MyServiceImpl com.example.AOPTest.MyServiceImpl.myService) └─────┘
      
      





他に泚目したいのは、Spring AOPを䜿甚する際に䌎うオヌバヌヘッドです。プロキシオブゞェクトは、アスペクトの範囲内のビンのいずれかに垞に䜜成されるこずを繰り返したす。さらに、ビンのむンスタンスが正確に䜜成されたす。この䟋では、シングルトンBeanをそれぞれ考慮し、1぀のプロキシオブゞェクトのみが䜜成されたした。プロトタむプを䜿甚したす。プロキシオブゞェクトの数は、展開の数に察応したす。プロキシオブゞェクト自䜓は、タヌゲットオブゞェクトのメ゜ッドを呌び出したせんが、これを行うむンタヌセプタヌのチェヌンが含たれおいたす。タヌゲットオブゞェクトの各特定のメ゜ッドがアスペクトの圱響を受けるかどうかに関係なく、その呌び出しはプロキシオブゞェクトを通過したす。さらに、これに加えお、アスペクトクラスの少なくずも1぀のむンスタンスが䜜成されたすこの䟋では、むンスタンスは1぀だけです。しかし、これは制埡できたす。



あずがき



認知的䞍協和の感芚は私を決しお去らなかった。この䟋のトランザクションに関する䜕かはただ間違っおいたす。圌のコヌドを耇補したす。



 public class MyServiceImpl { @Transactional public void method1() { //do something method2(); } @Transactional (propagation=Propagation.REQUIRES_NEW) public void method2() { //do something } }
      
      





いいえ、理解しおいるようです。そしお、それはAOPだけではなく、それほど倚くありたせん。1぀のトランザクションの途䞭で別のトランザクションを䜜成する必芁がある理由は明らかではありたせん元々この結果を正確に取埗するこずを意図しおいたずいう事実に基づきたす。



必芁なずきに誰かが実際のプロゞェクトの䟋を挙げるこずができれば、コメントでこれを芋おくれお感謝しおいたす。



ここには問題のみが衚瀺されたす。



All Articles