《学会 SpringMVC 系列 · 返回值处理器》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

写在前面的话

前几篇博文,大致了解了SpringMVC请求流程中的入参和出参处理环节,后续的几篇博文,会将流程中涉及的若干关键环节单独拿出来讲解,并结合实战中的运用,帮助领略SpringMVC带来的定制和扩展能力。

本篇文章先介绍一下返回值处理器相关内容。

学前准备与回顾

本篇为 SpringMVC 源码分析系列文章,正片开始前,先总结回顾一下全流程。

【一次请求的主链路节点】

DispatcherServlet#doDispatch(入口方法)

DispatcherServlet#getHandler(根据path找到对应的HandlerExecutionChain

DispatcherServlet#getHandlerAdapter(根据handle找到对应的HandlerAdapter

HandlerExecutionChain#applyPreHandle(触发拦截器的前置逻辑)

AbstractHandlerMethodAdapter#handle(核心逻辑)

HandlerExecutionChain#applyPostHandle(触发拦截器的后置逻辑)

【核心handle方法的主链路节点】

RequestMappingHandlerAdapter#handleInternal(入口方法)

RequestMappingHandlerAdapter#invokeHandlerMethod(入口方法2)

ServletInvocableHandlerMethod#invokeAndHandle(入口方法3)

InvocableHandlerMethod#invokeForRequest(参数和实际执行的所在,3.1)

InvocableHandlerMethod#getMethodArgumentValues(参数处理,3.1.1)

InvocableHandlerMethod#doInvoke(实际执行,3.1.2)

HandlerMethodReturnValueHandlerComposite#handleReturnValue(返回处理,3.2)

【针对 @RequestBody 和 @ResponseBody 场景】

相关博文
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《学会 SpringMVC 系列 · 剖析出参处理》
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》


SpringMVC 返回值处理器

技术说明

HandlerMethodReturnValueHandler 是 Spring MVC 中的一个接口,用于处理控制器方法的返回值。它是处理返回值与 HTTP 响应之间关系的核心机制。通过自定义 HandlerMethodReturnValueHandler,可以实现特定返回值类型的处理逻辑,例如格式化输出、包装响应数据等。

接口信息

HandlerMethodReturnValueHandler 接口定义了两个主要方法:

boolean supportsReturnType(MethodParameter returnType):支持处理给定返回值类型的处理器方法。

void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest):处理实际的返回值。

MethodParameter:封装方法参数或方法返回值的元数据,包括类型、注解等信息。

ModelAndViewContainer:用于存储处理器方法的返回值以及视图名称或 View 对象。

NativeWebRequest:封装当前 HTTP 请求和响应。

常见列表

RequestResponseBodyMethodProcessor -- 处理 @ResponseBody

ViewNameMethodReturnValueHandLer -- 处理返回 String(视图)

自定义 HandlerMethodReturnValueHandler -- 包装返回值输出

应用场景

统一响应格式:可以用于统一返回格式,将所有返回值封装为特定的响应对象。

数据包装:在返回数据之前,进行某些特定的包装操作,如添加元数据、状态码等。

权限过滤:在返回之前,检查并过滤掉用户无权限访问的数据。

日志记录:对返回的数据进行记录,用于审计或分析。

自定义示例

代码:study-up#MyHandlerMethodReturnValueHandler

逻辑:对添加了自定义注解的接口,包装上一层实体返回。

实现步骤:

Step1、添加自定义返回值处理器

java 复制代码
public class MyHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResultModelAnnotation.class) || returnType.hasMethodAnnotation(ResultModelAnnotation.class));
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        ResultModel<Object> resultModel;

        String message = "";
        Operation methodAnnotation = returnType.getMethodAnnotation(Operation.class);
        if (methodAnnotation != null) {
            message = methodAnnotation.summary() + "成功";
        }

        if (returnValue instanceof ResultModel) {
            resultModel = (ResultModel<Object>) returnValue;
            if (!resultModel.isSuccess()) {
                resultModel.setMessage(message + "失败");
            }
        } else {
            resultModel = ResultModel.success(returnValue, message);
        }

        mavContainer.setRequestHandled(true);
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        if (response != null) {
            response.setStatus(HttpStatus.OK.value());
            response.setHeader("result-model", "true");
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding("UTF-8");
            try (PrintWriter writer = response.getWriter()) {
                writer.write(JSON.toJSONString(resultModel, SerializerFeature.WriteMapNullValue));
                writer.flush();

                // 设置请求处理已完成,防止Spring继续处理返回值
                mavContainer.setRequestHandled(true);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

Step2、配置返回值处理器

Tips:使用addReturnValueHandlers方式要注意顺序的影响。

通过自定义 RequestMappingHandlerAdapter 的方式实现效果。

java 复制代码
public class CustomResultMappingHandlerAdapter extends RequestMappingHandlerAdapter {

    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();
        List<HandlerMethodReturnValueHandler> returnValueHandlers = super.getReturnValueHandlers();
        MyHandlerMethodReturnValueHandler handler = new MyHandlerMethodReturnValueHandler();
        List<HandlerMethodReturnValueHandler> list = new ArrayList<>();
        list.add(handler);
        list.addAll(returnValueHandlers);
        super.setReturnValueHandlers(list);
    }
}
java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public WebMvcRegistrations feignWebRegistrations() {
        return new WebMvcRegistrations() {
            @Override
            public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
                return new CustomResultMappingHandlerAdapter();
            }
        };
    }

}

Step3、测试效果

正常书写Controller,该处理器会包裹ResultModel返回。

java 复制代码
@RestController
@ResultModelAnnotation
@Tag(name = "教师信息控制层", description = "教师信息")
@RequestMapping(value = "/zyTeacherInfo")
public class ZyTeacherInfoController extends BaseController {

    @Autowired
    private ZyTeacherInfoService zyTeacherInfoService;

    @GetMapping("/{id}")
    public ZyTeacherInfo get(@PathVariable String id) {
        return zyTeacherInfoService.getById(id);
    }
}

返回结果如下:

json 复制代码
{
  "code": "00000",
  "data": {
    "createdTime": "2024-05-16 20:07:21",
    "modifiedTime": "2024-07-29 14",
    "sortNo": null,
    "stuItem": null,
    "teaCode": "1",
    "teaConfig": null,
    "teaImg": null,
    "teaName": "张老师",
    "teaPhone": null,
    "teaType": null,
    "validFlag": "1"
  },
  "error": "",
  "message": "获取教师信息表详细信息成功",
  "success": true
}

观察源码可以看到,这里走的是自定义的返回值处理器,细节就不展开了。

RequestResponseBodyMethodProcessor

前面源码分析章节,可以看到,参数解析和返回值解析中,都用到了 RequestResponseBodyMethodProcessor,那它为何可以如此呢?其实看一下类图就明白了。

既实现了HandlerMethodReturnValueHandler接口,也实现了HandlerMethodArgumentResolver,看它的匹配方法也可以看出来,就是针对 @RequestBody 和 @ResponseBody 的处理器。


总结陈词

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

相关推荐
zquwei11 分钟前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
TT哇17 分钟前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
火烧屁屁啦40 分钟前
【JavaEE进阶】初始Spring Web MVC
java·spring·java-ee
w_31234541 小时前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安1 小时前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA1 小时前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
Q_19284999061 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
sdaxue.com1 小时前
帝国CMS:如何去掉帝国CMS登录界面的认证码登录
数据库·github·网站·帝国cms·认证码
张国荣家的弟弟1 小时前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S2 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring