Spring AOP implementation principle

Proxy-based AOP implementation

First, this is a proxy-based implementation. The following diagram nicely expresses this relationship:

This diagram reflects several key components involved in the AOP process (using @Before Advice as an example):

  1. Caller Beans - that is, the initiator of the call, it only knows the bean where the target method is located, and does not know the existence of the proxy and Advice
  2. Bean where the target method is located - the target method being called
  3. Generated proxy - a proxy object generated by Spring AOP for the bean where the target method is located
  4. Advice - Execution logic for aspects

The calling sequence between them is reflected in the sequence number in the above figure:

  1. The caller bean attempted to call the target method, but was intercepted by the generated proxy
  2. The agent first calls the Advice according to the type of Advice (@Before Advice in this case)
  3. The delegate calls the target method
  4. Return the result of the call to the caller Bean (returned by the proxy, not shown in the figure)

In order to understand the meaning of this picture and the role of the agent in the middle, let's take a look at the following code:

@Component
public class SampleBean { public void advicedMethod() { } public void invokeAdvicedMethod() { advicedMethod(); } } @Aspect @Component public class SampleAspect { @Before("execution(void advicedMethod())") public void logException() { System.out.println("Aspect被调用了"); } } sampleBean.invokeAdvicedMethod(); // 会打印出 "Aspect被调用了" 吗?

SampleBeanIt plays the role of the Bean where the target method is located, and SampleAspectplays the role of Advice. Obviously, the method modified by AOP is advicedMethod(), not invokeAdvicedMethod(). However, the invokeAdvicedMethod()method is called internally advicedMethod(). So will the output in Advice be printed out?

The answer is no .

If you can't figure out why this is happening, you might as well take a closer look at the diagram above.

This is a problem you may encounter when using Spring AOP. The reason why such an indirect call does not trigger Advice is that the call occurs inside the bean where the target method is located, and has nothing to do with the external proxy object. We can think of this proxy as an intermediary, only it knows the existence of Advice. The caller bean and the bean where the target method is located know the existence of each other, but they know nothing about the proxy or Advice. Therefore, it is absolutely impossible to trigger the logic of Advice without calling through the proxy. As shown below:

Two implementations of Spring AOP

Spring AOP has two implementations:

  • Interface-based dynamic proxy (Dynamic Proxy)
  • Subclassing based CGLIB proxy

When we use Spring AOP, we generally do not need to choose a specific implementation. Spring AOP can help us choose an appropriate one according to the context. So is it possible to choose so " intelligently " every time? Not necessarily, the following example reflects this problem:

@Component
public class SampleBean implements SampleInterface { public void advicedMethod() { } public void invokeAdvicedMethod() { advicedMethod(); } } public interface SampleInterface {}

In the above code, we implement a new interface for the original bean SampleInterface, and no methods are defined in this interface. At this time, an exception will occur when the relevant test code is run again (some exception information is excerpted):

org.springframework.beans.factory.BeanCreationException: 
Error ceating bean with name 'com.destiny1020.SampleBeanTest': 
Injection of autowired dependencies failedCaused by: 
org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type [com.destiny1020.SampleBean] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. 

That is to say, the Autowiring of the Bean in the Test class failed because an exception occurred when the SampleBeanTest Bean was created. So why is there an exception to create a bean? It is not obvious from the exception information. In fact, the root of the problem is that there is a problem with Spring AOP when creating the proxy.

The source of this problem can get some clues here:

Spring AOP Reference - AOP Proxies

The documentation says it like this (with translations after each paragraph):

Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.

Spring AOP uses standard JDK dynamic proxies by default to implement AOP proxies. This enables any interface (or set of interfaces) to be proxied.

Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes; business classes normally will implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface, or where you need to pass a proxied object to a method as a concrete type.

Spring AOP also uses CGLIB proxies. This is necessary for proxy classes rather than interfaces. If a business object does not implement any interface, then CGLIB will be used by default. Since it is a good practice to program interface-oriented rather than classes-oriented; business objects usually implement one or more business interfaces. It is also possible (hopefully rare) to force the use of CGLIB, in which case the method you need to advise is not defined in the interface, or you need to pass a concrete object to the method as a proxy object.

Therefore, the reason for the above exception is:

It is also possible (hopefully rare) to force the use of CGLIB, in which case the method you need to advise is not defined in the interface.

The method we need advice is the advisedMethod method in SampleBean. After adding the interface, this method is not defined in the interface. So as the documentation says, we need to force the use of CGLIB to avoid this problem.

Forcing the use of CGLIB is simple:

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan(basePackages = "com.destiny1020") public class CommonConfiguration {}

@EnableAspectJAutoProxyJust add attributes to the annotation proxyTargetClass = true
CGLIB realizes the principle of AOP proxy by dynamically creating a subclass of the target bean. The instance of the subclass is the AOP proxy, which establishes the connection between the target bean and the advice.

Of course there is another solution, which is to declare the method definition in the newly created interface and remove the previously added proxyTargetClass = true:

@Component
public class SampleBean implements SampleInterface { @Override public void advicedMethod() { } @Override public void invokeAdvicedMethod() { advicedMethod(); } } public interface SampleInterface { void invokeAdvicedMethod(); void advicedMethod(); } @Configuration @EnableAspectJAutoProxy @ComponentScan(basePackages = "com.destiny1020") public class CommonConfiguration {}
  • The difference between the two AOP implementations can also be seen from the perspective of Debug Stacktrace:
  • JDK dynamic proxy
  • CGLIB
  • A brief summary of the two ways of dynamic proxy and CGLIB is as follows:

    • JDK Dynamic Proxy (Dynamic Proxy)

      • Dynamic proxy function based on standard JDK
      • Only for business objects that implement the interface
    • CGLIB

      • The AOP proxy is implemented by dynamically subclassing the target object. The above screenshot is a SampleBean$$EnhancerByCGLIB$$1767dd4bdynamically created subclass
      • needs to be specified @EnableAspectJAutoProxy(proxyTargetClass = true)to force the use
      • When the business object does not implement any interface, CGLIB is selected by default

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324685199&siteId=291194637