Spring Boot【定制化】~ AOP统一结果处理以及异常拦截

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、自定义异常拦截

结束语:不要半途而废!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值