Fork me on GitHub

两个切面配置的记录

前言

之前也学习过validate注解去校验一些参数的必要条件,这回工作中用的时候遇到了一些问题。这里都是使用spring boot框架去做的切面,省去了很多不需要的配置。这里去记录一下。

两个切面

validate注解

这里主要是去使用了hibernate中的注解而去做的切面,之前在博客中写到的整合了两个标准的注解的切面(注意区分下)。看一下切面的代码:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@Aspect
@Configuration
public class ValidateAspect {
/**
* BEAN对象校验器 配置快速失败模式
*/
private final Validator BEAN_VALIDATOR = Validation.byProvider(HibernateValidator.class)
.configure()
//快速失败模式开启,当检测到有一项失败立即停止
.failFast(true)
.buildValidatorFactory().getValidator();

/**
* 方法参数对象校验器
*/
private final ExecutableValidator METHOD_VALIDATOR = BEAN_VALIDATOR.forExecutables();

/**
* point配置
*/
@Pointcut("execution(* com.zhanglijun.springbootdemo.web.controller..*.*(..))")
public void pointcut() {
}

@Before("pointcut()")
public void before(JoinPoint point) {

// 获得切入目标对象
Object target = point.getThis();
// 获得切入方法参数
Object [] args = point.getArgs();
// 获得切入的方法
Method method = ((MethodSignature)point.getSignature()).getMethod();

// 校验以基本数据类型 为方法参数的
checkWithResult(validMethodParams(target, method, args));

// 校验以java bean对象
for (Object bean : args) {
if (null != bean) {
checkWithResult(validBeanParams(bean));

}
}
}

/**
* 校验bean对象中的参数
* @param bean
* @param <T>
* @return
*/
private <T> Set<ConstraintViolation<T>> validBeanParams(T bean) {
return BEAN_VALIDATOR.validate(bean);
}

/**
* 校验方法中的参数
* @param obj
* @param method
* @param params
* @param <T>
* @return
*/
private <T> Set<ConstraintViolation<T>> validMethodParams(T obj, Method method, Object [] params){
return METHOD_VALIDATOR.validateParameters(obj, method, params);
}

/**
* 校验参数校验结果
* @param set
*/
private void checkWithResult(Set<ConstraintViolation<Object>> set) {
if (CollectionUtils.isEmpty(set)) {
return;
}
Iterator<ConstraintViolation<Object>> methodIterator = set.iterator();
if (methodIterator.hasNext()) {
throw new IllegalArgumentException(methodIterator.next().getMessage());
}
}
}

这个可以去实现validate注解的切面配置。

访问接口信息注解

还有就是参考翟DD博客中的spring boot配置的一个记录访问接口信息的切面,这里在配置了之后遇到了两个问题,去记录一下。

切面配置

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
31
32
33
34
35
36
37
@Aspect
@Configuration
@Slf4j
public class WebRequestAspect {

// TODO 可以加上sessionUser的获取

@Pointcut("execution(* com.zhanglijun.springbootdemo.web.controller..*.*(..))")
public void webLog(){}

@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
// 获取request对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return;
}
HttpServletRequest request = attributes.getRequest();

log.info("REQUEST_URL : {}, HTTP_METHOD : {}, ARGS : {}",
request.getRequestURL().toString(),
request.getMethod(),
JSON.toJSONString(joinPoint.getArgs()));
}

@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) {
// 打印返回内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
// 容器启动时也去初始化了切面(因为是execution)这里去判断一下
return;
}
HttpServletRequest request = attributes.getRequest();
log.info("REQUEST_URL : {}, RESPONSE : {} ", request.getRequestURL(), JSON.toJSONString(ret));
}
}

问题1 fastJson去打印入参

在doBefore方法中去做了对方法参数的打印,这里没有思索去用了fastJson去打印参数,但是当去访问一个参数中带有response对象的时,会发生报错。

接口:

1
2
3
4
5
6
//========================= 测试web log切面 =====================

@GetMapping(path = "/testAspect", produces = MediaType.APPLICATION_JSON_VALUE)
public void testAspect(HttpServletResponse response) {
log.info("测试web log 切面 fastJson序列化response对象会报错");
}

报错:

这个错误在网上找了比较久,也跟进去源码看了,reponse的getOutputSteam在走到切面的这个输出的时候,已经被调用过了,会有一个标志位标志其被调用过,这里如果用fastJson去序列化这个response对象,这里会报错。这就提醒我们这里输出参数不能去使用fastJson去序列化。(有的mapping还是会去用到response对象)。修改方法就是可以把输出参数的部分改成Arrays.toString进行输出。

1
Arrays.toString(joinPoint.getArgs()));

这里要注意参数内部的toString方法的实现。

request对象获取的问题

这个切面中因为要记录访问的url,所以用到了HttpServletRequest对象,这里获取的方式是:RequestContextHolder.getRequestAttributes();这个实现的原理就是请求到来的时候去使用ThreadLocal放入request对象,所以在@AfterReturning中也可以去使用这个对象(因为这次请求还没有结束)。但是在项目初始化时,这个切面就会被加载一次,此时并没有请求所以ThreadLocal中也不会有这个request对象。

解决办法:我这里去对request对象做了判断,如果是null的话,那么方法直接返回。

这里也可以去写一下@AfterThrowing之后的处理,可以直接throw出去交给统一异常处理。这里注意切面的order属性和数据层面的事务的order大小顺序。order越小,执行的越靠前。

当你的切面order>数据层面的事务order时,执行顺序是:

transaction -> doBefore -> Exception -> @AfterThrowing -> rollback。注意这个时候会造成你的@AfterThrowing内容不生效,一起rollback了。

而当你的切面order < 数据层面事务order时,执行顺序是:

dobefore -> transaction -> exception -> rollback -> @AfterThrowing

所以要配置你的webLog切面的order小一些。在spring boot框架中可以通过@Order(level)在类上加上注解进行order控制。

-------------本文结束感谢您的阅读-------------

本文标题:两个切面配置的记录

文章作者:夸克

发布时间:2018年09月21日 - 00:09

最后更新:2022年07月01日 - 05:07

原始链接:https://zhanglijun1217.github.io/2018/09/21/两个切面配置的记录/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。