AOP usage and principle analysis

1. Introduction to AOP

1. AOP concept

AOP, Aspect Oriented Programming, aspect-oriented programming, is the sublimation of object-oriented programming OOP. OOP is a vertical abstraction of a thing, an object includes static attribute information, including dynamic method information, etc. AOP is a horizontal abstraction of different things. Attributes and attributes, methods and methods, objects and objects can all form an aspect, and the way to design programming with this kind of thinking is called aspect-oriented programming.

AOP refers to a programming method that dynamically cuts a certain piece of code into a specified method and a specified location during the running of the program. The bottom layer of AOP is implemented using dynamic proxies.

insert image description here

2. Implementation plan of AOP idea

Dynamic proxy technology, during runtime, the method of the target object is enhanced, and the original logic can be executed in the method of the same name of the proxy object, and then embedded to execute other enhanced logic or methods of other objects

insert image description here

3. Simulate the basic code of AOP

In fact, when learning BeanPostProcessor before, the Bean is enhanced by using dynamic proxy in the after method of BeanPostProcessor. What is actually stored in the single object pool singleObjects is not the current target object itself, but the proxy object Proxy of the current target object. In this way, when calling When the target object method is used, the method of the same name of the proxy object Proxy is actually called, which has the function of enhancing the target method before and after. This method is optimized, and the enhanced method is extracted into an enhanced class, and only for com. any method of any class under the itheima.service.impl package.

// 自定义增强类
public class MyAdvice {
    
    
	public void beforeAdvice() {
    
    
		System.out.println("beforeAdvice...");
	}
	public void afterAdvice() {
    
    
		System.out.println("afterAdvice...");
	}
}
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
    
    

	// 注入 Spring 容器对象
    private ApplicationContext applicationContext;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
    
		// 获得 Advice 对象
        MyAdvice myAdvice = applicationContext.getBean(MyAdvice.class); 
		String packageName = bean.getClass().getPackage().getName();
        if("com.itheima.service.impl".equals(packageName)) {
    
    
            // 对 Bean 进行动态代理,返回的是 Proxy 代理对象
            Object proxyBean = Proxy.newProxyInstance(
                    bean.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    (Object proxy, Method method, Object[] args) -> {
    
    
                        // 执行增强对象的before方法
                        myAdvice.beforeAdvice();
                        // 执行目标对象的目标方法
                        Object result = method.invoke(bean, args);
                        // 执行增强对象的after方法
                        myAdvice.afterAdvice();
                        return result;
                    }
            );
			// 返回代理对象
            return proxyBean;
        }

        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        this.applicationContext = applicationContext;
    }
}

4. AOP related concepts

insert image description here

2. AOP based on XML configuration

1. Configure AOP in XML mode

There are still some problems with the AOP basic code we wrote earlier, mainly as follows:

  • The enhanced package name is written hard in the code
  • The method of notifying the object is hard-coded in the code

insert image description here

Solve the above problems by means of configuration files

  • Configure which packages, which classes, and which methods need to be enhanced
  • Configure the target method to be enhanced by which notification methods, whether to execute the design of the enhanced configuration method and the analysis of the configuration file (annotation) before or after the execution of the target method, Spring has already packaged it for us

Steps to configure AOP in xml mode:

  1. Import AOP related coordinates;
  2. Prepare the target class, prepare the enhanced class, and configure it for Spring management;
  3. Configure pointcut expressions (which methods are enhanced);
  4. Configuration weaving (which notification methods are enhanced by the pointcut, whether it is pre-enhanced or post-enhanced).

(1) Import AOP related coordinates

<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.6</version>
</dependency>

insert image description here

(2) Prepare the target class, prepare the enhanced class, and configure it for Spring management

insert image description here

(3) Configure pointcut expressions (which methods are enhanced)

(4) Configuration weaving (which notification methods are enhanced by the cut point, whether it is pre-enhancement or post-enhancement)

