Spring框架|AOP面向切面编程的十个细节


使用AOP可以实现在不侵入到目标方法的情况下,动态的将某一模块加入IoC容器中。AOP的应用场景一般在日志权限验证安全检查事务控制

一、IoC容器中保存的是组件的代理对象

假设使用动态代理后,从容器中获取bean的类型,可以发现,获得的是一个代理类型,说明AOP的底层就是动态代理,Ioc容器中保存的是组件的代理对象。

@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class AOPTest {
	@Autowired
	Calculator ioc;
	
	@Test
	public void test() {
		System.out.println(ioc.getClass());
	}
}

在这里插入图片描述
补充:如果从IoC容器中使用类型来获取到目标对象,一定要使用它的接口类型来获取。如果没有接口,Spring的cglib会自动为我们创建代理对象。

二、切入点表达式的写法

固定格式:execution(访问权限符 返回值类型 方法全类名(参数表))
通配符:
(1)*

  • 匹配一个或多个字符。如:"execution(public int com.gql.impl.myMathCalculator.*.Math*(int, int))"
  • 匹配任意一个参数。如:"execution(public int com.gql.impl.myMathCalculator.*(int, *))"
  • 如果*放在路径里,只能匹配一层路径。如:"execution(public int com.gql.impl.myMathCalculator.*(int, int))"
  • 权限位置不能写*默认就是public的。

(2)..

  • 匹配任意任意个任意类型参数。如:"execution(public int com.gql.impl.myMathCalculator.*(..)"
  • 匹配任意多层路径。如:"execution(public int com.gql..myMath*(..)"

记住两种切入点表达式:
最模糊的:execution(* *.*(..))。代表任意包下任意类的任意方法,
最精确的:execution(public int com.gql.impl.myMathCalculator.add(int,int))
另外:切入点表达式还支持"&&"、"||"、"!"

三、通知方法的执行顺序

正常执行:

  • ① @Before(前置通知)
  • ② @After(后置通知)
  • ③ @AfterReturning(正常返回)

异常执行:

  • ①@Before(前置通知)
  • ② @After(后置通知)
  • ③ @AfterThrowing(方法异常)

四、JoinPoint获取目标方法的信息

在通知方法运行的时候,拿到目标方法的详细信息。只需要在通知方法的参数列表上写一个参数。

JoinPoint joinPoint:封装了当前目标方法的详细信息。

  • 获得方法名:joinPoint.getSignature().getName()
  • 获得参数列表:Arrays.asList(joinPoint.getArgs())

五、throwing、returning指定哪个参数用来接收异常、返回值

使用returning获得方法返回的结果:

	@AfterReturning(value = "execution(public int com.gql.impl.myMathCalculator.*(int, int))", returning = "result")
	public static void logReturn(JoinPoint joinPoint, Object result) {
		System.out.println("[" + joinPoint.getSignature().getName() + "]方法执行完成,计算结果是[" + result + "]");
	}

使用throwing获得异常信息:

	@AfterThrowing(value = "execution(public int com.gql.impl.myMathCalculator.*(int, int))", throwing = "e")
	public static void logException(JoinPoint joinPoint, Exception e) {
		System.out.println("[" + joinPoint.getSignature().getName() + "方法出现异常,异常信息是:");
	}

这种写法和Ajax接收服务器数据特别像,只不过要告诉Spring这个变量的作用是接收返回值还是抛出异常。

六、Spring对通知方法的约束

Spring对通知方法的要求不严格,唯一有要求的是方法的参数列表一定不能乱写。 通知方法是Spring利用反射调用的,每次方法调用需要确定这个方法的参数表的值。即参数列表上的每一个参数,Spring都得知道它是什么。

  • Spring认识的参数:JoinPoint joinPoint。
  • Spring不认识的参数:使用直接中的属性告诉Spring。

补充:在进行接收异常和接收返回值的时候,最好在参数中将异常的范围、返回值的类型写大一点。

七、抽取可重用的切入点表达式

在切面(Aspect)中声明一个无返回类型的空方法,并为方法标注@Pointcut注解。

	@Pointcut("execution(public int com.gql.impl.myMathCalculator.*(int, int))")
	public void myPoint() {
	
	}

后来的方法需要写表达式时只需要将value的值填写为方法名即可。

八、环绕通知实际上就是动态代理

@Around环绕通知是Spring中最强大的通知,环绕通知实际上就是动态代理。

 * try{
 * 		//前置通知
 * 		method.invoke(obj,args);
 * 		//返回通知
 * }catch(e){
 * 		//异常通知
 * }finally{
 * 		//后置通知
 * }

环绕通知,就是上面代码中的四个通知合起来的效果。下面演示环绕通知:

	@Around("myPoint()")
	public Object myAround(ProceedingJoinPoint pjp) {
		Object proceed = null;
		String methodName = pjp.getSignature().getName();
		try {
			System.out.println("环绕通知前:" + methodName + "方法开始执行");
			proceed = pjp.proceed(pjp.getArgs());// 相当于method.invoke()
			System.out.println("环绕通知后:" + methodName + "方法返回,返回值是" + proceed);
		} catch (Throwable e) {
			System.out.println(methodName + "方法出现异常,异常信息:" + e);
			throw new RuntimeException(e);
		} finally {
			System.out.println("环绕后置通知:" + methodName + "方法结束");
		}
		return proceed;
	}

上面的环绕通知中:

  • pjp.proceed(pjp.getArgs())实际上就是method.invoke(),即通过反射调用了方法。

如果程序正常执行:
在这里插入图片描述
如果程序出现异常:
在这里插入图片描述

九、同时声明环绕通知和普通通知时的执行顺序

环绕通知优先于普通通知执行,下面说明两者都声明时的执行顺序:

 * [普通前置]
 * {
 *   try{
 *   	 环绕:前置
 *  	 目标方法执行
 * 		 环绕:返回
 * 	 }catch(){
 * 		环绕:出现异常
 *   }finally{
 *   	环绕:后置
 *   }
 * [普通后置]
 * [普通返回/异常]

真正的执行顺序是:

  • ①环绕:前置 ②普通前置。(这两个的执行顺序是随机的,不必关注)
  • ③目标方法执行
  • ④环绕:返回/环绕:出现异常
  • ⑤环绕:后置
  • ⑥普通后置
  • ⑦普通返回/异常

注意:如果环绕通知拿到异常不处理,普通通知就不会拿到异常,则普通通知感受到的方法是正常的,这是由于环绕通知把异常catch掉了。因此,为了让外界能够知道这个异常,环绕通知中的异常一定要抛出去。

十、多切面运行顺序

下图的结果是LogUtils切面VaApsect切面LogUtils切面中的环绕通知,同时作用与一个方法时的执行顺序:

  • 默认情况下是以开头字母顺序决定哪个切面先执行。
  • 可以通过@Order(num)指定切面执行顺序,数值越小优先级越高。
    在这里插入图片描述
  • 牢记一句话:环绕是在哪个切面内,就只作用于哪个切面内!
    在这里插入图片描述
发布了451 篇原创文章 · 获赞 1428 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/weixin_43691058/article/details/105093240
今日推荐