Original works, yes, but please mark the source address: www.cnblogs.com/V1haoge/p/9476550.html
First, ask questions
I do not know if you have encountered such a situation, the development of Web references in the SSM framework, or the use of springboot to develop applications, when we invoke a method with @transactional annotations to perform a transaction operation, sometimes we find that the transaction does not take effect.
Have you ever thought about why and how to fix a transaction?
Second, analysis of the problem
To understand why a transaction does not take effect, we first need to understand the implementation principle of the transaction in spring, and the declarative transaction in spring is implemented using AOP.
What does AOP rely on in spring? Dynamic agent, two dynamic agents used in spring, one is the JDK dynamic agent provided by Java native, the other is the Cglib dynamic agent provided by the third party, the former is based on the interface, the latter is based on the class implementation, the latter is more widely used. However, the native JDK dynamic agent is much faster, both of which have their own characteristics.
The purpose of dynamic proxies is to generate proxy classes in real time when the application is running, so that we can enhance them on the basis of existing implementations, which is actually the purpose of AOP, enhancing the functionality of the class.
The proxy class generated by the dynamic proxy has all the public methods of the native class, and the call to the specified method is transferred to the method of the same name as the proxy class, and within this method there are other operations, such as logging, such as a security check, such as a transaction operation, that are performed outside of the same name as the native class.
When we call a method of the service layer with a transaction annotation directly at the controller layer, we perform the above steps: Generate the proxy class, invoke the same name method of the proxy class, implement the transaction function by the proxy class, and then invoke the method of the native class to perform the logical execution.
The above situation is not a problem, the problem is that we in the service layer inside the method calls the method with the transaction annotations in this class, the transaction annotations will be invalidated, we call the way is simply called or with this call, the two cases are actually the same effect, are called with the current instance.
With the introduction of previous AOP and dynamic proxies, it is easy to understand the cause of this transaction failure: that is, the native method that we call directly when we invoke the target transaction method, not the proxy method in the proxy class, that is, we do not invoke the method of transaction enhancement, As a result, the transaction will certainly fail.
In this case, we need to invoke the enhanced proxy method in the proxy class in order for the transaction to take effect.
Third, solve the problem
So how are we going to fix it? It's really simple, as long as we don't use this call. This represents the current instance, in spring is generally a singleton instance, call their own method, transaction annotations equal to the device. If we change the invocation method, inject our singleton instance into the current class, and invoke the method with the injected instance, we can make the transaction take effect.
Why is it? In general, the service layer in our SSM architecture has interfaces and implementation classes, and since interfaces exist, it is necessary that the JDK dynamic proxy be used to generate proxy classes. When we inject a singleton instance of the current class into itself, using this injected instance to invoke the method in the interface, if there is an AOP enhancement annotation such as @transactional, then it is the generation of the proxy class to implement the function enhancement. (We used to get rid of interface development when we developed in Springboot, then the proxy class was generated using the Cglib dynamic agent).
This also requires that our transaction methods need to be declared in the interface first, then implement the logic in the implementation class, and add the transaction annotations.
This approach is useful for resolving transactions that fail when a transaction method in the service is called in the service. Think of that before invoking the service from the controller is also invoked by an injected service singleton instance, which also proves that the method we provide is valid.
Iv. extension of the problem
4.1 Extension questions: Cyclic dependencies
As for another problem that arises: when we inject an instance of the current class into the current class, we need to inject an instance of this class when we create an instance of the class, but what if this class is created???
This is the famous circular dependency problem in spring.
The more obvious example is that in a, relying on b,b and dependent on a, relying on each other, then will not cause two instances to create a failure?
4.2 Cycle-dependent solutions
It is necessary to briefly say the solution to this problem in spring. Why is a brief introduction, because I am just a simple understanding, but this simple understanding is more applicable to the friends who do not understand, not to be confused.
We all know that in spring, beans have a variety of life cycle ranges, primarily singleton and prototype (and of course, request, session, etc.), and the singleton indicates that there will only be one instance of the bean in the entire application context, and the prototype is the opposite, there can be multiple bean instances, A new bean instance is created each time the Getbean is called.
We would like to emphasize that in spring, if the bean instance in the scope of the central area has a cyclic dependency, there is only one thing: Throw an exception.
In order to solve the problem of cyclic dependence, a kind of effective early-exposure mechanism is provided for the single-case bean,spring. Of course, the only solution here is to use setter mode to implement dependency injection, if the use of the constructor dependency injection is the case: Throw exception.
Throw exception representative, spring is not able to resolve this problem, the program error.
Why is it? Doesn't spring want to fix it? Certainly not, but powerless.
Let's take a quick look at the solution to the cyclic dependency of a singleton bean that implements dependency injection by setter mode:
Let's start with some of the cache pools in spring:
singletonobjects: A single cache pool, which is used to save a singleton bean that is created, is a map, and any bean instance that has been created is saved in the cache pool, and no circular dependent beans are saved directly to the cache after it is created. A bean that has a cyclic dependency is then transferred by Earlysingletonobjects to this cache after its creation is complete.
singletonfactories: A single factory cache pool for storing pre-exposed objectfactory, which is a map
earlysingletonobjects: An early singleton cache pool, which is used to save a singleton bean that has not yet been created for early exposure, is a map that is mutually exclusive with singletonobjects and cannot be stored in both. Can only be saved in this cache pool is not yet complete creation, and is injected into other beans in the bean instance, it is said that the cache is an intermediate cache (or called the process cache), Only when the beanname corresponding native bean (in the Create pool) is injected into another instance of the bean, it is added to the cache, which is always an instance of the bean that is never finished, and is removed from this cache when the bean instance is finalized. Transferred to the singletonobjects cache for saving.
registeredsingletons: A registered singleton cache pool, which holds the beanname of the bean instance that has been created, is set (this cache is not covered)
singletonscurrentlyincreation: Creating a medium pool, saving the beanname of the singleton Bean in the creation, is set, which is added to the pool when the bean instance is started to be created. The bean instance is removed from the pool after it has been created.
When there are cyclic dependencies, such as in the previous case: a relies on b,b and relies on a, in which case an instance of a is first created and its beanname added to the singletonscurrentlyincreation pool. Then call A's constructor to create a native instance of a and add its objectfactory to the singletonfactories cache, and then process the dependency injection (b instance). Found that the B instance does not exist and is not in the Singletonscurrentlyincreation pool, indicating that the bean instance has not yet been created, then the next step is to create the B instance, Add its beanname to the singletonscurrentlyincreation pool, then call the constructor of B to create a native instance of a and add its objectfactory to the singletonfactories cache. Then processing dependency injection (a instance), found that a instance has not yet been created, but found in the singletonscurrentlyincreation pool The beanname of a instance, stating that a instance is in the process of being created, indicating a cyclic dependency, Spring will inject the bean instance returned by the GetObject method in the Singletonfactories cache to the beanname of the objectfactory in the corresponding A, to B to complete the creation of the B instance. A bean instance is also added to the earlysingletonobjects cache, indicating that an instance of a is an early-exposed bean instance, and that a native instance of B needs to be removed from the singletonfactories cache after the B instance is created. and add the full instance to the Singletonobjects cache (which, of course, does not exist in earlysingletonobjects). and remove its beanname from the Singletonscurrentlyincreation pool (indicating that the B instance is completely created). The B instance is then injected into the a instance to complete the creation of a instance, and finally a native instance of a is removed from the earlysingletonobjects, the full instance is added to Singletonobjects, and the beanname of a is removed from the Create pool. This completes the creation of A and B two singleton instances.
Having understood the solution described above, we can see why it cannot be resolved in the case of a cyclic dependency of the bean that implements dependency injection for the constructor. That is because, before the premise of early exposure is to create a good native bean instance, the original instance of the bean is built by the constructor, if the constructor to create a bean when you need to inject dependency, and the dependency is in the creation, because the objectfactory can not be exposed, and cannot solve the circular dependency problem.
In addition to the case of the prototype bean, spring simply does not add the cache to the prototype bean, because the purpose of adding the cache is to guarantee the uniqueness of the singleton Bean, but for the prototype, it cannot be cached, if the bean instance fetched from the cache, is it a prototype mode? There is no cache, and of course it is not possible to implement the series of operations described above, and it will not solve the problem of cyclic dependencies.
V. Summary
The transaction problem in spring boils down to the problem of injection, and the problem of cyclic dependency is the injection problem, which is discussed later.
All of the enhancements in spring are based on AOP, and AOP relies on dynamic proxies, and the dynamic proxy of the JDK relies on the reflection technology, while the cglib dynamic proxy relies on bytecode technology.