<!--aop配置-->
<aop:config>
    <!--配置切点表达式,目的是要指定哪些方法被增强-->
    <aop:pointcut id="myPointcut" expression="execution(void com.itheima.service.impl.UserServiceImpl.show1())"/>   
    <!--配置织入,目的是要执行哪些切点与那些通知进行结合-->
    <!--切面=切点+通知-->
    <aop:aspect ref="myAdvice">
        <!--前置通知-->
        <aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
        <!--后置通知-->
        <aop:after-returning method="afterAdvice" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>

2. Detailed explanation of AOP configuration in XML mode

The way to configure AOP in xml is relatively simple. Let's take a look at the details of AOP detailed configuration:

  • How to configure pointcut expressions
  • Configuration syntax for pointcut expressions
  • type of notification
  • Two ways of AOP configuration

There are two ways to configure the pointcut expression, directly configure the pointcut expression on the notification, or extract the pointcut expression to the outside and reference it on the notification

The pointcut expression is an enhancement to configure which connection points (which methods of which classes) to be notified, and the syntax is as follows:

execution([访问修饰符] 返回值类型 包名.类名.方法名(参数))

in:

  • Access modifiers can be omitted;
  • The return value type, a certain level of package name, class name, method name can use * to represent any;
  • Use a single dot between the package name and the class name to indicate the class under the package, use double dots... to indicate the class under the package and its sub-packages;
  • The parameter list can use two dots ... to represent any parameter.

Pointcut expressions give a few examples for easy understanding

// 表示访问修饰符为 public 、无返回值、在 com.itheima.aop 包下的 TargetImpl 类的无参方法 show
execution(public void com.itheima.aop.TargetImpl.show())
// 表述 com.itheima.aop 包下的 TargetImpl 类的任意方法
execution(* com.itheima.aop.TargetImpl.*(..))
// 表示 com.itheima.aop 包下的任意类的任意方法
execution(* com.itheima.aop.*.*(..))
// 表示 com.itheima.aop 包及其子包下的任意类的任意方法
execution(* com.itheima.aop..*.*(..))
// 表示任意包中的任意类的任意方法
execution(* *..*.*(..))

AspectJ advice has the following five types

insert image description here

surround notification

public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    
    
    System.out.println("环绕前的增强....");
    proceedingJoinPoint.proceed();// 执行目标方法
    System.out.println("环绕后的增强....");
}
<aop:around method="around" pointcut-ref="myPointcut"/>

Exception notification, when the target method throws an exception, the exception notification method is executed, and the post notification and surround notification are no longer executed

public void afterThrowing(){
    
    
    System.out.println("目标方法抛出异常时,后置通知和环绕通知不再执行");
}
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>

Final notification, similar to finally in exception capture, regardless of whether the target method has an exception, the final notification will be executed

public void after() {
    
    
    System.out.println("最终的增强....");
}
<aop:after method="after" pointcut-ref="myPointcut"/>

When the notification method is called, Spring can pass some necessary parameters to it

insert image description here

JoinPoint object

public void 通知方法名称(JoinPoint joinPoint) {
    
    
	// 获得目标方法的参数
	System.out.println(joinPoint.getArgs());
	// 获得目标对象
	System.out.println(joinPoint.getTarget());
	// 获得精确的切点表达式信息
	System.out.println(joinPoint.getStaticPart());
}

ProceedingJoinPoint object

public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
	// 获得目标方法的参数
	System.out.println(joinPoint.getArgs());
	// 获得目标对象
	System.out.println(joinPoint.getTarget());
	// 获得精确的切点表达式信息
	System.out.println(joinPoint.getStaticPart());
	// 执行目标方法
	Object result = joinPoint.proceed(); 
	// 返回目标方法返回值
	return result; 
}

Throwable object

public void afterThrowing(JoinPoint joinPoint, Throwable th) {
    
    
	// 获得异常信息
	System.out.println("异常对象是:" + th + ",异常信息是:" + th.getMessage());
}
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="th"/>

