前言
上一节课定义好了统一返回值的格式,但是还需要在每个接口都返回定义好的Reust对象,并在此对象中携带业务相关的数据。
这一节讲统一处理返回值,即每个接口只返回业务相关的数据,然后由拦截器中统一包装成Result对象返回。
再重复一下思路:
- 增加一个自定义注解类,表示接口返回值都要统一包装
- 定义拦截器,使用拦截器对所有请求对所有请求拦截,将所有使用@ResponseResult注解的类和方法做统一处理
- 配置拦截器,拦截所有请求
- 重新封装返回体
- 在控制器类或方法中使用此注解,测试效果
自定义注解
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