SpringController返回值和异常自动包装

今天遇到一个需求,在不改动原系统代码的情况下。将Controller的返回值和异常包装到一个统一的返回对象中去。

例如原系统的接口

public String myIp(@ApiIgnore HttpServletRequest request);

返回的只是一个IP字符串"0:0:0:0:0:0:0:1",目前接口需要包装为:

{"code":200,"message":"","result":"0:0:0:0:0:0:0:1","success":true}

而原异常跳转到error页面,需要调整为

{
  "success": false,
  "message": "For input string: \"fdsafddfs\"",
  "code": 500,
  "result": "message"
}

因此就有了2个工作子项需要完成:

1)Exception的处理

2)controller return值的处理

Exception的自动包装

返回的exception处理可以采用@RestControllerAdvice来处理。

建立自己的Advice类,注入国际化资源(异常需要支持多语言)
 

package org.ccframe.commons.mvc;

import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.ccframe.commons.filter.CcRequestLoggingFilter;
import org.ccframe.commons.util.BusinessException;
import org.ccframe.config.GlobalEx;
import org.ccframe.subsys.core.dto.Result;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.core.MethodParameter;
import org.springframework.http.ResponseEntity;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import java.util.Locale;

@RestControllerAdvice
@Log4j2
public class GlobalRestControllerAdvice{

    private MessageSource messageSource; //国际化资源

    private LocaleResolver localeResolver;

    private Object[] EMPTY_ARGS = new Object[0];

    public GlobalRestControllerAdvice(MessageSource messageSource, LocaleResolver localeResolver){
        this.messageSource = messageSource;
        this.localeResolver = localeResolver;
    }

    private Result<String> createError(HttpServletRequest request, Exception e,int code, String msgKey, Object[] args){
        Locale currentLocale = localeResolver.resolveLocale(request);
        String message = "";
        try {
            message = messageSource.getMessage(msgKey, args, currentLocale);
        }catch (NoSuchMessageException ex){
            message = e.getMessage();
        }finally {
            log.error(message);
            CcRequestLoggingFilter.pendingLog(); //服务器可以记录出错时的请求啦😂
        }
        return Result.error(code, message, msgKey);
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public Result<?> handlerNoFoundException(HttpServletRequest request, Exception e) {
        return createError(request, e, HttpStatus.SC_NOT_FOUND, "error.mvc.uriNotFound", EMPTY_ARGS);
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<?> httpRequestMethodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException e){
        return createError(request,e, HttpStatus.SC_NOT_FOUND,"error.mvc.methodNotSupported",
            new Object[]{e.getMethod(), StringUtils.join(e.getSupportedMethods(), GlobalEx.DEFAULT_TEXT_SPLIT_CHAR)});
    }

    @ExceptionHandler(BusinessException.class)
    public Result<?> businessException(HttpServletRequest request, BusinessException e){
        return createError(request,e, HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMsgKey(), e.getArgs());
    }

    @ExceptionHandler(ObjectOptimisticLockingFailureException.class) //乐观锁异常
    public Result<?> objectOptimisticLockingFailureException(HttpServletRequest request, ObjectOptimisticLockingFailureException e){
        return createError(request,e, HttpStatus.SC_INTERNAL_SERVER_ERROR, "errors.db.optimisticLock", EMPTY_ARGS);
    }

    @ExceptionHandler(MaxUploadSizeExceededException.class) // 文件上传超限,nginx请设置为10M
    public Result<?> handleMaxUploadSizeExceededException(HttpServletRequest request, MaxUploadSizeExceededException e) {
        return createError(request, e, HttpStatus.SC_INTERNAL_SERVER_ERROR, "error.mvc.fileTooLarge", EMPTY_ARGS);
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result<?> handleException(HttpServletRequest request, Exception e) {
        log.error(e);
        return createError(request,e, HttpStatus.SC_INTERNAL_SERVER_ERROR, "message", new Object[]{e.getMessage()});
    }
}


在Config类初始化该Bean(当然也可以使用@Component支持扫描,随你喜欢)
 

	@Bean
	public GlobalRestControllerAdvice globalRestControllerAdvice(MessageSource messageSource, LocaleResolver localeResolver){
		return new GlobalRestControllerAdvice(messageSource, localeResolver);
	}

controller return值的自动包装

网上的例子有很多坑,包括使用HandlerMethodReturnValueHandler,看了源码才发现。还是ResponseBodyAdvice好使。

建立自己的处理Bean

package org.ccframe.commons.mvc;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import org.apache.http.HttpStatus;
import org.ccframe.commons.util.JsonUtil;
import org.ccframe.subsys.core.dto.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import springfox.documentation.swagger.web.ApiResourceController;

import java.util.regex.Pattern;

@ControllerAdvice
public class CcResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    private static final Pattern CONTROLLER_PATTERN = Pattern.compile("^org\\.ccframe\\.(subsys|sdk)\\.[a-z0-9]+\\.controller\\.");
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return 
    // 只有自己的cotroller类才需要进入,否则swagger都会挂了
CONTROLLER_PATTERN.matcher(returnType.getContainingClass().getName()).find();
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        System.out.println(returnType.getContainingClass());
        Result<Object> result = new Result<>();
        result.setResult(body);
        result.setCode(HttpStatus.SC_OK);

        if(body instanceof String){ //String返回要特殊处理
            return JSON.toJSONString(result);
        }else {
            return result;
        }
    }
}