Another configuration method of AOP, which requires the notification class to implement the sub-function interface of Advice

public interface Advice {
    
    
}

Sub-function interface of Advice

insert image description here

For example: the notification class implements the pre-notification and post-notification interfaces

public class Advices implements MethodBeforeAdvice, AfterReturningAdvice {
    
    

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
    
    
        System.out.println("前置通知..........");
    }

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
    
    
        System.out.println("后置通知...........");
    }

}

Aspects are configured using the advisor tag

<aop:config>
	<!--将通知和切点进行结合-->
	<aop:advisor advice-ref="advices" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>

Another example: the notification class implements the method interceptor interface

public class MyMethodInterceptor implements MethodInterceptor {
    
    

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    
    
        System.out.println("环绕前******");
        // 执行目标方法
        Object res = methodInvocation.getMethod().invoke(methodInvocation.getThis(),  methodInvocation.getArguments());
        System.out.println("环绕后******");
        return res;
    }
}

Aspects are configured using the advisor tag

<aop:config>
	<!--将通知和切点进行结合-->
	<aop:advisor advice-ref="myMethodInterceptor" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>

The difference between using aspect and advisor configuration is as follows:

  1. The configuration syntax is different:
<!--使用 advisor 配置-->
<aop:config>
	<!--advice-ref: 通知Bean 的 id-->
	<aop:advisor advice-ref="advices" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>

<!--使用 aspect 配置-->
<aop:config>
	<!--ref: 通知Bean 的 id-->
	<aop:aspect ref="advices" pointcut="execution(void com.itheima.aop.TargetImpl.show())"/>
<aop:config>
  1. The definition requirements of the notification class are different. The advisor notification class needs to implement the sub-function interface of Advice:
public class Advices implements MethodBeforeAdvice {
    
    

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
    
    
        System.out.println("前置通知..........");
    }
}

Aspect does not require the notification class to implement any interface. It is more flexible and convenient to specify which methods belong to which notification type during configuration:

public class Advices {
    
    
	public void before() {
    
    
		System.out.println("前置通知..........");
	}
}
  1. The number of configurable aspects varies
  • An advisor can only configure one fixed notification and one pointcut expression;
  • An aspect can be configured with any combination of multiple advices and multiple pointcut expressions, with finer granularity.
  1. different usage scenarios
  • If there are many types of notifications and it is allowed to match freely, you can use aspect to configure;
  • If the notification type is single and the notification method in the notification class will be used at one time, you can use the advisor to configure it;
  • When the notification type has been fixed and there is no need to manually specify the notification type, you can use the advisor to configure it, such as the configuration of Spring transaction control to be learned later;

Since in actual development, the configuration of custom aop functions mostly uses the configuration method of aspect, so we will mainly explain the configuration of aspect later. The advisor is to pave the way for the later Spring declarative transaction control, and you can understand it here.

3. Analysis of the principle of AOP in XML mode

When configuring AOP through
xml, we introduced the AOP namespace. According to the previous chapters, we need to find the META-INF under the spring-aop package and find the spring.handlers file

insert image description here

AopNamespaceHandler is finally loaded, and the parser corresponding to the config tag is registered in the init method of the Handler

public class AopNamespaceHandler extends NamespaceHandlerSupport {
    
    

	@Override
	public void init() {
    
    
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}

}

Using ConfigBeanDefinitionParser as the entry point for source code analysis, an AspectJAwareAdvisorAutoProxyCreator will eventually be registered and entered into the Spring container. What is the function of this class? Take a look at the integrated system diagram

insert image description here

The postProcessAfterInitialization method in the parent class AbstractAutoProxyCreator of AspectJAwareAdvisorAutoProxyCreator

