1、简介
用处:统一结果处理与异常拦截。
2、配置步骤
2.1、创建一个spring boot项目(idea)
完整项目结构!
2.2、导入依赖(需要使用到简化环境搭建)
<!-- SpringBoot Aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 阿里JSON处理器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
2.3、yml
注意:我们这个测试不需要yml(写上来防止你们疑惑!)
spring:
application:
name: springboot-day02-aop-exception #服务名称(项目名)
2.4、需要使用的工具类
注意:直接复制使用,工具类方法太多所以直接抽取几个用到的方法(不是重点)。
1、StringUtils
/**
* @author cms
* @version 1.0.0.0
* @Date: 2022/5/21 20:10
*/
public class StringUtils extends org.apache.commons.lang3.StringUtils
{
public static boolean isEmpty(String str)
{
return isNull(str) || "".equals(str.trim());
}
public static boolean isNotEmpty(String str)
{
return !isEmpty(str);
}
public static boolean isNull(Object object)
{
return object == null;
}
public static boolean isNotNull(Object object)
{
return !isNull(object);
}
}
2、ServletUtils
/**
* @author cms
* @version 1.0.0.0
* @Date: 2022/5/21 20:10
*/
public class ServletUtils
{
/**
* 响应字符串
* @param response 响应
* @param string 内容
* @return 结果
*/
public static String parseString(HttpServletResponse response, String string)
{
try
{
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
}
2.5、需要使用的枚举以及状态
1、ErrorEnum
/**
* @author cms
* @version 1.0.0.0
* @Date: 2022/3/16 13:17
*/
public enum ErrorEnum {
/* 参数错误P0100 - P0200 */
P_ERROR_NULL("P0100", "参数%s不能为空"),
P_NOT_EXIST("P0101", "%s不存在"),
P_FORMAT_ERROR("P0102", "%s格式不正确"),
/* 系统内部错误S0100 - Sxxx */
S_CATCH_AUTH("S0104", "%s"),
S_TOKEN_EXPIRED("S0104", "Token过期"),
S_ERROR("S0105","系统异常 练习管理员!佳佳吖!"),
;
private String code;
private String message;
ErrorEnum() {
}
ErrorEnum(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
/**
* @param message 填充内容
* @return 结果
*/
public String getMessage(String message) {
if (StringUtils.isNotEmpty(message)) {
return String.format(this.message, message);
}
return "";
}
public void setCode(String code) {
this.code = code;
}
public void setMessage(String message) {
this.message = message;
}
}
2、状态码常量类
/**
* 返回状态码
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/5/21 20:05
*/
public class HttpStatus
{
/**
* 操作成功
*/
public static final int SUCCESS = 200;
/**
* 不允许的http方法
*/
public static final int BAD_METHOD = 405;
/**
* 系统内部错误
*/
public static final int ERROR = 500;
}
2.6、统一结果返回格式对象
/**
* 统一结果格式返回控制类
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/5/21 19:23
*/
public class ResultReturn extends LinkedHashMap<String, Object>
{
public static final Date newDateTime = new Date();
public static final String CODE = "code";
public static final String MSG = "message";
public static final String DATE_TIME = "datetime";
public static final String DATA = "data";
public ResultReturn() {}
public ResultReturn(Object code, String msg, Date datetime, Object data)
{
super.put(CODE, code);
super.put(MSG, msg);
super.put(DATE_TIME, datetime.getTime());
if (StringUtils.isNotNull(data))
{
super.put(DATA, data);
}
}
/**
* 成功状态码统一:200
*
* @return 返回结果
*/
public static Object success(Object data)
{
return new ResultReturn(200, "success", newDateTime, data);
}
public static Object success()
{
return ResultReturn.success(null);
}
/**
* 错误返回
*
* @param errorEnum 错误枚举信息
* @return 返回结果
*/
public static Object error(ErrorEnum errorEnum)
{
return new ResultReturn(errorEnum.getCode(), errorEnum.getMessage(), new Date(), null);
}
public static Object error(Object code, String errorMsg)
{
return new ResultReturn(code, errorMsg, newDateTime, null);
}
public static ResultReturn error(RuanjiaExceptionHandler e)
{
return new ResultReturn(e.getCode(), e.getMessage(), newDateTime, null);
}
}
2.7、Controller统一返回结果(AOP)
1、自定义注解
作用:如果有不想被Aop拦截的controller方法,就是用该注解标识。
/**
* 默认返回结果
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/5/21 19:17
*/
@Target({ ElementType.TYPE, ElementType.METHOD }) //用于方法上
@Retention(RetentionPolicy.RUNTIME) //保留政策:运行时
@Documented
public @interface OriginalReturn
{
}
2、统一结果拦截类(AOP切面类)
作用(重点):使用Aop实现统一结果拦截。
/**
* 使用Aop拦截统一结果
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/5/21 19:15
*/
@Aspect
@Slf4j
@Component
public class ResultAspectj
{
@Pointcut("execution(public * com.ruanjia.controller..*.*(..))")
public void result() {};
/** AOP拦截所有Controller控制器结果 */
@Around("result()")
public Object afterReturningMethod(ProceedingJoinPoint point) throws Throwable {
Object proceed = point.proceed();
/* 模型和视图 -> 直接放行 */
if (proceed instanceof ModelAndView || proceed instanceof View) {
return proceed;
}
Method method = ((MethodSignature) point.getSignature()).getMethod();
/* 方法上标志@OriginalReturn -> 直接放行 */
if (method.isAnnotationPresent(OriginalReturn.class)) {
return proceed;
}
return ResultReturn.success(proceed);
}
}
2.8、全局异常拦截
1、自定义异常类
作用:处理系统业务异常(这里还可以细写,可以根据自己需要增加自定义异常!)
/**
* 自定义异常
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/5/21 20:02
*/
@Data
public class RuanjiaExceptionHandler extends RuntimeException
{
private Object code;
private String message;
public RuanjiaExceptionHandler(ErrorEnum errorCodeEnum)
{
this.code = errorCodeEnum.getCode();
this.message = errorCodeEnum.getMessage();
}
/**
* @param errorCodeEnum 枚举
* @param message 替换字符串
*/
public RuanjiaExceptionHandler(ErrorEnum errorCodeEnum, String message)
{
super(errorCodeEnum.getMessage(message));
if (StringUtils.isNotEmpty(message)) {
this.message = errorCodeEnum.getMessage(message);
} else {
this.message = errorCodeEnum.getMessage();
}
this.code = errorCodeEnum.getCode();
}
/**
* @param message 异常提示信息
*/
public RuanjiaExceptionHandler(String message)
{
this.code = HttpStatus.ERROR;
this.message = message;
}
}
2、全局异常类
作用:拦截所有Controller异常
/**
* 全局异常捕获:
* @ControllerAdvice 包含@Component,可以被扫描到。统一处理异常。
* @ExceptionHandler(Exception.class) 用在方法上面表示遇到这个异常就执行以下方法。
*
* @author cms
* @version 1.0.0.0
* @Date: 2022/5/21 19:15
*/
@Slf4j
@ControllerAdvice
public class SystemGlobalExceptionHandler
{
/**
* 自定义异常
*
* @param e 算术异常
* @param response 响应结果
*/
@ExceptionHandler(RuanjiaExceptionHandler.class)
public void ruanjiaExceptionHandler(RuanjiaExceptionHandler e, HttpServletResponse response)
{
log.error("===========RuanjiaExceptionHandler(自定义异常)==========");
e.printStackTrace();
ServletUtils.parseString(response, JSON.toJSONString(ResultReturn.error(e.getCode(), e.getMessage())));
}
/**
* 算术异常
*
* @param e 异常对象
* @param response 响应结果
*/
@ExceptionHandler(ArithmeticException.class)
public void handlerArithmeticException(ArithmeticException e, HttpServletResponse response)
{
log.error("===========ArithmeticException(算术异常)==========");
e.printStackTrace();
ServletUtils.parseString(response, JSON.toJSONString(ResultReturn.error(HttpStatus.ERROR, e.getMessage())));
}
/**
* GET/POST不支持异常
*
* @param e 异常对象
* @param request 请求
* @param response 响应结果
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public void handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request, HttpServletResponse response)
{
log.error("===========HttpRequestMethodNotSupportedException(GET/POST 不支持异常)==========");
String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
e.printStackTrace();
ServletUtils.parseString(response, JSON.toJSONString(ResultReturn
.error(HttpStatus.BAD_REQUEST,"请求地址【"+requestURI+"】,不支持【"+e.getMethod()+"】请求")));
}
}
此致!配置完成,进入测试环节!
2.9、编写Controller测试类
/**
* @author cms
* @version 1.0.0.0
* @Date: 2022/5/21 19:11
*/
@RestController
public class TestController
{
/**
* 统一结果返回测试
*
* @return 结果
*/
@GetMapping("/result")
public Object result()
{
return "同意结果返回测试!";
}
/**
* 算术异常(全局异常处理程序拦截)
*
* @return 结果
*/
@GetMapping("/error1")
public Object error()
{
int a = 10/0;
return a;
}
/**
* 自定义异常拦截(全局异常处理程序拦截)
*
* @param username 用户名
* @return 返回结果
*/
@GetMapping("/error2")
public Object error2(String username)
{
if (StringUtils.isEmpty(username))
{
throw new RuanjiaExceptionHandler(ErrorEnum.S_CATCH_AUTH, "用户名不能为空!");
}
return username;
}
}
效果图:
1、统一结果返回测试
2、系统算术异常拦截
3、自定义异常拦截
结束语:不要半途而废!