面試官:Spring 注解 @After,@Around,@Before 的執(zhí)行順序是?
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)??

AOP中有@Before,@After,@Around,@AfterRunning注解等等。
首先上下自己的代碼,定義了切點(diǎn)的定義
@Aspect
@Component
public?class?LogApsect?{
?
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(LogApsect.class);
?
????ThreadLocal?startTime?=?new?ThreadLocal<>();
?
????//?第一個(gè)*代表返回類型不限
????//?第二個(gè)*代表所有類
????//?第三個(gè)*代表所有方法
????//?(..)?代表參數(shù)不限
????@Pointcut("execution(public?*?com.lmx.blog.controller.*.*(..))")
????@Order(2)
????public?void?pointCut(){};
?
????@Pointcut("@annotation(com.lmx.blog.annotation.RedisCache)")
????@Order(1)?//?Order?代表優(yōu)先級(jí),數(shù)字越小優(yōu)先級(jí)越高
????public?void?annoationPoint(){};
?
????@Before(value?=?"annoationPoint()?||?pointCut()")
????public?void?before(JoinPoint?joinPoint){
????????System.out.println("方法執(zhí)行前執(zhí)行......before");
????????ServletRequestAttributes?attributes?=?(ServletRequestAttributes)?RequestContextHolder.getRequestAttributes();
????????HttpServletRequest?request?=?attributes.getRequest();
????????logger.info("<=====================================================");
????????logger.info("請(qǐng)求來(lái)源:?=》"?+?request.getRemoteAddr());
????????logger.info("請(qǐng)求URL:"?+?request.getRequestURL().toString());
????????logger.info("請(qǐng)求方式:"?+?request.getMethod());
????????logger.info("響應(yīng)方法:"?+?joinPoint.getSignature().getDeclaringTypeName()?+?"."?+?joinPoint.getSignature().getName());
????????logger.info("請(qǐng)求參數(shù):"?+?Arrays.toString(joinPoint.getArgs()));
????????logger.info("------------------------------------------------------");
????????startTime.set(System.currentTimeMillis());
????}
?
????//?定義需要匹配的切點(diǎn)表達(dá)式,同時(shí)需要匹配參數(shù)
????@Around("pointCut()?&&?args(arg)")
????public?Response?around(ProceedingJoinPoint?pjp,String?arg)?throws?Throwable{
????????System.out.println("name:"?+?arg);
????????System.out.println("方法環(huán)繞start...around");
????????String?result?=?null;
????????try{
????????????result?=?pjp.proceed().toString()?+?"aop?String";
????????????System.out.println(result);
????????}catch?(Throwable?e){
????????????e.printStackTrace();
????????}
????????System.out.println("方法環(huán)繞end...around");
????????return?(Response)?pjp.proceed();
????}
?
????@After("within(com.lmx.blog.controller.*Controller)")
????public?void?after(){
????????System.out.println("方法之后執(zhí)行...after.");
????}
?
????@AfterReturning(pointcut="pointCut()",returning?=?"rst")
????public?void?afterRunning(Response?rst){
????????if(startTime.get()?==?null){
????????????startTime.set(System.currentTimeMillis());
????????}
????????System.out.println("方法執(zhí)行完執(zhí)行...afterRunning");
????????logger.info("耗時(shí)(毫秒):"?+??(System.currentTimeMillis()?-?startTime.get()));
????????logger.info("返回?cái)?shù)據(jù):{}",?rst);
????????logger.info("==========================================>");
????}
?
????@AfterThrowing("within(com.lmx.blog.controller.*Controller)")
????public?void?afterThrowing(){
????????System.out.println("異常出現(xiàn)之后...afterThrowing");
????}
?
?
}
@Before,@After,@Around注解的區(qū)別大家可以自行百度下。
總之就是@Around可以實(shí)現(xiàn)@Before和@After的功能,并且只需要在一個(gè)方法中就可以實(shí)現(xiàn)。
首先我們來(lái)測(cè)試一個(gè)方法用于獲取數(shù)據(jù)庫(kù)一條記錄的
@RequestMapping("/achieve")
public?Response?achieve(){
????System.out.println("方法執(zhí)行-----------");
????return?Response.ok(articleDetailSercice.getPrimaryKeyById(1L));
}
以下是控制臺(tái)打印的日志
方法執(zhí)行前執(zhí)行......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 -?請(qǐng)求來(lái)源:?=》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 -?請(qǐng)求URL:http://localhost:8888/user/achieve
2018-11-23 16:31:59.795 [http-nio-8888-exec-9] INFO c.l.blog.config.LogApsect -?請(qǐng)求方式:GET
2018-11-23 16:31:59.795 [http-nio-8888-exec-9] INFO c.l.blog.config.LogApsect -?響應(yīng)方法:com.lmx.blog.controller.UserController.achieve
2018-11-23 16:31:59.796 [http-nio-8888-exec-9] INFO c.l.blog.config.LogApsect -?請(qǐng)求參數(shù):[]
2018-11-23?16:31:59.796?[http-nio-8888-exec-9]?INFO??c.l.blog.config.LogApsect?-?------------------------------------------------------
方法執(zhí)行-----------
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
方法之后執(zhí)行...after.
方法執(zhí)行完執(zhí)行...afterRunning
2018-11-23 16:31:59.824 [http-nio-8888-exec-9] INFO c.l.blog.config.LogApsect -?耗時(shí)(毫秒):27
2018-11-23 16:31:59.824 [http-nio-8888-exec-9] INFO c.l.blog.config.LogApsect -?返回?cái)?shù)據(jù):com.lmx.blog.common.Response@8675ce5
2018-11-23?16:31:59.824?[http-nio-8888-exec-9]?INFO??c.l.blog.config.LogApsect?-?==========================================>
可以看到,因?yàn)闆](méi)有匹配@Around的規(guī)則,所以沒(méi)有進(jìn)行環(huán)繞通知。(PS:我定義的環(huán)繞通知意思是要符合是 controller 包下的方法并且方法必須帶有參數(shù),而上述方法沒(méi)有參數(shù),所以只走了@before和@after方法,不符合@Around的匹配邏輯)
我們?cè)僭囈幌铝硪粋€(gè)帶有參數(shù)的方法
@RedisCache(type?=?Response.class)
@RequestMapping("/sendEmail")
public?Response?sendEmailToAuthor(String?content){
????System.out.println("測(cè)試執(zhí)行次數(shù)");
????return?Response.ok(true);
}
以下是該部分代碼的console打印
name:第二封郵件呢
方法環(huán)繞start...around
方法執(zhí)行前執(zhí)行......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 -?請(qǐng)求來(lái)源:?=》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 -?請(qǐng)求URL:http://localhost:8888/user/sendEmail
2018-11-23 16:34:55.348 [http-nio-8888-exec-2] INFO c.l.blog.config.LogApsect -?請(qǐng)求方式:GET
2018-11-23 16:34:55.348 [http-nio-8888-exec-2] INFO c.l.blog.config.LogApsect -?響應(yīng)方法:com.lmx.blog.controller.UserController.sendEmailToAuthor
2018-11-23 16:34:55.348 [http-nio-8888-exec-2] INFO c.l.blog.config.LogApsect -?請(qǐng)求參數(shù):[第二封郵件呢]
2018-11-23?16:34:55.348?[http-nio-8888-exec-2]?INFO??c.l.blog.config.LogApsect?-?------------------------------------------------------
測(cè)試執(zhí)行次數(shù)
com.lmx.blog.common.Response@6d17f2fdaop?String
方法環(huán)繞end...around
方法執(zhí)行前執(zhí)行......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 -?請(qǐng)求來(lái)源:?=》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 -?請(qǐng)求URL:http://localhost:8888/user/sendEmail
2018-11-23 16:34:55.349 [http-nio-8888-exec-2] INFO c.l.blog.config.LogApsect -?請(qǐng)求方式:GET
2018-11-23 16:34:55.349 [http-nio-8888-exec-2] INFO c.l.blog.config.LogApsect -?響應(yīng)方法:com.lmx.blog.controller.UserController.sendEmailToAuthor
2018-11-23 16:34:55.349 [http-nio-8888-exec-2] INFO c.l.blog.config.LogApsect -?請(qǐng)求參數(shù):[第二封郵件呢]
2018-11-23?16:34:55.350?[http-nio-8888-exec-2]?INFO??c.l.blog.config.LogApsect?-?------------------------------------------------------
測(cè)試執(zhí)行次數(shù)
方法之后執(zhí)行...after.
方法執(zhí)行完執(zhí)行...afterRunning
2018-11-23 16:34:55.350?[http-nio-8888-exec-2] INFO c.l.blog.config.LogApsect -?耗時(shí)(毫秒):0
2018-11-23 16:34:55.350?[http-nio-8888-exec-2] INFO c.l.blog.config.LogApsect -?返回?cái)?shù)據(jù):com.lmx.blog.common.Response@79f85428
2018-11-23?16:34:55.350?[http-nio-8888-exec-2]?INFO??c.l.blog.config.LogApsect?-?==========================================>
顯而易見(jiàn),該方法符合 @Around 環(huán)繞通知的匹配規(guī)則,所以進(jìn)入了@Around的邏輯,但是發(fā)現(xiàn)了問(wèn)題,所有的方法都被執(zhí)行了2次,不管是切面層還是方法層。(有人估計(jì)要問(wèn)我不是用的自定義注解 @RedisCache(type = Response.class) 么。為什么會(huì)符合 @Around的匹配規(guī)則呢,這個(gè)等會(huì)在下面說(shuō))
我們分析日志的打印順序可以得出,在執(zhí)行環(huán)繞方法時(shí)候,會(huì)優(yōu)先進(jìn)入 @Around下的方法。@Around的方法再貼一下代碼。
//?定義需要匹配的切點(diǎn)表達(dá)式,同時(shí)需要匹配參數(shù)
@Around("pointCut()?&&?args(arg)")
public?Response?around(ProceedingJoinPoint?pjp,String?arg)?throws?Throwable{
????System.out.println("name:"?+?arg);
????System.out.println("方法環(huán)繞start...around");
????String?result?=?null;
????try{
????????result?=?pjp.proceed().toString()?+?"aop?String";
????????System.out.println(result);
????}catch?(Throwable?e){
????????e.printStackTrace();
????}
????System.out.println("方法環(huán)繞end...around");
????return?(Response)?pjp.proceed();
}
打印了前兩行代碼以后,轉(zhuǎn)而去執(zhí)行了 @Before方法,是因?yàn)橹型居|發(fā)了 ?ProceedingJoinPoint.proceed() 方法。
這個(gè)方法的作用是執(zhí)行被代理的方法,也就是說(shuō)執(zhí)行了這個(gè)方法之后會(huì)執(zhí)行我們controller的方法,而后執(zhí)行 @before,@after,然后回到@Around執(zhí)行未執(zhí)行的方法,最后執(zhí)行 @afterRunning,如果有異常拋出能執(zhí)行 @AfterThrowing
也就是說(shuō)環(huán)繞的執(zhí)行順序是 ?@Around→@Before→@After→@Around執(zhí)行 ProceedingJoinPoint.proceed() 之后的操作→@AfterRunning(如果有異常→@AfterThrowing)
而我們上述的日志相當(dāng)于把上述結(jié)果執(zhí)行了2遍,根本原因在于 ProceedingJoinPoint.proceed() 這個(gè)方法,可以發(fā)現(xiàn)在@Around 方法中我們使用了2次這個(gè)方法,然而每次調(diào)用這個(gè)方法時(shí)都會(huì)走一次@Before→@After→@Around執(zhí)行 ProceedingJoinPoint.proceed() 之后的操作→@AfterRunning(如果有異常→@AfterThrowing)。
因此問(wèn)題是出現(xiàn)在這里。所以更改@Around部分的代碼即可解決該問(wèn)題。更改之后的代碼如下:
@Around("pointCut()?&&?args(arg)")
public?Response?around(ProceedingJoinPoint?pjp,String?arg)?throws?Throwable{
????System.out.println("name:"?+?arg);
????System.out.println("方法環(huán)繞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("方法環(huán)繞end...around");
????return?(Response)?object;
}
更改代碼之后的運(yùn)行結(jié)果
name:第二封郵件呢
方法環(huán)繞start...around
方法執(zhí)行前執(zhí)行......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 -?請(qǐng)求來(lái)源:?=》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 -?請(qǐng)求URL:http://localhost:8888/user/sendEmail
2018-11-23 16:52:14.315 [http-nio-8888-exec-4] INFO c.l.blog.config.LogApsect -?請(qǐng)求方式:GET
2018-11-23 16:52:14.316 [http-nio-8888-exec-4] INFO c.l.blog.config.LogApsect -?響應(yīng)方法:com.lmx.blog.controller.UserController.sendEmailToAuthor
2018-11-23 16:52:14.316 [http-nio-8888-exec-4] INFO c.l.blog.config.LogApsect -?請(qǐng)求參數(shù):[第二封郵件呢]
2018-11-23?16:52:14.316?[http-nio-8888-exec-4]?INFO??c.l.blog.config.LogApsect?-?------------------------------------------------------
測(cè)試執(zhí)行次數(shù)
com.lmx.blog.common.Response@1b1c76afaop?String
方法環(huán)繞end...around
方法之后執(zhí)行...after.
方法執(zhí)行完執(zhí)行...afterRunning
2018-11-23 16:52:14.316 [http-nio-8888-exec-4] INFO c.l.blog.config.LogApsect -?耗時(shí)(毫秒):0
2018-11-23 16:52:14.316 [http-nio-8888-exec-4] INFO c.l.blog.config.LogApsect -?返回?cái)?shù)據(jù):com.lmx.blog.common.Response@1b1c76af
2018-11-23?16:52:14.316?[http-nio-8888-exec-4]?INFO??c.l.blog.config.LogApsect?-?==========================================>
回到上述未解決的問(wèn)題,為什么我定義了切面的另一個(gè)注解還可以進(jìn)入@Around方法呢?
因?yàn)槲覀兊姆椒ㄈ匀辉赾ontroller下,因此滿足該需求,如果我們定義了controller包下的某個(gè)controller才有用。
例如:
@Pointcut("execution(public?*?com.lmx.blog.controller.UserController.*(..))")
而如果我們剛才定義的方法是寫(xiě)在 TestController 之下的,那么就不符合 @Around方法的匹配規(guī)則了,也不符合@before和@after的注解規(guī)則,因此不會(huì)匹配任何一個(gè)規(guī)則,如果需要匹配特定的方法,可以用自定義的注解形式或者特性controller下的方法
①:特性的注解形式
@Pointcut("@annotation(com.lmx.blog.annotation.RedisCache)")
@Order(1)?//?Order?代表優(yōu)先級(jí),數(shù)字越小優(yōu)先級(jí)越高
public?void?annoationPoint(){};
然后在所需要的方法上加入 @RedisCache注解,在@Before,@After,@Around等方法上添加該切點(diǎn)的方法名(“annoationPoint()”),如果有多個(gè)注解需要匹配則用 || 隔開(kāi)
②:指定controller或者指定controller下的方法
@Pointcut("execution(public?*?com.lmx.blog.controller.UserController.*(..))")
@Order(2)
public?void?pointCut(){};
該部分代碼是指定了 com.lmx.blog.controller包下的UserController下的所有方法。
第一個(gè)*代表的是返回類型不限
第二個(gè)*代表的是該controller下的所有方法,(..)代表的是參數(shù)不限
總結(jié)
當(dāng)方法符合切點(diǎn)規(guī)則不符合環(huán)繞通知的規(guī)則時(shí)候,執(zhí)行的順序如下
@Before→@After→@AfterRunning(如果有異常→@AfterThrowing)
當(dāng)方法符合切點(diǎn)規(guī)則并且符合環(huán)繞通知的規(guī)則時(shí)候,執(zhí)行的順序如下
@Around→@Before→@Around→@After執(zhí)行 ProceedingJoinPoint.proceed() 之后的操作→@AfterRunning(如果有異常→@AfterThrowing)
原文鏈接:https://blog.csdn.net/lmx125254/article/details/84398412
版權(quán)聲明:本文為CSDN博主「Leonis丶L」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
1.?牛了!通過(guò) Java 技術(shù)手段,獲取女朋友定位地址...
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)