// 参数 bean :为目标对象
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    
    
	if (bean != null) {
    
    
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    
    
			// 如果需要被增强,则 wrapIfNecessary 方法最终返回的就是一个 Proxy 对象
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

Observing through breakpoints, when the bean matches the pointcut expression, this.wrapIfNecessary (bean, beanName , cacheKey returns a JDKDynamicAopProxy

insert image description here

You can go a little deeper and analyze wrapIfNecessary again to see if it is a
proxy object created through JDK's Proxy.newProxyInstance ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) that we are familiar with? After the following series of source code tracking

insert image description here

The choice of dynamic proxy implementation, when calling the getProxy () method, there are two implementation classes of the AopProxy interface we can choose, as shown in the figure above, both of which are ways to dynamically generate proxy objects, one is based on JDK, and the other is One is based on Cglib.

insert image description here
insert image description here

JDK's dynamic proxy code has been written before, let's take a look at Cglib's superclass-based dynamic proxy

Target target = new Target(); // 目标对象
Advices advices = new Advices(); // 通知对象
Enhancer enhancer = new Enhancer(); // 增强器对象
enhancer.setSuperclass(Target.class); // 增强器设置父类
// 增强器设置回调
enhancer.setCallback((MethodInterceptor )(o, method, objects, methodProxy) -> {
    
    
	advices.before();
	Object result = method.invoke(target, objects);
	advices.afterReturning();
	return result;
});	
// 创建代理对象
Target targetProxy = (Target) enhancer.create();
// 测试
String result = targetProxy.show("haohao");

3. AOP based on annotation configuration

1. Annotation way to configure AOP

Spring's AOP also provides annotation configuration, using the corresponding annotations to replace the previous xml configuration. When xml configures AOP, we mainly configure three parts: the target class is managed by the Spring container, the notification class is managed by Spring, and the notification and point-cutting Weave into (section), as follows:

insert image description here

The target class is managed by the Spring container, and the notification class is managed by Spring

insert image description here

In fact, configuring aop is mainly to configure which method (notification type) in the notification class corresponds to the point-cutting expression

insert image description here

2. Detailed explanation of AOP configuration in annotation mode

Various annotation notification types

insert image description here

To extract the pointcut expression, use an empty method to mark the pointcut expression on the empty method, and other notification methods can be referenced

insert image description here

3. Analysis of AOP principle of annotation method

When using xml to configure AOP before, it was done with the help of Spring's external namespace loading method. After using annotation configuration, the <aop:config> tag was discarded, and the tag finally loaded the BeanPostProcessor named AspectJAwareAdvisorAutoProxyCreator, and finally , the proxy object generation is completed in this BeanPostProcessor.

Similarly, start with the parser for the aspectj-autoproxy tag

public class AopNamespaceHandler extends NamespaceHandlerSupport {
    
    

	@Override
	public void init() {
    
    
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}

}

Inside the code of AspectJAutoProxyBeanDefinitionParser, the same code as AOP in xml mode is finally executed.

registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class , registry, source);

If the core configuration uses the configuration class, you need to configure the aop automatic proxy in the annotation mode

@Configuration
@ComponentScan("com.itheima.aop")
@EnableAspectJAutoProxy // 第三步
public class ApplicationContextConfig {
    
    
}

View @EnableAspectJAutoProxy source code, also use @Import to import related parsing classes

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({
    
    AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
    
    
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;
}

Use the source code of AspectJAutoProxyRegistrar imported by @Import, trace all the way, and finally register the AnnotationAwareAspectJAutoProxyCreator class.

AspectJAutoProxyRegistrar source code:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    
    
    AspectJAutoProxyRegistrar() {
    
    
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        // ...
    }
}

进入AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
    
    
	return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
		BeanDefinitionRegistry registry, @Nullable Object source) {
    
    
		
	// 最终还是注册了AnnotationAwareAspectJAutoProxyCreator 这个类
	return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

Principle comparison between xml method and annotation method

insert image description here

Guess you like

Origin blog.csdn.net/qq_36602071/article/details/129166235