共计 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 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!