I had a chance here the other day to attend the next interview. And they asked me there is such a question. What actually will be executed (from the point of view of transactions) if to cause method1 ()?
public class MyServiceImpl { @Transactional public void method1() {
Well, we are all smart, we read the documentation, or at least we watch the video of Yevgeny Borisov’s speeches. Accordingly, we know the correct * answer (the correct * is the one who asks those who ask to hear from us). And it should sound like this.
“
Due to the fact that Spring AOP is used to support transactions through annotations, at the time of calling method1 (), the object's proxy method is actually called. A new transaction is created and then method1 () is called on the MyServiceImpl class. And when we call method2 () from method1 (), there is no appeal to the proxy, the method of our class is called right away and, accordingly, no new transactions will be created . ”
')
But you know how it happens, and the answer has been known for a long time. And apply this knowledge regularly. But what if all of a sudden ... and suddenly you think about it: “
Wait a minute, because if we use Spring AOP, then there can be created a proxy through JDK, or with CGLIB; and it is also possible that CTW or LTW is hooked up. And that such an answer will always be true? ".
Well then: interesting? Need to check.
In fact, I was not interested in how transactions would be created, but in the very statement that Spring AOP always creates proxy objects, and these proxy objects have the behavior described above. Obviously, if the JDK dynamic proxy is used to create wrappers, this statement should be true. Indeed, in this case, objects are created on the basis of interfaces. Such an object will fully comply with the Proxy pattern and everything looks quite logical. But CGLib uses a different approach, it creates classes of heirs. And then doubts are beginning to creep in whether the behavior will be identical. Everything becomes even more interesting when we decide to use external binding tools, i.e. CTW (compile-time weaving) and LTW (load-time weaving).
To begin with, I will add here a couple of definitions from the Spring AOP documentation, which are most interesting in the context of what we are considering (all the other definitions themselves can be found in the documentation itself, for example,
here )
- AOP proxy: an order created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy will be a JDK dynamic proxy or a CGLIB proxy.
- Weaving: This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP framework, like other pure Java AOP frameworks, performs weaving at runtime.
Now we will create the simplest project on which we will conduct experiments. For this we use Spring Boot. Since I use STS for development, I will describe the steps for this IDE. But, by and large, everything will be about the same for other tools.
Run the wizard for creating the Spring Boot project: File> New> Spring Starter Project. And fill in the form:
- Name : AOPTest;
- Type : Maven;
- Packaging : Jar;
- Java version : 8;
- Language : Java;
- Group : com.example.AOPTest;
- Artifact : AOPTest;
- Package : com.example.AOPTest.
The rest to taste. Click the Next button, and fill out again:
- Spring Boot Version : 2.0.0.M7 (and why not? This is the last available version at the time of this writing). What Spring Boot actually does is that in its minimal form it includes all the necessary dependencies and definitions. We only need to specify what we will use from Spring.
- At the same step, in the list of available dependencies, we find Aspects (you can use the filter) and add it.
Click Finish. Our application is ready. The created pom'nik should look something like this.
<?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/> </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>
In fact, due to the fact that we are using the milestone version, the code will be slightly larger, links to the repositories will be added.
An application class will also be generated.
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); } }
Our annotation (since, as I said, we are not interested in transactions, but how this situation will be handled, we create our own annotation):
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 { }
And aspect
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"); } }
Actually created an aspect that will bind to the methods that have an annotation @ Annotation1. Before executing such methods, the text “Aspect1” will be displayed in the console.
Note that the class itself is also annotated as a
Component . This is necessary so that Spring can find this class and create a bin based on it.
And now you can add our class.
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"); } }
With goals, objectives and tools decided. You can begin to experiment.
JDK dynamic proxy vs CGLib proxy
If you refer to the documentation, you can find the following text there.
Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred).
JDK dynamic proxy will be used. All types of interfaces are implemented. The CGLIB proxy will be created.
For example, it can be used to make it easier for you to use it.
CGLIB proxies set the value of the proxy target attribute of the <aop: config> element to true
That is, according to the documentation, both JDK and CGLib can be used to create proxy objects, but preference should be given to JDK. And, if a class has at least one interface, then the JDK dynamic proxy will be used (although this can be changed by explicitly setting the proxy-target-class flag). When creating a proxy object using JDK, all interfaces of the class and the method for implementing new behavior are passed to the input. As a result, we obtain an object that exactly implements the Proxy pattern. All this happens at the stage of creating bins, so when the introduction of dependencies begins, in reality, this very proxy object will be implemented. And all calls will be made to him. But after completing its part of the functional, it will access the object of the original class and transfer control to it. If this object itself is addressed to one of its methods, then it will be a direct call without any proxies. Actually, it is this behavior that is expected according to the correct * answer.
With this, everything seems to be clear, but what about CGLib? After all, he actually creates not a proxy object, but a class heir. And here my brain is just shouting: STOP! After all, here we have, well, just an example from an OOP textbook.
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"); } }
Where SampleChild is, in fact, our proxy object. And here I am already beginning to doubt even the PLO (or in my knowledge about it). After all, if we have inheritance, then the overlapped method should be called instead of the parent one, and then the behavior will be different from what we have when using JDK dynamic proxy.
Although there is another option, maybe I misunderstood how objects are created using CGLib and, in fact, they are “not exactly heirs”, but also “some kind of proxy”. And, of course, the easiest way to be sure of at least something is to check it with a simple example. So let's create another small project.
This time, we no longer need Spring, just create the simplest maven project and add a CGLib dependency to pom (in fact, this is the entire content of our pom-file).
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency> </dependencies>
Let's add our two Sample classes to the created project (well, just in case, to convince ourselves that the principles of the OOP are unshakeable) and the class itself with the main () method, in which we will perform our tests.
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(); } }
The first line is to call method1 () on the SampleChild object of the class (as already said, well, just to be sure ...) and then create the Enchancer. In the Enchancer object, we override the behavior of the method method2 (). After that, in fact, a new object is created and already we call method1 () on it. And run.
SampleParent.method1 SampleChild.method2
Fuh ... You can breathe out. If you believe the first two lines of output, over the past 20 years, nothing has changed in the PLO.
The last two lines say that with the objects created through CGLib, my understanding was absolutely correct. This is really an inheritance. And from this point on, my doubts about the fact that the object created in this way will work exactly the same as the JDK dynamic proxy object only intensified. Therefore, we no longer postpone, but run our project, which we created for experiments. To do this, we will need to add a runner in the application class, and our class will take the following form.
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(); }; } }
I still like this Spring Boot, everything that needs to be done has already been done for us (sorry for this little lyrical digression). The @SpringBootApplication annotation includes many other annotations that you would have to write if we didn’t use Spring Boot. By default, it is already indicated that this class contains the configuration, and also that it is necessary to scan the packages for the presence of definitions of bins.
At the same time, right here we are creating a new CommandLineRunner bean, which itself will execute the method1 () method call on our myService bean.
Aspect1 method1 method2
Hmm ... This conclusion was unexpected for me. Yes, it fully meets expectations, if Spring AOP uses the JDK dynamic proxy. Those. when calling method1 () of our service, the aspect was first worked out, after which control was transferred to an object of the class MyServiceImpl and further calls would be made within this object.
But we did not specify a single interface for the class. And I expected Spring AOP to use CGLib in this case. Can Spring somehow bypass this limitation and, as written in the documentation, try to use the JDK dynamic proxy as the main option, unless explicitly stated otherwise?
After sitting a little above the call stack at the moment of raising the application, i.e. at the stage of creating bins, I found a place where the choice is actually made which library to use. This happens in the DefaultAopProxyFactory class, namely in the method
@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); } }
In Javadocs to this class is written
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.
And in order to make sure that no interfaces to our class have appeared, it suffices to check the hasNoUserSuppliedProxyInterfaces (config) condition. In our case, it returns true. And, as a result, the creation of a proxy through CGLib is called.
In other words, Spring AOP not only uses CGLib to create heirs from the classes of beans, but implements a full-fledged proxy object at this stage (ie, an object corresponding to the Proxy pattern). How exactly he does it, everyone can see for himself, walking through the steps under debugging in this application. Far more important for me was the conclusion that absolutely no matter which library Spring uses under the hood. In any case, his behavior will be the same. In any case, a proxy object will be created for the organization of end-to-end programming, which itself will provide calls to the methods of the real class object. On the other hand, if any methods are called from the methods of the same class, then intercepting (blocking) with Spring AOP means they cannot be obtained.
We could stop at this with a search for differences in the behavior of proxies created through the JDK and CGLib. But my inquisitive mind continued its attempts to find at least some inconsistency. And I decided to ensure that the proxy object will be created through the JDK. Theoretically, this should be simple and not take a lot of time. Returning to the documentation, you can remember that this option should be used by default, with the only caveat: the object must have interfaces. Also, the ProxyTargetClass flag must be cleared (i.e., false).
The first condition is fulfilled by adding the appropriate interface to the project (I will not give this code anymore, I think it’s pretty obvious how it will look like). The second is by adding the corresponding annotation to the configuration, i.e. something like this
@SpringBootApplication @EnableAspectJAutoProxy(proxyTargetClass = false) public class AopTestApplication {
But in fact, everything was not so simple. Both checks — config.isProxyTargetClass () and hasNoUserSuppliedProxyInterfaces (config) still returned true. I decided to stop at this. I received an answer to my question, and also made a note in my memory that (at least when using Spring 5), despite the statements of the documentation, proxies are more likely to be created using CGLib.
By the way, if someone knows how to force Spring AOP to use the JDK, I will wait for your comments.
Compile-time weaving and AspectJ
Well, our hypothesis that the behavior of the code under the aspects will depend on which library is used under the hood, failed. However, this is not all the features that Spring provides in terms of AOP. One of the most interesting (in my opinion) features that Spring AOP provides us with is the ability to use the AspectJ compiler. Those. the framework is written in such a way that if we use the @AspectJ annotations to describe aspects, we will not have to make any changes to move from runtime weaving to compile-time weaving. In addition (another advantage of Spring Boot) all the necessary dependencies are already included. We just need to connect the plugin, which will compile.
To do this, make changes to our pom'nik. Now the build section will look like this.
<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> </goals> </execution> </executions> </plugin> </plugins> </build>
I will not dwell on the configuration parameters of this plug-in, I will only explain why I commented out the purpose of test-compile. When building a project, maven fell with an error during the test run. Rummaging through the Internet, I saw that this problem is known, and there are ways to solve it. But, since, in our test application, the tests seem to be absent, the simplest solution was to simply disable their call completely (and at the same time call the plug-in at the compilation stage).
Actually, these are all the changes we needed to make. We can run our application
Aspect1 Aspect1 method1 Aspect1 Aspect1 method2
At this point, I realized the whole depth of my misunderstanding of aspects. I rechecked the whole code several times. I tried to scroll in my mind, where and what I did wrong. But I did not realize why I got such a result (no, well, honestly, it’s not obvious why the aspect code is called twice before each call of the method of our class).
In the end, I still guessed to see the logs received during the project build phase and found the next 4 lines there.
[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))
Everything fell into place. Code binding occurred not only at the place where the methods were executed, but also at the place of their calls. The fact is that Spring AOP has a restriction that allows you to link the code only at the place of execution. The possibilities of AspectJ in this regard are much wider. Removing unnecessary aspect calls is quite simple, for example, you can modify the aspect code like this.
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"); } }
Then our conclusion will take the expected form.
Aspect1 method1 Aspect1 method2
Well, here we can already say that our correct * answer still needs clarification. At a minimum, if you are asked at the interview about the expected behavior of the code given at the beginning, you should clarify: “But aren't we using compile-time binding? Indeed, in the class code this is not reflected in any way, but pom'nik was not provided to us. ”
What else would I like to mention.
The documentation on Spring AOP describes that everything was done, so that with a flick of the wrist, (c) switches from runtime linking to compile-time linking. And as we have seen, this is true. The code for both cases was used the same (in fact, the work that was done much more than just creating handlers for the same annotations, but I will not dwell on this, those who wish can read the documentation and be impressed on their own).
With all this, depending on the choice of binding, the behavior may be different (and even unexpected). Except for those cases that have already been considered, I want to note that the AspectJ compiler does not need the Component annotation . Those.
if we remove it, then Spring AOP will not find such a bean and the aspect will not be involved. At the same time, if we decide to switch to AspectJ compilation, this aspect will be valid, but the behavior of the application is unpredictable.
Load-time weaving
Inspired by the result of the previous stage, I mentally rubbed my hands. After all, if, when linking the code during compilation, we got excellent behavior, the one we had for binding during execution, then we probably expect about the same result if we connect during loading (well, I thought so). Having quickly read the documentation about this, I found out that LTW in Spring is available out of the box. All you need is just to add another annotation in the configuration class: @EnableLoadTimeWeaving.
@SpringBootApplication @EnableLoadTimeWeaving public class AopTestApplication {
Oh yes.
Do not forget to remove the aspectj-maven-plugin from the pom'nik that we added earlier. We don't need it anymore.
And now you can run ... No, in fact there is another nuance.
Generic Java applications
It is not supported by the existing LoadTimeWeaver implementations, a JDK agent can be used. For such cases, Spring provides InstrumentationLoadTimeWeaver, which requires a Spring-specific agent (previously named spring-agent.jar).
JVM options:
-javaagent: /path/to/org.springframework.instrument- {version} .jar
There is a small inaccuracy in the documentation, the name of the library: spring-instrument- {version} .jar. And this library is already on your computer (thanks to Spring Boot and Maven). In my case, the path to it looks like this: c: \ Users \ {MyUserName} \. M2 \ repository \ org \ springframework \ spring-instrument \ 5.0.2.RELEASE \ spring-instrument-5.0.2.RELEASE.jar . If you, like me, use STS for development, perform the following steps. Open the menu Run> Run Configurations ... We find there Spring Boot App and in it the launch configuration of our application. Open the bookmark Arguments. In the VM arguments: add parameter -javaagent: c: \ Users \ \ {MyUserName} \. M2 \ repository \ org \ springframework \ spring-instrument \ 5.0.2.RELEASE \ spring-instrument-5.0.2.RELEASE.jar . And now we start.
Aspect1 method1 method2
Well, again. I did not expect such a result. Maybe it's still not so easy to connect LTW, maybe something else needs to be set up somewhere? And again, the easiest way to make sure that our settings work, run the application under debugging and see what code is being executed. The above piece of documentation states that in our case the class should be the InstrumentationLoadTimeWeaver class. And there is a method in it that should be called exactly at the stage of creating beans.
public void addTransformer(ClassFileTransformer transformer)
This is where we set the breakpoint.
We start ... We stop ... DefaultAopProxyFactory.createAopProxy (). The breakpoint we set earlier worked when the JDK vs CGLib was parsed. We start again, and this time it stops exactly where it was expected. Doubt no more.
Well, in this case, Spring AOP creates all the same proxy objects that we saw earlier. With the only difference that the binding will now be made not at the stage of execution, but already at the stage of loading classes. For details of this process, please in the code.
Conclusion
Well, it seems that our correct * answer is indeed correct, albeit with a reservation (see chapter "Compile-time weaving and AspectJ").
Does the behavior depend on which proxying mechanism is selected: JDK or CGLib? The framework is written in such a way that the fact that there under the hood does not affect what result we get. And even the LTW connection should not affect this in any way. And within the framework of the example we considered, we did not observe these differences. And yet in the documentation you can find a mention that there are differences. JDK dynamic proxy can override only public methods. CGLib proxy - except public, also protected methods and package-visible. Accordingly, if we have not explicitly specified the restriction “only for public methods” for a slice (pointcut), then we can potentially get unexpected behavior. Well, if you have any doubts, you can force the use of CGLib to generate proxy objects (it seems that in recent versions of Spring this has already been done for us).
Spring AOP is a proxy-based framework. This means that proxy objects will always be created on any of our bin that falls under the aspect. And the moment that calling one class method to another cannot be intercepted by Spring AOP tools - this is not a mistake or a flaw in the developers, but a feature of the pattern underlying the implementation of the framework. On the other hand, if we still need to achieve that in this case the aspect code is executed, then we need to take this fact into account and write the code, so that the calls go through a proxy object. In the documentation there is an example of how this can be done, but even there it is directly stated that this is not a recommended solution. Another option is to “inject” the service to itself. In the latest versions of Spring, this is the solution that will work.
@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"); }
However, this option is applicable only for bins with a scope equal to "singleton". If we change the scope to “prototype”, the application will not be able to rise due to the attempt of the infinite introduction of the service into itself. It will look like this
*************************** 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) └─────┘
What else I would like to draw attention to is the overhead that accompanies us when using Spring AOP. I repeat that there will always be created proxy objects on any of our bin falling under the action of an aspect. And there will be exactly as many of them as the bin instances will be created. In the example we looked at the singleton bin, respectively, only one proxy object was created. We use prototype - and the number of proxy objects will correspond to the number of implementations. The proxy object itself does not make calls to the methods of the target object, it contains a chain of interceptors that do this. Regardless of whether or not each particular method of the target object is affected by an aspect, its call passes through a proxy object. Well, plus at least one instance class of the aspect will be created (in our example it will be only one,but it can be controlled).
Afterword
The feeling of cognitive dissonance never left me. Something in this example with transactions is still wrong. I duplicate his code.
public class MyServiceImpl { @Transactional public void method1() {
Although no, it seems, understood. And it's not only and not so much about AOP. It is not obvious to me why it might be necessary to create another one in the middle of one transaction (assuming that it was originally intended to get exactly this result).
If someone can give an example from real projects, when it was necessary, I would appreciate it in the comments.
I see only problems here.