[JAVA] The essence of AspectJ's mood-why your `@Transactional` is ignored

The Aspect-oriented programming (AOP) library AspectJ is useful for intercepting method calls and inserting arbitrary processing in a JVM environment, and frames. It is often used as a work implementation.

For example, there are quite a few cases where AspectJ (CGLIB proxy [^ cglib]) is used to realize transactions with @Transactional annotation even in the field of Spring Framework [^ aop-impl].

[^ cglib]: Although it is referred to as "CGLIB proxy" in the official Spring documentation, it refers to AspectJ (GCLIB is the name of the code generation library used internally by AspectJ).

[^ aop-impl]: Spring uses by default [java.lang.reflect.Proxy](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/ lang / reflect / Proxy.html) allows AOP only for interface, and there are quite a few cases where AspectJ is used because of its restrictions.

The widely used AspectJ is actually useful, but in projects where it works ** "My @ Transactional is ignored after hours of trial and error ... why ...! " I heard the sad cry of ** many times at various sites.

In Spring, if you read through the chapters of the official document Chapter 6. Aspect Oriented Programming with Spring, you can see the background. I know all the working principles and possible options, but if you're having trouble with ** "I want to move my AOP right now !!" **, see below:

Why is your AOP ignored

1) Methods that cannot be overrided

Symptoms of AOP not working if the following conditions are met:

--Class or method is final --static method

Let's make it possible to override.

2) Does not reference an instance of the wrapper generated by AspectJ

Common patterns:

--Called by this. method () --A method call is being made to the return this; --new your class ()

Why this happens-AspectJ's working principle

AspectJ achieves AOP by the following steps:

  1. Dynamically generate a class that inherits the class of the original instance and override all methods
  2. Create an instance by newing the above class (instance that wraps the original instance)
  3. The wrapper method executes the AOP process and then calls the method of the original instance [^ override]

[^ override]: That's why the target method needs to be able to override

The important point is that ** AspectJ creates another class and its instance (wrapper) that inherits from your class **.

So if you call a method on an instance of the wrapper, AOP will work, but if you call a method on an instance of the original class, AOP will not work at all.

The workaround is to get the instance from which the method is called from the framework (which cares for AspectJ).

For example, the specific workaround for the above-mentioned 2) pattern is as follows:

--Called by this. method () --this in the method of your class is the original instance, not the wrapper --Get your (wrapper) instance ** from the framework and call it with DI, etc. instead of this (= you'll get a wrapper for AspectJ) --A method call is being made to the return this; --Use the instance obtained from the framework by DI etc. and do not expose this --new your class () --Let's let the framework create the instance (define it as Bean / Component in Spring)

Summary

As those who have read all the explanations so far already know, it comes down to the simple principle that ** AspectJ calls the method without going through the wrapper generated by inheritance / override **.

In articles in the world, there are times when there is an explanation and enumeration that "AspectJ does not work well in that case, in such a case, in such a case", and when I saw that, "What a complicated mysterious library. I've seen people despaired many times, but it's essentially just that (I'm likely to explain that in the future, so the text I summarized it in).

Even if the specific examples mentioned so far do not directly apply, you can probably find the cause by thinking of the operating principle of AspectJ mentioned in this article.

Bonus: Spring's @Transactional's rollbackFor

By default, a transaction will be rolling back on RuntimeException and Error but not on checked exceptions (business exceptions). See DefaultTransactionAttribute.rollbackOn(Throwable) for a detailed explanation.

Official Javadoc

Although irrelevant to the subject of this article, Spring's @Transactional does not rollback the checked exception (eg ʻIOException) by default, so" the transaction was committed even though an exception occurred. There is often a story saying "Ta!". Therefore, there is some know-how that it is less confusing to explicitly specify rollbackFor`.

Recommended Posts

The essence of AspectJ's mood-why your `@Transactional` is ignored
'% 02d' What is the percentage of% 2?
[Ruby] See the essence of ArgumentError
Your use of JobScheduler is wrong
What is testing? ・ About the importance of testing
Verify the uniqueness of your email address
What is the data structure of ActionText?
What is JSP? ~ Let's know the basics of JSP !! ~
ActiveSupport underscore is not the inverse of camelize
The order of Java method modifiers is fixed
Improve the performance of your Docker development environment
The official name of Spring MVC is Spring Web MVC