Spring AOP晦涩的概念你真的懂吗?

1 篇文章 0 订阅
1 篇文章 0 订阅

AOP核心知识点

切面、切点、连接点、通知、织入

光看概念就一脸懵逼,根本记不住。

原理和思想

asm框架

通过某个技术在程序运行期间,根据需求动态的创建字节码文件,生成的字节码可以在内存中,也可以溢写到磁盘上,但最终要由一个class文件。由此asm框架进行动态生成。

匹配方法(正则表达式)

重新生成或者在原来的class文件基础之上,直接添加日志处理的相关逻辑即可。给这个核心功能起个名字就是AOP。

AOP是一种编程思想,当对AOP进行实践的时候,需要制定一些规范要求,根据规范要求,来实现AOP功能。

有了规范之后,那么需要安装某种规则进行一个匹配,比如通过某些规则找到需要实现AOP功能的方法。

通过类似于正则表达式的一个东西,来进行匹配方法。可以通过方法名字、方法参数、方法的返回值、类的包名(完全限定名)等等。

通过expression表达式,找到这个切入点

表达式语法

Spring aop官网语法如下

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
          throws-pattern?)

翻译后表达的意思是

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

其中除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

execution(* com.shuweicloud.bi.api.web..*Controller.list(..))

标识符

含义

execution()

表达式的主体

第一个*符号

表示任意的返回类型

com.shuweicloud.bi.api.web

AOP所切的服务的包名,即,需要进行横切的业务类

包后面的..

表示当前包及子包

第二个*符号

表示类名,*即所有类

.list(..)

表示匹配方法名list(),括号表示参数,两个点表示任何参数类型.

通知

当通过上述的表达式匹配到方法(切入点)之后,那么需要方法的哪些位置进行增加呢?

AOP提供了5种通知方法

通知方法:
* 前置通知(@Before):logStart():在目标方法(div)运行之前运行
* 后置通知(@After):logEnd():在目标方法(div)运行结束之后运行
* 返回通知(@AfterReturning):logReturn():在目标方法(div)正常返回之后运行
* 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
* 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())

注解版AOP操作步骤

  1. 导入Spring AOP需要的jar包。(spring-aspects)
  2. 定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、异常等)。
  3. 定义一个日志切面类(LogAspects):切面类里面的方法需要动态的感知MathCalculator.div()方法运行到哪里?
  4. 给切面类的目标方法标注何时运行。
  5. 将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
  6. 必须告诉spring哪个类是切面类,给切面类加上一个注解 @Aspect
  7. 给配置类中加入注解@EnableAspectJAutoProxy开启基于注解的AOP模式

maven配置

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>5.1.7.RELEASE</version>
</dependency>

业务类:MathCalculator类

public class MathCalculator {

	public int  div(int i,int j) {
		return i/j;
	}
}

切面类

@Aspect
public class LogAspects {
	
	//抽取公共的切入点表达式
	//1、本类引用
	//2、其他的切面引用
	@Pointcut("execution(public int cn.com.git.annotation.aop.MathCalculator.*(..))")
	public void pointCut() {
		
	}
	@Around("pointCut()")
	public Object logArround(ProceedingJoinPoint pjp){
		Signature signature = pjp.getSignature();
		Object[] args = pjp.getArgs();
		Object result= null;
		System.out.println("执行around方法,获取到的参数是:"+Arrays.asList(args));
		try {
			 result = pjp.proceed(args);
		} catch (Throwable throwable) {
			throwable.printStackTrace();
			System.out.println("环绕通常异常:"+signature.getName());
		}finally {
			System.out.println("环绕返回通知,返回结果"+result);
		}
		return result;
	}
	//@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
	@Before("pointCut()")
	public void logStart(JoinPoint joinPoint) {
		Object[] args = joinPoint.getArgs();
		System.out.println(joinPoint.getSignature().getName()+"before运行。。。。参数列表是:{"+Arrays.asList(args)+"}");
	}
	@After("pointCut()")
	public void logEnd(JoinPoint joinPoint) {

		System.out.println(joinPoint.getSignature().getName()+"after除法结束");
	}
	@AfterReturning(value="pointCut()",returning="result")
	public void logReturn(JoinPoint joinPoint,Object result) {
		System.out.println(joinPoint.getSignature().getName()+"afterReturning除法正常返回。。。运行结果:{"+result+"}");
	}
	//JoinPoint 一定要出现在参数表的第一位
	@AfterThrowing(value = "pointCut()",throwing="exception")
	public void logException(JoinPoint joinPoint,Exception exception) {
		System.out.println(joinPoint.getSignature().getName()+"除法异常。。。异常信息:{"+exception+"}");
	}

Config类

@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
	//业务逻辑类加入到容器中
	@Bean
	public MathCalculator getMathCalculator() {
		return new MathCalculator();
	}
	//切面类加入到容器中
	@Bean
	public LogAspects getLogAspects() {
		return new LogAspects();
	}
}

