关于java:面试官Spring-注解-AfterAroundBefore-的执行顺序是

46次阅读

共计 10785 个字符,预计需要花费 27 分钟才能阅读完成。

AOP 中有 @Before@After@Around@AfterRunning 注解等等。

首先高低本人的代码,定义了切点的定义

@Aspect
@Component
public class LogApsect {private static final Logger logger = LoggerFactory.getLogger(LogApsect.class);
 
    ThreadLocal<Long> startTime = new ThreadLocal<>();
 
    // 第一个 * 代表返回类型不限
    // 第二个 * 代表所有类
    // 第三个 * 代表所有办法
    // (..) 代表参数不限
    @Pointcut("execution(public * com.lmx.blog.controller.*.*(..))")
    @Order(2)
    public void pointCut(){};
 
    @Pointcut("@annotation(com.lmx.blog.annotation.RedisCache)")
    @Order(1) // Order 代表优先级,数字越小优先级越高
    public void annoationPoint(){};
 
    @Before(value = "annoationPoint() || pointCut()")
    public void before(JoinPoint joinPoint){System.out.println("办法执行前执行......before");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("<=====================================================");
        logger.info("申请起源:=》" + request.getRemoteAddr());
        logger.info("申请 URL:" + request.getRequestURL().toString());
        logger.info("申请形式:" + request.getMethod());
        logger.info("响应办法:" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("申请参数:" + Arrays.toString(joinPoint.getArgs()));
        logger.info("------------------------------------------------------");
        startTime.set(System.currentTimeMillis());
    }
 
    // 定义须要匹配的切点表达式,同时须要匹配参数
    @Around("pointCut() && args(arg)")
    public Response around(ProceedingJoinPoint pjp,String arg) throws Throwable{System.out.println("name:" + arg);
        System.out.println("办法盘绕 start...around");
        String result = null;
        try{result = pjp.proceed().toString() + "aop String";
            System.out.println(result);
        }catch (Throwable e){e.printStackTrace();
        }
        System.out.println("办法盘绕 end...around");
        return (Response) pjp.proceed();}
 
    @After("within(com.lmx.blog.controller.*Controller)")
    public void after(){System.out.println("办法之后执行...after.");
    }
 
    @AfterReturning(pointcut="pointCut()",returning = "rst")
    public void afterRunning(Response rst){if(startTime.get() == null){startTime.set(System.currentTimeMillis());
        }
        System.out.println("办法执行完执行...afterRunning");
        logger.info("耗时(毫秒):" +  (System.currentTimeMillis() - startTime.get()));
        logger.info("返回数据:{}", rst);
        logger.info("==========================================>");
    }
 
    @AfterThrowing("within(com.lmx.blog.controller.*Controller)")
    public void afterThrowing(){System.out.println("异样呈现之后...afterThrowing");
    }
 
 
}

举荐一个 Spring Boot 基础教程及实战示例:
https://github.com/javastacks…

@Before@After@Around注解的区别大家能够自行百度下。总之就是 @Around 能够实现 @Before@After的性能,并且只须要在一个办法中就能够实现。

首先咱们来测试一个办法用于获取数据库一条记录的

@RequestMapping("/achieve")
public Response achieve(){System.out.println("办法执行 -----------");
    return Response.ok(articleDetailSercice.getPrimaryKeyById(1L));
}

以下是控制台打印的日志

办法执行前执行......before
2018-11-23 16:31:59.795 [http-nio-8888-exec-9] INFO  c.l.blog.config.LogApsect - <=====================================================
2018-11-23 16:31:59.795 [http-nio-8888-exec-9] INFO  c.l.blog.config.LogApsect - 申请起源:=》0:0:0:0:0:0:0:1
2018-11-23 16:31:59.795 [http-nio-8888-exec-9] INFO  c.l.blog.config.LogApsect - 申请 URL:http://localhost:8888/user/achieve
2018-11-23 16:31:59.795 [http-nio-8888-exec-9] INFO  c.l.blog.config.LogApsect - 申请形式:GET
2018-11-23 16:31:59.795 [http-nio-8888-exec-9] INFO  c.l.blog.config.LogApsect - 响应办法:com.lmx.blog.controller.UserController.achieve
2018-11-23 16:31:59.796 [http-nio-8888-exec-9] INFO  c.l.blog.config.LogApsect - 申请参数:[]
2018-11-23 16:31:59.796 [http-nio-8888-exec-9] INFO  c.l.blog.config.LogApsect - ------------------------------------------------------
办法执行 -----------
2018-11-23 16:31:59.806 [http-nio-8888-exec-9] DEBUG c.l.b.m.A.selectPrimaryKey - ==>  Preparing: select * from article_detail where id = ? 
2018-11-23 16:31:59.806 [http-nio-8888-exec-9] DEBUG c.l.b.m.A.selectPrimaryKey - ==>  Preparing: select * from article_detail where id = ? 
2018-11-23 16:31:59.806 [http-nio-8888-exec-9] DEBUG c.l.b.m.A.selectPrimaryKey - ==> Parameters: 1(Long)
2018-11-23 16:31:59.806 [http-nio-8888-exec-9] DEBUG c.l.b.m.A.selectPrimaryKey - ==> Parameters: 1(Long)
2018-11-23 16:31:59.814 [http-nio-8888-exec-9] DEBUG c.l.b.m.A.selectPrimaryKey - <==      Total: 1
2018-11-23 16:31:59.814 [http-nio-8888-exec-9] DEBUG c.l.b.m.A.selectPrimaryKey - <==      Total: 1
办法之后执行...after.
办法执行完执行...afterRunning
2018-11-23 16:31:59.824 [http-nio-8888-exec-9] INFO  c.l.blog.config.LogApsect - 耗时(毫秒):27
2018-11-23 16:31:59.824 [http-nio-8888-exec-9] INFO  c.l.blog.config.LogApsect - 返回数据:com.lmx.blog.common.Response@8675ce5
2018-11-23 16:31:59.824 [http-nio-8888-exec-9] INFO  c.l.blog.config.LogApsect - ==========================================>

能够看到,因为没有匹配 @Around 的规定,所以没有进行盘绕告诉。(PS:我定义的盘绕告诉意思是要合乎是 controller 包下的办法并且办法必须带有参数,而上述办法没有参数,所以只走了 @before@after办法,不合乎 @Around 的匹配逻辑)

咱们再试一下另一个带有参数的办法

@RedisCache(type = Response.class)
@RequestMapping("/sendEmail")
public Response sendEmailToAuthor(String content){System.out.println("测试执行次数");
    return Response.ok(true);
}

以下是该局部代码的 console 打印

name: 第二封邮件呢
办法盘绕 start...around
办法执行前执行......before
2018-11-23 16:34:55.347 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - <=====================================================
2018-11-23 16:34:55.347 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 申请起源:=》0:0:0:0:0:0:0:1
2018-11-23 16:34:55.347 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 申请 URL:http://localhost:8888/user/sendEmail
2018-11-23 16:34:55.348 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 申请形式:GET
2018-11-23 16:34:55.348 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 响应办法:com.lmx.blog.controller.UserController.sendEmailToAuthor
2018-11-23 16:34:55.348 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 申请参数:[第二封邮件呢]
2018-11-23 16:34:55.348 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - ------------------------------------------------------
测试执行次数
com.lmx.blog.common.Response@6d17f2fdaop String
办法盘绕 end...around
办法执行前执行......before
2018-11-23 16:34:55.349 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - <=====================================================
2018-11-23 16:34:55.349 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 申请起源:=》0:0:0:0:0:0:0:1
2018-11-23 16:34:55.349 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 申请 URL:http://localhost:8888/user/sendEmail
2018-11-23 16:34:55.349 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 申请形式:GET
2018-11-23 16:34:55.349 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 响应办法:com.lmx.blog.controller.UserController.sendEmailToAuthor
2018-11-23 16:34:55.349 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 申请参数:[第二封邮件呢]
2018-11-23 16:34:55.350 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - ------------------------------------------------------
测试执行次数
办法之后执行...after.
办法执行完执行...afterRunning
2018-11-23 16:34:55.350 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 耗时(毫秒):0
2018-11-23 16:34:55.350 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - 返回数据:com.lmx.blog.common.Response@79f85428
2018-11-23 16:34:55.350 [http-nio-8888-exec-2] INFO  c.l.blog.config.LogApsect - ==========================================>

不言而喻,该办法合乎 @Around 盘绕告诉的匹配规定,所以进入了 @Around 的逻辑,然而发现了问题,所有的办法都被执行了 2 次,不论是切面层还是办法层。(有人预计要问我不是用的自定义注解 @RedisCache(type = Response.class) 么。为什么会合乎 @Around的匹配规定呢,这个等会在上面说)

咱们剖析日志的打印程序能够得出,在执行盘绕办法时候,会优先进入 @Around下的办法。@Around的办法再贴一下代码。

// 定义须要匹配的切点表达式,同时须要匹配参数
@Around("pointCut() && args(arg)")
public Response around(ProceedingJoinPoint pjp,String arg) throws Throwable{System.out.println("name:" + arg);
    System.out.println("办法盘绕 start...around");
    String result = null;
    try{result = pjp.proceed().toString() + "aop String";
        System.out.println(result);
    }catch (Throwable e){e.printStackTrace();
    }
    System.out.println("办法盘绕 end...around");
    return (Response) pjp.proceed();}

打印了前两行代码当前,转而去执行了 @Before办法,是因为中途触发了 ProceedingJoinPoint.proceed() 办法。这个办法的作用是执行被代理的办法,也就是说执行了这个办法之后会执行咱们 controller 的办法,而后执行 @before@after,而后回到 @Around 执行未执行的办法,最初执行 @afterRunning,如果有异样抛出能执行 @AfterThrowing

也就是说盘绕的执行程序是 @Around→@Before→@After→@Around执行 ProceedingJoinPoint.proceed() 之后的操作→@AfterRunning(如果有异样→@AfterThrowing)

而咱们上述的日志相当于把上述后果执行了 2 遍,根本原因在于 ProceedingJoinPoint.proceed() 这个办法,能够发现在 @Around 办法中咱们应用了 2 次这个办法,然而每次调用这个办法时都会走一次 @Before→@After→@Around 执行 ProceedingJoinPoint.proceed() 之后的操作→@AfterRunning(如果有异样→@AfterThrowing)。因而问题是呈现在这里。所以更改 @Around 局部的代码即可解决该问题。更改之后的代码如下:

@Around("pointCut() && args(arg)")
public Response around(ProceedingJoinPoint pjp,String arg) throws Throwable{System.out.println("name:" + arg);
    System.out.println("办法盘绕 start...around");
    String result = null;
    Object object = pjp.proceed();
    try{result = object.toString() + "aop String";
        System.out.println(result);
    }catch (Throwable e){e.printStackTrace();
    }
    System.out.println("办法盘绕 end...around");
    return (Response) object;
}

更改代码之后的运行后果

name: 第二封邮件呢
办法盘绕 start...around
办法执行前执行......before
2018-11-23 16:52:14.315 [http-nio-8888-exec-4] INFO  c.l.blog.config.LogApsect - <=====================================================
2018-11-23 16:52:14.315 [http-nio-8888-exec-4] INFO  c.l.blog.config.LogApsect - 申请起源:=》0:0:0:0:0:0:0:1
2018-11-23 16:52:14.315 [http-nio-8888-exec-4] INFO  c.l.blog.config.LogApsect - 申请 URL:http://localhost:8888/user/sendEmail
2018-11-23 16:52:14.315 [http-nio-8888-exec-4] INFO  c.l.blog.config.LogApsect - 申请形式:GET
2018-11-23 16:52:14.316 [http-nio-8888-exec-4] INFO  c.l.blog.config.LogApsect - 响应办法:com.lmx.blog.controller.UserController.sendEmailToAuthor
2018-11-23 16:52:14.316 [http-nio-8888-exec-4] INFO  c.l.blog.config.LogApsect - 申请参数:[第二封邮件呢]
2018-11-23 16:52:14.316 [http-nio-8888-exec-4] INFO  c.l.blog.config.LogApsect - ------------------------------------------------------
测试执行次数
com.lmx.blog.common.Response@1b1c76afaop String
办法盘绕 end...around
办法之后执行...after.
办法执行完执行...afterRunning
2018-11-23 16:52:14.316 [http-nio-8888-exec-4] INFO  c.l.blog.config.LogApsect - 耗时(毫秒):0
2018-11-23 16:52:14.316 [http-nio-8888-exec-4] INFO  c.l.blog.config.LogApsect - 返回数据:com.lmx.blog.common.Response@1b1c76af
2018-11-23 16:52:14.316 [http-nio-8888-exec-4] INFO  c.l.blog.config.LogApsect - ==========================================>

回到上述未解决的问题,为什么我定义了切面的另一个注解还能够进入 @Around 办法呢?

因为咱们的办法依然在 controller 下,因而满足该需要,如果咱们定义了 controller 包下的某个 controller 才有用。例如:

@Pointcut("execution(public * com.lmx.blog.controller.UserController.*(..))")

而如果咱们方才定义的办法是写在 TestController 之下的,那么就不合乎 @Around办法的匹配规定了,也不合乎 @before@after的注解规定,因而不会匹配任何一个规定,如果须要匹配特定的办法,能够用自定义的注解模式或者个性 controller 下的办法

①:个性的注解模式

@Pointcut("@annotation(com.lmx.blog.annotation.RedisCache)")
@Order(1) // Order 代表优先级,数字越小优先级越高
public void annoationPoint(){};

而后在所须要的办法上退出 @RedisCache注解,在 @Before@After@Around 等办法上增加该切点的办法名(“annoationPoint()”),如果有多个注解须要匹配则用 || 隔开

②:指定 controller 或者指定 controller 下的办法

@Pointcut("execution(public * com.lmx.blog.controller.UserController.*(..))")
@Order(2)
public void pointCut(){};

该局部代码是指定了 com.lmx.blog.controller包下的 UserController 下的所有办法。

第一个 * 代表的是返回类型不限

第二个 * 代表的是该 controller 下的所有办法,(..)代表的是参数不限

总结

当办法合乎切点规定不合乎盘绕告诉的规定时候,执行的程序如下

@Before→@After→@AfterRunning(如果有异样→@AfterThrowing)

当办法合乎切点规定并且合乎盘绕告诉的规定时候,执行的程序如下

@Around→@Before→@Around→@After 执行 ProceedingJoinPoint.proceed() 之后的操作→@AfterRunning(如果有异样→@AfterThrowing)

原文链接:https://blog.csdn.net/lmx1252…

版权申明:本文为 CSDN 博主「Leonis 丶 L」的原创文章,遵循 CC 4.0 BY-SA 版权协定,转载请附上原文出处链接及本申明。

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2021 最新版)

2. 别在再满屏的 if/ else 了,试试策略模式,真香!!

3. 卧槽!Java 中的 xx ≠ null 是什么新语法?

4.Spring Boot 2.5 重磅公布,光明模式太炸了!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0