spring中AOP 注解开发示例详解
一、简介
aop主要包含了通知、切点和连接点等术语,介绍如下:
通知(advice)
通知定义了切面是什么以及何时调用,何时调用包含以下几种
- before 在方法被调用之前调用通知
- after 在方法完成之后调用通知,无论方法执行是否成功
- after-returning 在方法成功执行之后调用通知
- after-throwing 在方法抛出异常后调用通知
- around 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
切点(pointcut)
通知定义了切面的什么和何时,切点定义了何处,切点的定义会匹配通知所要织入的一个或多个连接点,
我们通常使用明确的类的方法名称来指定这些切点,或是利用正则表达式定义匹配的类和方法名称来指定这些切点。
连接点(joinpoint)
连接点是在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时,抛出异常时,甚至是修改一个字段时,
切面代码可以利用这些连接点插入到应用的正常流程中,并添加新的行为,如日志、安全、事务、缓存等。
-
joinpoint.getsignature().getdeclaringtypename()
可以获取到调用方法的类名(包括包名), -
joinpoint.getsignature().getname()
可以获取方法名, -
arrays.tostring(joinpoint.getargs())
得到的是方法调用的参数列表, -
joinpoint.proceed()
可以得到方法的返回结果
二、注解开发
声明一个切面,只需要在类名上添加@aspect属性即可,具体的连接点,我们用@pointcut和@before、@after等标注。
在声明前 我们需要依赖配置pom
<dependency> <groupid>org.aspectj</groupid> <artifactid>aspectjrt</artifactid> <version>1.6.11</version> </dependency> <dependency> <groupid>org.aspectj</groupid> <artifactid>aspectjweaver</artifactid> <version>1.6.11</version> </dependency>
例子
package com.ganji.demo.service.aspect; import org.aspectj.lang.proceedingjoinpoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.service; /** * created by admin on 2015/9/2. */ @aspect @service public class xmlaopdemouserlog { // 配置切点 及要传的参数 @pointcut("execution(* com.ganji.demo.service.user.userservice.getdemouser(..)) && args(id)") public void pointcut(int id) { } // 配置连接点 方法开始执行时通知 @before("pointcut(id)") public void beforelog(int id) { system.out.println("开始执行前置通知 日志记录:"+id); } // 方法执行完后通知 @after("pointcut(id)") public void afterlog(int id) { system.out.println("开始执行后置通知 日志记录:"+id); } // 执行成功后通知 @afterreturning("pointcut(id)") public void afterreturninglog(int id) { system.out.println("方法成功执行后通知 日志记录:"+id); } // 抛出异常后通知 @afterthrowing("pointcut(id)") public void afterthrowinglog(int id) { system.out.println("方法抛出异常后执行通知 日志记录"+id); } // 环绕通知 @around("pointcut(id)") public object aroundlog(proceedingjoinpoint joinpoint,int id) { object result = null; try { system.out.println("环绕通知开始 日志记录"+id); long start = system.currenttimemillis(); //有返回参数 则需返回值 result = joinpoint.proceed(); long end = system.currenttimemillis(); system.out.println("总共执行时长" + (end - start) + " 毫秒"); system.out.println("环绕通知结束 日志记录"); } catch (throwable t) { system.out.println("出现错误"); } return result; } }
aop切面中的同步问题
在weblogaspect切面中,分别通过dobefore和doafterreturning两个独立函数实现了切点头部和切点返回后执行的内容,
若我们想统计请求的处理时间,就需要在dobefore处记录时间,并在doafterreturning处通过当前时间与开始处记录的时间计算
得到请求处理的消耗时间。
那么我们是否可以在weblogaspect切面中定义一个成员变量来给dobefore和doafterreturning一起访问呢?是否会有同步问题呢?
的确,直接在这里定义基本类型会有同步问题,所以我们可以引入threadlocal对象,像下面这样进行记录:
@aspect @component public class weblogaspect { private logger logger = logger.getlogger(getclass()); threadlocal<long> starttime = new threadlocal<>(); @pointcut("execution(public * com.juzi.web..*.*(..))") public void weblog(){} @before("weblog()") public void dobefore(joinpoint joinpoint) throws throwable { starttime.set(system.currenttimemillis()); // 省略日志记录内容 } @afterreturning(returning = "ret", pointcut = "weblog()") public void doafterreturning(object ret) throws throwable { // 处理完请求,返回内容 logger.info("response : " + ret); logger.info("spend time : " + (system.currenttimemillis() - starttime.get())); } }
aop切面的优先级
由于通过aop实现,程序得到了很好的解耦,但是也会带来一些问题,比如:我们可能会对web层做多个切面,校验用户,
校验头信息等等,这个时候经常会碰到切面的处理顺序问题。
所以,我们需要定义每个切面的优先级,我们需要@order(i)注解来标识切面的优先级。i的值越小,优先级越高。
假设我们还有一个切面是checknameaspect用来校验name必须为didi,我们为其设置@order(10),
而上文中weblogaspect设置为@order(5),所以weblogaspect有更高的优先级,这个时候执行顺序是这样的:
在@before中优先执行@order(5)的内容,再执行@order(10)的内容
在@after和@afterreturning中优先执行@order(10)的内容,再执行@order(5)的内容
所以我们可以这样子总结:
- 在切入点前的操作,按order的值由小到大执行
- 在切入点后的操作,按order的值由大到小执行
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。