源码debug

通过AOP注解版,新建一个test类进行测试

public class IOCTest_AOP {

	@Test
	public void testAOP() {
		
		AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
		
		MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
		mathCalculator.div(2, 2);
		
		applicationContext.close();
	}
}

1、获取Spring context上下文Beanfactory对象

Spring上下文benfactory里加载了16个类,保护自定义类和容器自身所需要的类。

2、自定义类生成的代理类(DynamicAdvisedInterceptor)

通过名字可以看出,自定义类是生成了动态代理类。

当执行业务类的div()方法时候,其实执行的是代理类的方法。上图可以看到调用的CALLBACK_0对应的类是DynamicAdvisedInterceptor类的interceptor()方法。

3、责任链(chain)

在interceptor()方法中,获取到一个责任链

链中存放了5个通知类型。

获取到链之后,执行proceed()方法。

4、proceed()方法。

proceed()方法是在CglibMethodInvocation类的父类ReflectiveMethodInvocation中,方法如下

public Object proceed() throws Throwable {
		//	We start with an index of -1 and increment early.
        //从索引为-1的拦截器开始调用,并按序递增,如果拦截器链中的拦截器迭代调用完毕,开始调用target函数,这个函数是通过反射机制完成的。
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}
		//获取下一个要执行的拦截器,沿着定义好的interceptorOrInterceptionAdvice链进行处理
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
            //这里对拦截器进行动态匹配的判断,这里是对pointcut触发进行匹配的地方,如果和定义的pointcut匹配,那么这个advice将会得到执行
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

通过currentInterceptorIndex参数的++操作,获取chain保存的链式通知。执行invoke()方法。按F7进入invoke方法,可以看到具体的invoke都做了哪些操作。

第1个执行的类是ExposeInvocationInterceptor类的invoke方法

通过截图,可以看到invoke调用的是mi.proceed()方法。而此处的mi正是ReflectiveMethodInvocation。所以又回到了CglibMethodInvocation类的父类ReflectiveMethodInvocation中继续调用proceed()方法。

前面我们知道proceed()方法,会进行链的++操作,来获取链上的下一个对象。

第2个类:AspectJAfterThrowingAdvice

同样的,这个通知类也是执行mi.proceed();

第3个类、AfterReturningAdviceInterceptor

第4个类、AspectJAfterAdvice

第5个类、MethodBeforeAdviceInterceptor

可以看出,执行Before的时候,是先AOP注解标注的before的方法的。执行完之后,又回到了mi.proceed()。继续进行链式执行。

执行@Before方法

当currentInterceptorIndex++ 到5的时候,

问题、

1、递归执行不好理解,需要加深这个概念的理解

2、@Around环绕执行的逻辑

3、执行的这些类和定义的那些chain之间的关系

ExposeInvocationInterceptor-->AspectJAfterThrowingAdvice-->AfterReturningAdviceInterceptor-->AspectJAroundAdvice-->MethodBeforeAdviceInterceptor-->LogAspects-->(@Around通知执行proceed

()前半部分)-->LogAspects(@Before)-->@Around方法的后半部分-->@after方法-->@AfterReturning方法。

链的顺序是一样的吗?

不一样。chian上的顺序是通过拓扑排序,顺序可能会不一样,那么放到链上的目的就是能方便获取找到下一个需要执行的任务。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值