96. Spring Validation及慢sql自定义日志

1,449 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第32天,点击查看活动详情

1.Spring Validation

如何使用:www.modb.pro/db/46585

Web接口校验-单参数

类上需加注解@Validated

@RestController
@Validated
@RequestMapping("/validation")
public class testController {

    @GetMapping("/test1")
    public ResultEntity<Void> test1(@NotNull(message = "id不能为空") String id) {
        return ResultEntity.success(null);
    }
    }

Web接口校验-对象参数

在对象内需要校验的参数上加注解

如果对象内部有嵌套对象,并且需要校验嵌套对象的内部属性,需要在嵌套对象上加@Valid注解

@RestController
@Validated
@RequestMapping("/validation")
public class testController {

    @GetMapping("/test2")
    public ResultEntity<Void> test2(@Validated TradeGoodsPageQuery query) {
        return ResultEntity.success(bigScreenService.testCache(query));
    }
}
@Data
public class GoodsQuery implements Serializable {

    private static final long serialVersionUID = -4738695863328719832L;

    @NotNull(message = "id不能为空")
    private Long id;

    @Valid
    @NotNull
    private TradeGoodsPageQuery query;
}

Dubbo接口校验

dubbo接口参数校验也是在参数对象中添加校验注解,但dubbo的参数校验即可以在服务端做,也可以在消费端做,在服务端开启参数校验如下

@DubboService(validation = "true")
@Component("ITradeGoodService")
public class ITradeGoodServiceImpl implements ITradeGoodService {

}
@Reference(validation = "true")
private ITradeGoodService iTradeGoodService;

建议服务端必须开启,保障业务代码正常运行。消费端尽量开启,提前在消费端进行校验可以减少一次rpc消耗

添加全局异常处理

MethodArgumentNotValidException、ConstraintViolationException、ValidationException都是参数校验中可能抛出的异常

@ControllerAdvice
public class CustomExceptionHandle {
    
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResultEntity handle(HttpServletRequest request , Exception e) {

       //开始处理异常
        if (e instanceof CustomException) {
            // 省略
        } else if (e instanceof BindException || e instanceof MethodArgumentNotValidException) {
            StringBuilder sb = new StringBuilder("参数错误:[");
            BindingResult result;
            if (e instanceof BindException) {
                BindException exception = (BindException) e;
                result = exception.getBindingResult();
            } else {
                MethodArgumentNotValidException exception = (MethodArgumentNotValidException) e;
                result = exception.getBindingResult();
            }
            List<ObjectError> list = result.getAllErrors();
            for (ObjectError item : list) {
                sb.append(item.getDefaultMessage()).append(',');
            }
            sb.deleteCharAt(sb.length() - 1);
            sb.append(']');

            String msg = sb.toString();
            LogHelper.warn(LogHelper.getLogMsg() +"入参校验异常,请求地址:{}," , request.getRequestURI() , e);
            return ResultTemplate.error(-1, msg);
        } else if (e instanceof ConstraintViolationException) {
            LogHelper.warn("入参校验异常,请求地址:{}," , request.getRequestURI() , e);
            return ResultTemplate.error(-1, e.getMessage());
        } else if (e instanceof ValidationException) {
            LogHelper.warn("入参校验异常,请求地址:{}," , request.getRequestURI() , e);
            return ResultTemplate.error(-1, "远程调用参数错误");
        } else {
            // 省略
        }

    }

}

2.自定义慢日志打印

目的

通过阿里云平台的慢日志统计和druid自带的慢日志打印,只能打印出执行慢的sql语句,很多时候发现并不能在代码中找到完全匹配的sql语句和调用方法,导致无从优化,因此希望可以扩展druid,自定义慢日志的输出

image.png

方式

由原本的日志打印类,得知druid的慢日志打印在StatFilter中,druid通过这个类进行sql执行的时长计算和统计,超出慢sql配置的阙值时,打印error日志。

而druid的过滤器链也是可拔插的形式,我们通过配置spring.datasource.druid.filters=stat, wall 使得StatFilter生效

因此我们只需要自定义一个CustomStatFilter类,完全复制StatFilter的代码,修改我们需要打印日志的方式,然后在配置中替换掉StatFilter即可

原本的慢日志打印:

修改为

打印出堆栈信息可以方便定位到具体的问题代码

修改完成后,需要替换spring.datasource.druid.filters配置,我们发现这里配置的是别名,druid是通过别名映射将stat映射到StatFilter,这个配置文件在druid的jar包中META-INF文件夹下的druid-filter.properties中

因此我们需要在业务工程的META-INF文件夹下新建一个同名的配置文件,将源文件的内容拷贝进来,并在最后加上我们自定义的filter的别名和类路径

druid.filters.customStat=com.anchu.xxx.xxx.CustomStatFilter

最后替换spring.datasource.druid.filters=customStat,wall 即可