SpringBoot+Vue项目系列教程-06-统一返回值的全局处理

前言

上一节课定义好了统一返回值的格式,但是还需要在每个接口都返回定义好的Reust对象,并在此对象中携带业务相关的数据。

这一节讲统一处理返回值,即每个接口只返回业务相关的数据,然后由拦截器中统一包装成Result对象返回。

再重复一下思路:

  1. 增加一个自定义注解类,表示接口返回值都要统一包装
  2. 定义拦截器,使用拦截器对所有请求对所有请求拦截,将所有使用@ResponseResult注解的类和方法做统一处理
  3. 配置拦截器,拦截所有请求
  4. 重新封装返回体
  5. 在控制器类或方法中使用此注解,测试效果

自定义注解

 package com.hgt.annotation;
 
 import java.lang.annotation.*;
 
 import static java.lang.annotation.ElementType.TYPE;
 
 /*第一步:自定义的注解类,对接口返回值统一包装*/
 @Retention(RetentionPolicy.RUNTIME)
 @Target({TYPE, ElementType.METHOD})
 @Documented
 public @interface ResponseResult {
 }

使用自定义注解

 package com.hgt.intercept;
 
 import com.hgt.annotation.ResponseResult;
 import org.springframework.stereotype.Component;
 import org.springframework.web.method.HandlerMethod;
 import org.springframework.web.servlet.HandlerInterceptor;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.lang.reflect.Method;
 
 /*第二步:定义拦截器
     使用拦截器对所有请求对所有请求拦截,将所有使用@ResponseResult注解的类和方法做统一处理*/
 @Component
 public class ResponseResultInterceptor implements HandlerInterceptor {
     //标记
     public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
 
     /*拦截请求,取所有使用@ResponseResult的类或方法添加标记*/
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
             throws Exception {
         if (handler instanceof HandlerMethod) {
             final HandlerMethod handlerMethod = (HandlerMethod) handler;
             final Class<?> clazz = handlerMethod.getBeanType();
             final Method method = handlerMethod.getMethod();
 
             if (clazz.isAnnotationPresent(ResponseResult.class)) {
                 request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
             } else if (method.isAnnotationPresent(ResponseResult.class)) {
                 request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class));
             }
         }
         return true;
     }
 }

配置拦截器

 package com.hgt.conf;
 
 import com.hgt.intercept.ResponseResultInterceptor;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 /**
  * 第三步:注册拦截器,拦截所有请求
  */
 @Configuration
 public class WebConfig implements WebMvcConfigurer {
 
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         //  /** 拦截所有请求,处理返回值
         registry.addInterceptor(new ResponseResultInterceptor()).addPathPatterns("/**");
         /*拦截处理登录业务*/
         //registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
     }
 }

重新封装返回体

 package com.hgt.handler;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.hgt.annotation.ResponseResult;
 import com.hgt.pojo.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.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
 
 import javax.servlet.http.HttpServletRequest;
 /*第四步:根据标记,重新封装返回体*/
 /*重新封装返回体*/
 @ControllerAdvice
 public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
     /*标记*/
     public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
 
     /*请求 是否包含了 包装注解,标记;不包含直接返回,不需要写返回体
     * 此方法返回true时,才会执行beforeBodyWrite()对返回体封装*/
     @Override
     public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
         ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
         HttpServletRequest request = sra.getRequest();
         //判断请求是否包含包装标记
         ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN);
 
         return responseResultAnn == null ? false : true;
     }
 
     /*对返回体封装*/
     @Override
     public Object beforeBodyWrite(Object body,
                                   MethodParameter returnType, MediaType selectedContentType,
                                   Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                   ServerHttpRequest request, ServerHttpResponse response) {
         System.out.println("正在重写返回体...");
 
         // String类型不能直接包装,所以要进行些特别的处理
         if (returnType.getGenericParameterType().equals(String.class)) {
             ObjectMapper objectMapper = new ObjectMapper();
             // 将数据包装在Result里后,再转换为json字符串响应给前端
             try {
                 return objectMapper.writeValueAsString(Result.success(body));
             } catch (JsonProcessingException e) {
                 e.printStackTrace();
                 throw new RuntimeException(e.getMessage());
             }
         }
 
         //防止重复封装
         if(body instanceof Result){
             return body;
         }
         return Result.success(body);
     }
 }

使用此注解

在控制器类中使用@ResponResult注解,分别测试返回String,List和Map的效果。

 package com.hgt.controller;
 
 import com.hgt.pojo.Person;
 import com.hgt.annotation.ResponseResult;
 import com.hgt.pojo.Result;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 @RestController
 @RequestMapping(path = "api")
 /*第二步:使用自定义包装类注解*/
 @ResponseResult
 public class HelloController {
     @RequestMapping("hello")
     public String hello() {
         String s = "hello spring boot";
         return s;
     }
 
     @RequestMapping("list")
     public List<String> testList() {
         List<String> list = new ArrayList<>();
         list.add("hello");
         list.add("spring");
         list.add("boot");
         return list;
     }
 
     @RequestMapping("map")
     public Map<String, String> testMap() {
         Map<String, String> map = new HashMap<>();
         map.put("name", "liuzhidong");
         map.put("age", "12");
         map.put("language", "spring boot,java,vue");
         return map;
     }
 }
 

访问:http://localhost:8083/api/hello

结果:

 {"code":1,"msg":"成功","data":"hello spring boot"}

访问:http://localhost:8083/api/list

结果:

 {"code":1,"msg":"成功","data":["hello","spring","boot"]}

访问:http://localhost:8083/api/map

结果:

 {"code":1,"msg":"成功","data":{"name":"liuzhidong","language":"spring boot,java,vue","age":"12"}}

这就一劳永逸的解决了返回值的问题,以后所有的接口只关注业务逻辑,封装的活交给拦截器干就成了,你说方便不方便。

提出问题

其实还有一个小问题没有解决,估计大家也会有感觉,在代码中直接处理异常会很麻烦,而且具体的异常信息也不能让前端的人员看到,更不能反馈问题给后端开发者了。

如果能统一处理,无论什么异常信息,都封装到Result返回,系统有了更友好的提示,也更方便开发人员处理问题,这样无疑是最好的。

这个问题放在下一节课讲。

总结

讲到了这里,可以多说几句了。

其实这就是SpringBoot努力解决的问题:让开发者专注于业务逻辑,其它事情交能Spring容器处理就可以。

现在试着理解一下Spring的工作原理:启动Main方法运行主程序后,容器会根据不同的注解把对象加载到容器中,期间产生很多的数据,再利用AOP面向切面编程,在数据产生的不同阶段,更细粒度的加工处理,最后产生出符合我们想要的数据。

个人认为带着看源码是浪费时间的,因为没有体会到运行原理的情况下,由别个带着看源码会处于懵逼的状态。而经过多次练习,加上自己的体会心得,再带着疑问反过来看源码,效率无疑会更高,体会的更深刻,也更能体会到学习的乐趣。这也是孔子所说的“学而时习之,不亦乐乎”。希望大家都能在学习中得到快乐,而不是压力。

 

代码下载:https://github.com/316620938/shop-springboot.git

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值