一 @ControllerAdvice @ExceptionHandler注解
简介:
@ControllerAdvice注解可以简单理解为@controller注解的装饰注解,用法有三种:
- 全局处理异常
- 请求参数预处理
- 预设全局数据
其中全局处理异常是比较常见的方法(和@ExceptionHandler注解)配合使用。
用法:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletException;
@ControllerAdvice
public class GlobalDefaultExceptionAdvice {
private Logger logger = LoggerFactory.getLogger(GlobalDefaultExceptionAdvice.class);
@ResponseBody
@ExceptionHandler(value = Exception.class)
public ResponseDTO errorHandler(Exception ex) {
logger.error("Exception:", ex);
return new ResponseDTO(ResponseEnum.UNKNOWN);
}
@ResponseBody
@ExceptionHandler(value = {HttpMessageNotReadableException.class, ServletException.class,})
public ResponseDTO paramErrorHandler(Exception ex) {
logger.error("PARAM_ERR:", ex);
return new ResponseDTO(ResponseEnum.PARAM_ERR);
}
@ResponseBody
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseDTO paramErrorHandler(MethodArgumentNotValidException ex) {
logger.error("MethodArgumentNotValidException:", ex);
return new ResponseDTO(ResponseEnum.PARAM_ERR, getParamErr(ex.getBindingResult()));
}
@ResponseBody
@ExceptionHandler(value = BindException.class)
public ResponseDTO paramErrorHandler(BindException ex) {
logger.error("BindException:", ex);
return new ResponseDTO(ResponseEnum.PARAM_ERR, getParamErr(ex.getBindingResult()));
}
private String getParamErr(BindingResult bindingResult) {
StringBuilder sb = new StringBuilder();
for (FieldError error : bindingResult.getFieldErrors()) {
sb.append(error.getDefaultMessage());
sb.append(". ");
}
return sb.toString();
}
@ResponseBody
@ExceptionHandler(value = BusinessException.class)
public ResponseDTO errorHandler(BusinessException ex) {
logger.error("BusinessException:", ex);
return new ResponseDTO(ex.getResponseEnum(), ex.getMessage());
}
}
注意事项:
1:可以在异常处理之后返回通用的ResponseVO
2:可以在这里统一打印错误日志,不需要在业务中有过多打印日志的逻辑
3:其他@ControllerAdvice的两种用法可以参考:@ControllerAdvice 的介绍及三种用法
二 @Aspect @Pointcut注解
简介:
@Aspect是spring中面向切面编程的一种思想,么,作用是把当前类标识为一个切面供spring注入,通过和@Pointcut配合达到切面编程的效果。常见的用法有
- 可以和自定义的注解做到权限控制
- 可以方便的打印入参和返回值。
- 可以做性能统计等。
@Pointcut注解是定义切面范围。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。
@Pointcut("execution(public * com.robot.web.controller .*.*(..))")
// execution括号里public可以省略(缺省值),也可以是private(那就只对private访问级别的方法做切面)
// execution括号里的第一个参数是返回值(*)表示任意返回值
// execution括号里的第二个参数是定位到哪些包的哪些方法
// execution括号里的第三个参数(..) 是指方法的参数列表
用法:
这里演示下有自定义注解才打印入参和返回值的demo
import com.alibaba.fastjson.JSON;
import com.robot.web.annoation.MethodLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Aspect
@Component
@Slf4j
public class GlobalLogAspect {
@Pointcut("execution(* com.robot.web.controller .*.*(..))")
public void logPoint() {
}
@Around("logPoint()")
public Object doLogAround(ProceedingJoinPoint pjp) throws Throwable {
// 强转成MethodSignature
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
MethodLog containsMethodClass = methodSignature.getMethod().getAnnotation(MethodLog.class);
Object result;
if (Objects.nonNull(containsMethodClass) && containsMethodClass.logType().equals(MethodLog.LogType.ALL)) {
log.info("invoke method={}, params={}", methodSignature.getMethod().getName(), JSON.toJSON(pjp.getArgs()));
result = pjp.proceed();
log.info("invoke method={}, result={}", methodSignature.getMethod().getName(), result);
} else {
result = pjp.proceed();
}
return result;
}
}
package com.robot.web.controller;
import com.robot.base.conf.TccConf;
import com.robot.base.req.RobotReq;
import com.robot.web.annoation.MethodLog;
import com.robot.web.biz.RobotBiz;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Api(tags = "机器人相关接口")
@RestController
@Slf4j
public class RobotController {
@Resource
private TccConf tccConf;
@Resource
private RobotBiz robotBiz;
@ApiOperation(value = "测试tcc")
@GetMapping("/tcc")
@MethodLog
public String testTcc(@RequestParam("key") String key) {
return tccConf.getConfigByKey("test");
}
@ApiOperation(value = "处理消息")
@PostMapping("/callBack")
public String callBack(@RequestBody RobotReq req) {
return robotBiz.CallBack(req);
}
}
第一个方法会打印出入参和返回值,第二个方法不会打印。
注意事项:
1:需要把ProceedingJoinPoint的Signature强转成MethodSignature,才能获取方法签名上的注解信息。方便后续的逻辑
2:可以在切面处理过程中改变方法参数,然后再调用方法(调用的时候得带上Object args[])才能生效,通用的方法可以这样处理。之前踩过一个改参数调用jar包时候坑,返回值中的transient修饰的字段信息不能读取出来。
三 @Target @Retention @Documented注解
简介:
这三个注解是元注解,所有的注解都是需要有元注解的(@Target @Retention是必须要有的)。
@Target指定注解作用的范围,可以是类,方法,成员变量。修饰类和方法比较常见。
@Retention指定注解的生命周期,可以是源文件保留,编译时保留,运行时保留。
@Documented注解修饰的注解类会被 JavaDoc 工具提取成文档
用法:
package com.robot.web.annoation;
import lombok.Data;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodLog {
/**
* 日志枚举
*/
enum LogType {
/**
* 打印全部
*/
ALL("全部"),
/**
* 打印参数
*/
PARAM("参数"),
/**
* 打印返回值
*/
RESULT("结果");
String desc;
LogType(String desc) {
this.desc = desc;
}
}
LogType logType() default LogType.ALL;
}