一、简介
开发过程中我们往往需要写许多例如:
1
2
3
4
5
6
7
8
|
@getmapping ( "/id/get" )
public result getbyid( string id) throws exception{
log.info( "请求参数为:" +id);
verify( new verifyparam( "部门id" , id));
result result = new result( "通过id获取部门信息成功!" , service.querybyid(id));
return result;
}
|
打印请求参数以及返回参数的方法,而这些操作存在于每个方法之中,使得我们代码较为冗余,为此我们可以通过动态代理将打印参数和打印返回报文作为切面,使用切入点表达式将其切入至每个方法之中。
二、步骤
1、引入aop相关的依赖:
1
2
3
4
5
|
<!--aop相关的依赖-->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-aop</artifactid>
</dependency>
|
引入依赖后spring-aop会加载其需要的依赖,spring默认使用aspectj实现通知
其中aspectjweaver.jar中包含了解析aspectj切入点表达式的文件,使用切入点表达式处理事务的时候也需要加入此依赖。
2、配置:
1)、创建配置类:
1
2
3
4
5
6
7
8
|
/**
* @功能描述:用于controller层操作的aop类
* @author administrator
*/
@component // 将对象交由spring进行管理
@aspect // 代表此类为一个切面类
public class controlleraop {
}
|
其中@aspect 注解代表其为一个切面管理类,可以在其下定义切入点表达式,aspectj框架会进行解析。
2)、定义切入点表达式:
1
2
3
|
@pointcut ( "execution(public * com.hzt.manage.*.web.controller..*.*(..))" ) // 切入点表达式
public void privilege() {
}
|
其中,@pointcut代表此方法为一个切入点表达式。其value值为切入点表达式,其中value可以省略其大致格式为:
@注解(表达标签+表达式格式)
的格式,spring aop支持的aspectj切入点指示符如下:
1、 execution:用于匹配方法执行的连接点;
2、within:用于匹配指定类型内的方法执行;
3、this:用于匹配当前aop代理对象类型的执行方法;注意是aop代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
4、target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
5、args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
6、@within:用于匹配所以持有指定注解类型内的方法;
7、@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
8、@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
9、@annotation:用于匹配当前执行方法持有指定注解的方法;
10、bean:spring aop扩展的,aspectj没有对于指示符,用于匹配特定名称的bean对象的执行方法;
11、reference pointcut:表示引用其他命名切入点,只有@apectj风格支持,schema风格不支持。
args中定义了切入点表达式方法执行时候的参数:
1
2
3
|
@pointcut (value= "execution(public * com.hzt.manage.*.web.controller..*.*(..))&&args(param)" ,argnames= "param" ) // 切入点表达式
public void privilege1(string param) {
}
|
我们重点介绍 execution 方法连接点的表达式,其大概结构为:
1
|
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws -pattern?)
|
1、修饰符匹配(modifier-pattern?)(可省略)
2、返回值匹配(ret-type-pattern)可以为*表示任何返回值 ,如 (string) 代表只筛选返回string类型的切入点 ,全路径的类名等(不可省略)
3、类路径匹配(declaring-type-pattern?)如*.manage代表一级包为任意,二级包为manage的名称。*..manage代表所有manage包下的子类包。com..*.comtroller代表com包下所有的controller包等,*代表所有包都匹配。(不可省略)
4、方法名匹配(name-pattern)可以指定方法名 或者 *代表所有, get* 代表以get开头的所有方法,也可指定前缀*get代表任意后缀为get的方法(不可省略)
5、参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“*”来表示匹配任意类型的参数,如(string)表示匹配一个string参数的方法;(*,string) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是string类型;可以用(..)表示任意参数(不可省略)
6、异常类型匹配(throws-pattern?)
3、定义切面方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@around ( "privilege()" )
public object around(proceedingjoinpoint pjd) throws throwable {
// 获取方法名
string classname = pjd.getsignature().getclass().getname();
// 获取执行的方法名称
string methodname = pjd.getsignature().getname();
/** 初始化日志打印 */
logger log = loggerfactory.getlogger(classname);
// 定义返回参数
object result = null ;
// 记录开始时间
long start = system.currenttimemillis();
// 获取方法参数
object[] args = pjd.getargs();
string params = "前端请求参数为:" ;
//获取请求参数集合并进行遍历拼接
for (object object : args) {
params += object.tostring() + "," ;
}
params = params.substring( 0 , params.length() - 1 );
//打印请求参数参数
log.info(classname+ "类的" +methodname + "的" + params);
// 执行目标方法
result = pjd.proceed();
// 打印返回报文
log.info( "方法返回报文为:" + (result instanceof result ? (result) result : result));
// 获取执行完的时间
log.info(methodname + "方法执行时长为:" + (system.currenttimemillis() - start));
return result;
}
|
5、@around 环绕通知,如上代码所示便是环绕通知,其有proceedingjoinpoint参数
其中 pjd.proceed();方法代表去执行目标方法,并获得一个object类型的返回值 ,我们可以对返回值进行加工处理,如装饰加工等。
return的值为方法执行的结果。上述代码中首先获取类名、方法名、方法请求参数等,进行打印的拼接,并且记录方法执行的开始时间,并进行打印至日志。
然后执行方法,获取到方法返回结果,进行打印执行时间和执行结果。
最后返回执行结果。即使用aop打印请求报文和返回报文的aop切面编码结束。
其中@around代表其为一个环绕通知方法,其有以下几种类型:
1、@before前置通知,拥有请求参数 joinpoint ,用来连接当前连接点的连接细节,一般包括方法名和参数值。在方法执行前进行执行方法体,不能改变方法参数,也不能改变方法执行结果。
1
2
3
|
@before (value = "privilege()" )
public void before(joinpoint joinpoint) {
}
|
2、@after 后置通知:在目标方法执行之后,无论是否发生异常,都进行执行的通知。在后置通知中,不能访问目标方法的执行结果(因为有可能发生异常),不能改变方法执行结果。
1
2
3
|
@before (value = "privilege()" )
public void after(joinpoint joinpoint) {
}
|
3、@afterreturning 返回通知,在目标方法执行结束时,才执行的通知,同后置方法相同。其能访问方法执行结果(因为正常执行)和方法的连接细节,但是不能改变方法执行结果。
1
2
3
|
@afterreturning (value = "privilege()" )
public void afterreturing(joinpoint joinpoint,object result) {
}
|
result中存放的为方法的返回值。
4、@afterthrowing 异常通知:在目标方法出现异常时才会进行执行的代码。 throwing属性代表方法体执行时候抛出的异常,其值一定与方法中exception的值需要一致。
1
2
3
|
@afterthrowing (value= "privilege()" ,throwing= "ex" )
public void exce(joinpoint joinpoint, exception ex) {
}
|
三、测试
编写一个controller方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@restcontroller
@requestmapping ( "/api/v1/dept" )
public class deptcontroller extends basecontroller{
/** 日志记录类 */
private logger log = loggerfactory.getlogger(getclass());
/** 自家的service */
@autowired
private deptservice service;
/**
* @功能描述:根据id查询部门内容的方法
* @return dept
*/
@getmapping ( "/id/get" )
public result getbyid( string id) throws exception{
verify( new verifyparam( "部门id" , id));
return new result( "通过id获取部门信息成功!" , service.querybyid(id));
}
}
|
如此我们的controller层中的方法就大大的简洁了。
测试结果:
2018-04-10 22:59:27.468 info 1460 --- [nio-8088-exec-5] nproceedingjoinpoint$methodsignatureimpl : getbyid的前端请求参数为:22
2018-04-10 22:59:27.470 info 1460 --- [nio-8088-exec-5] nproceedingjoinpoint$methodsignatureimpl : 方法返回报文为:result [result_code=suc, result_message=通过id获取部门信息成功!, data=dept [id=22, no=22, name=22, manager=22, description=22, phone=22, createtime=thu apr 19 23:38:37 cst 2018, edittime=null]]
2018-04-10 22:59:27.470 info 1460 --- [nio-8088-exec-5] nproceedingjoinpoint$methodsignatureimpl : getbyid方法执行时长为:2
如此便能很雅观简洁隐式的打印请求参数、返回结果和执行时间等!
原文链接:http://www.cnblogs.com/zeryts/p/8784172.html