SpringBoot 使用AOP

SpringBoot AOP

AOP(面向切面编程)是SpringBoot的两大核心功能之一,功能非常强大,为解耦提供了非常优秀的解决方案。

AOP术语

  • 执行点(Excutepoint):类初始化,方法调用
  • 连接点(JoinPoint):执行点 和 方位的组合,可以确定JoinPoint。比如类初始化前,初始化后,方法调用前,方法调用后。
  • 切点(PointCut):在众多的执行点中,定位合适的执行点。ExcutePoint相当于数据库中的记录,Pointcut相当于查询条件。
  • 增强(Advice):织入到目标类连接点上的一段代码,除了代码之后,还有执行点的方位信息。
  • 目标对象(Target):增强逻辑的织入目标类。
  • 引介(Introduction):一种特殊的增强,为类增加一些额外的属性和方法,动态为业务类增加其他接口的实现逻辑,让业务类成为这个接口的实现类。
  • 代理(proxy):一个类被AOP织入后,产生了一个结果类,它便是融合了原类和增强逻辑的代理类。
  • 切面(Aspect):切面由切点和增强组成既包括横切逻辑定义,也包括连接点定义。

AOP重点:

  • 如何通过切点和增强定位到连接点
  • 如何在增强中编写切面的代码

实现方式

  • 添加MAVEN依赖

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    

正则匹配

  • 创建切面类

    /**
     * 日志切面
     */
    
    @Aspect
    @Component
    public class LogAspect {
        @Pointcut("execution(public * com.xncoding.aop.controller.*.*(..))")
        public void webLog(){}
    
        @Before("webLog()")
        public void deBefore(JoinPoint joinPoint) throws Throwable {
            // 接收到请求,记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            // 记录下请求内容
            System.out.println("URL : " + request.getRequestURL().toString());
            System.out.println("HTTP_METHOD : " + request.getMethod());
            System.out.println("IP : " + request.getRemoteAddr());
            System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
    
        }
    
        @AfeterReturning(returning = "ret", pointcut = "webLog()")
        public void doAfterReturning(Object ret) throws Throwable {
            // 处理完请求,返回内容
            System.out.println("方法的返回值 : " + ret);
        }
    
        //后置异常通知
        @AfterThrowing("webLog()")
        public void throwss(JoinPoint jp){
            System.out.println("方法异常时执行.....");
        }
    
        //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
        @After("webLog()")
        public void after(JoinPoint jp){
            System.out.println("方法最后执行.....");
        }
    
        //环绕通知,环绕增强,相当于MethodInterceptor
        @Around("webLog()")
        public Object arround(ProceedingJoinPoint pjp) {
            System.out.println("方法环绕start.....");
            try {
                Object o =  pjp.proceed();
                System.out.println("方法环绕proceed,结果是 :" + o);
                return o;
            } catch (Throwable e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    

    结果:

    方法环绕start.....
    URL : http://localhost:8092/first
    HTTP_METHOD : GET
    IP : 0:0:0:0:0:0:0:1
    CLASS_METHOD : com.xncoding.aop.controller.UserController.first
    ARGS : []
    方法环绕proceed,结果是 :first controller
    方法最后执行.....
    方法的返回值 : first controller
    

    切面注解说明

    • @Aspect 作用是把当前类标识为一个切面供容器读取
    • @Pointcut 定义切点,切点方法不用任何代码,返回值是void,重要的是条件表达式
    • @Before 标识一个前置增强方法,相当于BeforeAdvice的功能
    • @AfterReturning 后置增强,相当于AfterReturningAdvice,方法退出时执行
    • @AfterThrowing 异常抛出增强,相当于ThrowsAdvice
    • @After final增强,不管是抛出异常或者正常退出都会执行
    • @Around 环绕增强,相当于MethodInterceptor

    方法参数说明

    除了@Around外,每个方法里都可以加或者不加参数JoinPoint。

    JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。

    @Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。

    @AfterReturning方法里,可以加returning = “xxx”,xxx即为在controller里方法的返回值,本例中的返回值是”first controller”。

    @AfterThrowing方法里,可以加throwing = “XXX”,读取异常信息,如本例中可以改为:

    //后置异常通知
    @AfterThrowing(throwing = "ex", pointcut = "webLog()")
    public void throwss(JoinPoint jp, Exception ex){
        System.out.println("方法异常时执行.....");
    }
    

    一般常用的有before和afterReturn组合,或者单独使用Around,即可获取方法开始前和结束后的切面。

    关于切点PointCut

    execution函数用于匹配方法执行的连接点,语法为:

    execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))

    参数部分允许使用通配符:

    表达式可由多个切点函数通过逻辑运算组成,与(&&)、 或(||)、 非(!)

使用注解实现AOP

自定义注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Rentention(RetentionPolicy.RUNTIME)
public  UserAccess {
    String desc() default "无信息";
}

创建切面类

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


@Component
@Aspect
public class UserAccessAspect {

    @Pointcut(value = "@annotation(com.xncoding.aop.aspect.UserAccess)")
    public void access() {

    }

    @Before("access()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        System.out.println("second before");
    }

    @Around("@annotation(userAccess)")
    public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {
        //获取注解里的值
        System.out.println("second around:" + userAccess.desc());
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        }
    }
}

主要看一下@Around注解这里,如果需要获取在controller注解中赋给UserAccess的desc里的值,就需要这种写法,这样UserAccess参数就有值了。

spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。

对于上面的例子就是,先外层的就是对所有controller的切面,内层就是自定义注解的。 那不同的切面,顺序怎么决定呢,尤其是同格式的切面处理,譬如两个execution的情况,那spring就是随机决定哪个在外哪个在内了。

所以大部分情况下,我们需要指定顺序,最简单的方式就是在Aspect切面类上加上@Order(1)注解即可,order越小最先执行,也就是位于最外层。像一些全局处理的就可以把order设小一点,具体到某个细节的就设大一点

参考:https://www.xncoding.com/2017/07/24/spring/sb-aop.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容