如果你不需要根据正则来指定包,可以直接用RestControllerAdvice的basePackages属性来过滤

注意这里有2个坑

1)String类型的返回被其它的转换接口StringHttpMessageConverter处理,因此返回要进行JSON编码而不能返回其他类型,否则会报cast类型错,因此就有了String部分的特殊处理方法。

2)controller方法签名返回是void时,不会被处理。为什么,有什么办法?得看spring这段源码:

当returnValue==null时,设置为RequestHandled,也就是提前结束了。后面任何返回的处理都不再进行。所以,如果一定要返回null值的话,可以在controller里返回一个
return new ResponseEntity<Void>(HttpStatus.OK);
这样在返回的值里面就有详细的结构了。

最后要生效的话,在Config类初始它:
 

	@Bean
	public CcResponseBodyAdvice ccResponseBodyAdvice() {
		return new CcResponseBodyAdvice();
	}

最后。上面两个Bean也可以写在一个,有兴趣的自己尝试。

---------------

null无法被BodyAdvice处理的问题。随着源码跟踪,慢慢知道怎么回事了,我们尝试根本来解决这个问题。从这个图开始:


当返回为null时,mavContainer.isRequestHandled()为true导致了后面的没有处理。

那么想当然的,mavContainer.isRequestHandled()为flase不久解决了吗,向前跟踪,基本到MVC invoke的核心代码里了,发现在invoke前,mavContainer.isRequestHandled()变成了true,再继续跟踪,找到这个方法:

在HandlerMethodArgumentResolverComposite的argumentResolvers看到了上面这个。进行了setRequestHandled。

那么再想当然的,我们在处理chain后面,把setRequestHandled再改成False如何?
 

HandlerMethodArgumentResolver是spring controller的参数自动注入机制,例如ServletHttpRequest等就是自动注入的。列表也看到了我自己写的权限自动注入Bean了。参照spring的写法,添加一个自己的类,去干把RequestHandled重新设置为false的活:),如下:

【未完持续】
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/450646.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Django入门 整体流程跑通

Django学习笔记 一、Django整体流程跑通 1.1安装 pip install django //安装 import django //在python环境中导入django django.get_version() //获取版本号&#xff0c;如果能获取到&#xff0c;说明安装成功Django目录结构 Python310-Scripts\django-admi…

噬菌体展示文库类型与应用-卡梅德生物

噬菌体展示抗体库构建的途径&#xff1a;目前主要有两种&#xff1a;一是有机合成法&#xff0c;二是基因合成法。前者是直接合成含有各种可能序列的短肽&#xff0c;构建至噬菌体载体。基因工程方法是将目的基因构建至载体&#xff0c;与噬菌体的pⅢ外壳蛋白融合表达。 卡梅德…

数据结构——堆的应用 堆排序详解

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

制造业工厂的设备管理系统

对企业来说&#xff0c;时间就是金钱&#xff0c;所有企业都在极力避免因生产延误而导致的金钱损失。在设备保养、设备维护和设备运行方面更是如此。如果工厂的设备因突发故障处于长时间停机状态&#xff0c;但没能被及时解决&#xff0c;工厂所需支付的成本可能就会螺旋式上升…

11、设计模式之享元模式(Flyweight)

一、什么是享元模式 享元模式是一种结构型的设计模式。它的主要目的是通过共享对象来减少系统种对象的数量&#xff0c;其本质就是缓存共享对象&#xff0c;降低内存消耗。 享元模式将需要重复使用的对象分为两个部分&#xff1a;内部状态和外部状态。 内部状态是不会变化的&…

基于java+springboot+mybatis+laiyu实现学科竞赛管理系统

基于javaspringbootmybatislaiyu实现学科竞赛管理系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获…

UI学习 一 可访问性 基础

