详细分析Spring关键之一的Aop(AspectJ)模块

欢迎来我的博客参观,交流:https://endwas.cn

1.Aop作用

我们都知道Spring中最关键的两大特性:IOC和AOP两者相辅相成,成就现在的Spring框架;

AOP叫面向切面编程,帮助我们在很多切入点进行增强、扩充操作,如果是传统我们就需要在所有类开头加上日志开始,末尾加上日志结束,这样不仅编程效率低下,浪费时间,代码冗余,维护也麻烦,万一有需求换方式记录日志,可不得改死;

AOP我们可以使用在很多地方,如日志模块,权限控制,事务处理等;

AOP的具体实现有两种方式

  1. JDK动态代理
    (需要被代理类实现一个接口,本质是创建一个代理类也实现该接口,然后对该方法进行增强,扩展)
  2. CGLib字节码处理
    (直接对编译好的字节码进行修改,修改对应的方法,本质直接继承该被代理类,所以该类不能设置为final)

2.Aop使用方式

在Spring中要实现aop通常我们会定义一个Aspect类,他使用的是org.aspectj.lang包下的类来帮助我们引入aop模块;

具体使用方式:
1.定义一个Aspect类
添加@Aspect注解、添加@Component注解将该类纳入spring容器;
2.定义切点
使用@PointCut注解定义在方法上,并标明想切入的点(方法)
3.对该切点进行增强
使用@Before、@Around等注解对该方法(@PointCut注解的方法,不是想切入的方法!!)

具体用法如下


@Aspect
@Component
public class logAspect {
	// 扫描所有controller类的方法
    @Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }
    

    @Before(value = "log()")
    public void before(JoinPoint joinPoint){
        System.out.println("[Aspect] before log method!");
    }

    @Around(value = "log()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[Aspect] around log before method!");
        proceedingJoinPoint.proceed();
        System.out.println("[Aspect] around log after method!");
    }

    @After(value = "log()")
    public void after(){
        System.out.println("[Aspect] after log method!");

    }

    @AfterReturning(value = "log()")
    public void afterReturning(){
        System.out.println("[Aspect] afterReturning log method!");
    }

    @AfterThrowing(value = "log()")
    public void afterThrowing(){
        System.out.println("[Aspect] afterThrowing log method!");
    }

}

3.具体注解作用

1.@Pointcut

使用参数value可以定义切点
@Pointcut(value = “execution(* com.exam.exam.controller..(…))”)
扫描所有controller包下所有类中所有返回值的方法
在这里插入图片描述

@Pointcut("@within(com.exam.exam.logaspect.LogAnnotation)")
扫描所有LogAnnotation注解标识的类,这个类以及它的子孙类都能被增强。

within()与execution()的功能类似,两者的区别是,within()定义的连接点的最小范围是类级别的(不是接口),而execution()定义的连接点的最小范围可以精确到方法的入参,因此可以认为execution()涵盖了within()的功能。

@Pointcut(“within(com.exam.exam.entity.User)”)
扫描该类及其子类,当注入使用的时候对它进行增强

target() 匹配目标类以及子类,目标类以及其子类的所有方法执行都会匹配到
@target:若当前类有注解,则对该类继承、自有、重载的方法有效。若子类无注解,则无效果。
@Annotation 匹配使用了该注解的类

@target和@within区别:两者都用于注解类
1.我们对被@target、@within注解的类中的方法会进行增强,但他子类重写的方法将不起作用;
2.对于被@target注解的子类,若子类调用父类的方法,而没有重写父类的方法那么就会被增强,但@within不会
3.对于被@within注解的父类,若父类方法子类没有重写,那么子类调用父类的方法,就会被增强,但@target不会
(2,3区别在于一个是@target子类调用父类的方法,一个是子类调用@within父类的方法)
4.@target、@within对子类新增方法都不起作用

2.@Before

用于切面前置动作,在执行方法前增强动作
一般两种用法

  1. 定义@Pointcut方法,对该方法进行前置动作
 	@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }

    @Before(value = "log()")
    public void before(JoinPoint joinPoint){
        System.out.println("[Aspect] before log method!");
    }
  1. 直接使用@within、@target等对注解进行切入
    @Before("@target(com.exam.exam.logaspect.LogAnnotation)")
    public void before1(){
    }

3.@After

用于切面后置动作,在执行方法后增强动作

  1. 定义@Pointcut方法,对该方法进行后置动作
 	@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }

    @After(value = "log()")
    public void After(JoinPoint joinPoint){
        System.out.println("[Aspect] After log method!");
    }
  1. 直接使用@within、@target等对注解进行切入
    @After("@within(com.exam.exam.logaspect.LogAnnotation)")
    public void After(){
    }

4.@Around

用于切面环绕动作,对方法进行环绕操作

  1. 对切面进行环绕,前后操作;
	@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }
    
    @Around(value = "log()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[Aspect] around log before method!");
        proceedingJoinPoint.proceed();
        System.out.println("[Aspect] around log after method!");
    }
  1. 直接使用@within、@target等对注解进行切入
	@Around(value = "@within(com.exam.exam.logaspect.LogAnnotation)")
    public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[Aspect] around log before method!");
        proceedingJoinPoint.proceed();
        System.out.println("[Aspect] around log after method!");
    }

5.@afterReturning

在方法返回后进行的后置处理

  1. 定义@Pointcut方法,对该方法进行返回后置动作
 	@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }

	@AfterReturning(value = "log()")
    public void afterReturning(){
        System.out.println("[Aspect] afterReturning log method!");
    }

  1. 直接使用@within、@target等对注解进行切入
	@AfterReturning(value = "@within(com.exam.exam.logaspect.LogAnnotation)")
    public void afterReturning(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[Aspect] afterReturning log method!");
    }

6.@AfterThrowing

在方法抛出异常后进行的后置处理

  1. 定义@Pointcut方法,对该方法进行返回后置动作
 	@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
    public void log(){
    }

	@AfterThrowing(value = "log()")
    public void AfterThrowing(){
        System.out.println("[Aspect] AfterThrowing log method!");
    }

  1. 直接使用@within、@target等对注解进行切入
	@AfterThrowing(value = "@within(com.exam.exam.logaspect.LogAnnotation)")
    public void AfterThrowing(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[Aspect] AfterThrowing log method!");
    }

4.参数ProceedingJoinPoint和JoinPoint

环绕通知(around) ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行,这也是环绕通知和前置、后置通知方法的一个最大区别。

Proceedingjoinpoint 继承了 JoinPoint 。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。

5.调用顺序

执行流程图:
在这里插入图片描述

1.当一个Aspect的时候

# 执行顺序
[Aspect] around log before method!
[Aspect] before log method!
doing test aspect!
[Aspect] afterReturning log method!
[Aspect] after log method!
[Aspect] around log after method!

2.当两个Aspect的时候

# 执行顺序
[Aspect] around log before method!
[Aspect] before log method!
[Aspect2] around log before method!
[Aspect2] before log method!
doing test aspect!
[Aspect2] afterReturning log method!
[Aspect2] after log method!
[Aspect2] around log after method!
[Aspect] afterReturning log method!
[Aspect] after log method!
[Aspect] around log after method!

3.当抛出异常的时候

[Aspect] around log before method!
[Aspect] before log method!
[Aspect] afterThrowing log method!
[Aspect] after log method!
2021-03-13 20:52:26.730 ERROR 21632 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: error] with root cause

java.lang.RuntimeException: error

我们可以看到afterReturning没有执行,around-after没有执行
after执行了!

参考:https://www.jianshu.com/p/fb109e03edec

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值