教程&#xff1a;Accessibility – Material Design 3 需要科学上网&#xff0c;否则图片显示不出来。设计教程没有图片说明&#xff0c;不容易理解。 优化UI方向 清晰可见的元素足够的对比度和尺寸重要性的明确等级一眼就能辨别的关键信息 传达某一事物的相对重要性 将重…

蓝牙系列七:开源蓝牙协议栈BTStack数据处理(Wireshark抓包分析)

继续蓝牙系列的研究。 在上篇博客&#xff0c;通过阅读BTStack的源码&#xff0c;大体了解了其框架&#xff0c;对于任何一个BTStack的应用程序都有一个main函数&#xff0c;这个main函数是统一的。这个main函数做了某些初始化之后&#xff0c;最终会调用到应用程序提供的btst…

学习Java的第八天

本节我们重点研究对象和类的概念。 对象&#xff08;Object&#xff09;是一个应用系统中的用来描述客观事物的实体&#xff0c;是有特定属性和行为&#xff08;方法&#xff09;的基本运行单位。是类的一个特殊状态下的实例。对象可以是一个实体、一个名词、一个可以想象为有…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:MenuItemGroup)

该组件用来展示菜单MenuItem的分组。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 包含MenuItem子组件。 接口 MenuItemGroup(value?: MenuItemGroupOptions) 参数&#xff1a; 参…

搜索组件的编写与数据的联动

src\components\SearchInput\index.vue 搜索组件编写 <template><div class"search-wrap"><input type"text":placeholder"placeholder":maxlength"maxlength":value"inputValue"input"searchData($ev…

软件测试 - postman高级使用

断言 概念&#xff1a;让程序代替人判断测试用例执行的结果是否符合预期的一个过程 特点&#xff1a; postman断言使用js编写&#xff0c;断言写在postman的tests中 tests脚本在发送请求之后执行&#xff0c;会把断言的结果最终在testresult中进行展示 常用的postman提供的…

Linux系统架构----nginx的访问控制

nginx的访问控制 一、nginx基于授权的访问控制概述 Nginx与Apache一样&#xff0c;可以实现基于用户权限的访问控制&#xff0c;当客户端想要访问相应的网站或者目录时&#xff0c;要求用户输入用户名和密码&#xff0c;才能正常访问配置步骤生成用户密码认证文件 &#xff1…

48. 【Linux教程】yum 软件包管理

本小节介绍如何在 Linux 系统中使用 yum 命令软件管理。 1.yum 简介 yum 是 Red Hat 软件包管理器&#xff0c;它能够查询有关可用软件包的信息&#xff0c;从存储库获取软件包&#xff0c;安装和卸载软件包&#xff0c;以及将整个系统更新到最新的可用版本。yum 在更新&#…

sql注入基础学习

1.常用SQL语句 01、显示数据库 show databases&#xff1b; 02、打开数据库 use db name&#xff1b; 03、显示数据表 show tables&#xff1b; 04、显示表结构 describe table_name&#xff1b; 05、显示表中各字段信息&#xff0c;即表结构 show columns from table_nam…

c++初阶------类和对象(下)

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

河北政采网2024年的入驻要求?

河北政采网2024年的入驻要求主要包括以下几个方面&#xff1a; 经营范围与资质&#xff1a;申请者需具有合法经营的营业执照&#xff0c;可以是一般纳税人、小规模纳税人或个体工商户。同时&#xff0c;申请者需要具备自主电商平台&#xff0c;该平台应为面向社会消费的专业销…

Linux学习(3)——使用Linux命令行

1.Shell是什么&#xff1f; shell本质上是Linux的应用程序&#xff0c;是Linux和用户进行沟通的桥梁 用户可以通过控制台终端输入各种命令&#xff0c;命令会被shell解析&#xff0c;解析后就会调用命令所对应的应用程序&#xff0c;应用程序又会去调用各种API接口以使用Linux内…

锐捷 EWEB auth 远程命令执行漏洞复现

一、漏洞信息 漏洞名称:锐捷 EWEB auth 远程命令执行漏洞 漏洞类别:远程代码执行 风险等级:高危 二、漏洞描述 锐捷睿易是锐捷网络针对商业市场的子品牌。拥有易网络、交换机、路由器、无线、安全、云服务六大产品线,解决方案涵盖商贸零售、酒店、KTV、网吧、监控安防…

【嵌入式——QT】文件系统和文件读写

【嵌入式——QT】文件系统和文件读写 文本文件读写二进制文件读写文件目录操作QCoreApplicationQFileQFileInfoQDirQTemporaryDir和QTemporaryFileQFileSystemWatcher 图示代码示例 文本文件读写 QT提供了两种读写纯文本文件的基本方法&#xff0c;一种是用QFile类的IODevice读…