关键词:
大家都知道,前后分离之后,后端响应最好以统一的格式的响应.
譬如:
名称 | 描述 |
---|---|
status | 状态码,标识请求成功与否,如 [1:成功;-1:失败] |
errorCode | 错误码,给出明确错误码,更好的应对业务异常;请求成功该值可为空 |
errorMsg | 错误消息,与错误码相对应,更具体的描述异常信息 |
resultBody | 返回结果,通常是 Bean 对象对应的 JSON 数据, 通常为了应对不同返回值类型,将其声明为泛型类型 |
话不多说,直接上代码
1. 定义一个统一响应结果类CommonResult<T>
import lombok.Data; @Data public final class CommonResult<T> { private int status = 1; private String errorCode = ""; private String errorMsg = ""; private T resultBody; public CommonResult() { } public CommonResult(T resultBody) { this.resultBody = resultBody; } }
2. 自定义一个ResponseBodyAdvice类
import org.springframework.context.annotation.Configuration; 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.RestControllerAdvice; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import qinfeng.zheng.data.common.CommonResult; @EnableWebMvc @Configuration @RestControllerAdvice(basePackages = "qinfeng.zheng.data.api") public class CommonResultResponseAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof CommonResult) { return body; } return new CommonResult<>(body); } }
3. 因为springmvc默认的message converter是Jackson框架,这个框架有点渣, 所以我们改成Fastjson
import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter(); FastJsonConfig config = new FastJsonConfig(); config.setCharset(Charset.forName("UTF-8")); config.setDateFormat("yyyyMMdd HH:mm:ssS"); //设置允许返回为null的属性 config.setSerializerFeatures(SerializerFeature.WriteMapNullValue); fastJsonConverter.setFastJsonConfig(config); List<MediaType> list = new ArrayList<>(); list.add(MediaType.APPLICATION_JSON_UTF8); fastJsonConverter.setSupportedMediaTypes(list); converters.add(fastJsonConverter); } }
4. 写一个Vo类和一个Controller类做测试,但是需要注解Controller类的包路径一定要与RestControllerAdvice注解中定义的package路径一致
import lombok.Data; import lombok.experimental.Accessors; @Accessors(chain = true) @Data(staticConstructor = "of") public class UserVo { private Integer id; private String name; private Integer age; }
package qinfeng.zheng.data.api; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import qinfeng.zheng.data.entity.UserVo; import java.util.ArrayList; import java.util.List; @RestController public class TestController { @GetMapping("/index") public String index() { return "index"; } @GetMapping("/list") public List<UserVo> list() { List<UserVo> list = new ArrayList<>(); UserVo userVo1 = UserVo.of().setId(1).setName("admin").setAge(12); UserVo userVo2 = UserVo.of().setId(2).setName("root").setAge(100); list.add(userVo1); list.add(userVo2); return list; } @GetMapping("/entity") public UserVo entity() { UserVo userVo1 = UserVo.of().setId(1).setName("admin").setAge(12); return userVo1; } @GetMapping("/responseEntity") public ResponseEntity responseEntity() { return new ResponseEntity(UserVo.of().setId(1).setName("大象").setAge(18), HttpStatus.OK); } }
5. 启动springboot项目,测试即可
测试应该是没有问题.,现在来看看源码,先看ResponseBodyAdvice接口
/** * Allows customizing the response after the execution of an {@code @ResponseBody} * or a {@code ResponseEntity} controller method but before the body is written * with an {@code HttpMessageConverter}. * * <p>Implementations may be registered directly with * {@code RequestMappingHandlerAdapter} and {@code ExceptionHandlerExceptionResolver} * or more likely annotated with {@code @ControllerAdvice} in which case they * will be auto-detected by both. * * @author Rossen Stoyanchev * @since 4.1 * @param <T> the body type */ public interface ResponseBodyAdvice<T> { /** * Whether this component supports the given controller method return type * and the selected {@code HttpMessageConverter} type. * @param returnType the return type * @param converterType the selected converter type * @return {@code true} if {@link #beforeBodyWrite} should be invoked; * {@code false} otherwise */ boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType); /** * Invoked after an {@code HttpMessageConverter} is selected and just before * its write method is invoked. * @param body the body to be written * @param returnType the return type of the controller method * @param selectedContentType the content type selected through content negotiation * @param selectedConverterType the converter type selected to write to the response * @param request the current request * @param response the current response * @return the body that was passed in or a modified (possibly new) instance */ @Nullable T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response); }
注释说的很明白,该接口方法是在Controller方法执行之后, 且HttpMessageConverter执行之前调用,所以,我们完全可以在这儿对Controller方法返回的结果进行统一格式处理,然后再说消息转换器进行转换,响应到视图层.
下面再来分析一下 CommonResultResponseAdvice 类是如何加载到spring的上下文环境中的.
通过@EnableWebMvc注解 ----> DelegatingWebMvcConfiguration.class ----> WebMvcConfigurationSupport.class
WebMvcConfigurationSupport这个类就很关键了......
在这个类中,有这样一段代码
/** * Returns a {@link RequestMappingHandlerAdapter} for processing requests * through annotated controller methods. Consider overriding one of these * other more fine-grained methods: * <ul> * <li>{@link #addArgumentResolvers} for adding custom argument resolvers. * <li>{@link #addReturnValueHandlers} for adding custom return value handlers. * <li>{@link #configureMessageConverters} for adding custom message converters. * </ul> */ @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcValidator") Validator validator) { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); adapter.setContentNegotiationManager(contentNegotiationManager); adapter.setMessageConverters(getMessageConverters()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator)); adapter.setCustomArgumentResolvers(getArgumentResolvers()); adapter.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice())); adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice())); } AsyncSupportConfigurer configurer = new AsyncSupportConfigurer(); configureAsyncSupport(configurer); if (configurer.getTaskExecutor() != null) { adapter.setTaskExecutor(configurer.getTaskExecutor()); } if (configurer.getTimeout() != null) { adapter.setAsyncRequestTimeout(configurer.getTimeout()); } adapter.setCallableInterceptors(configurer.getCallableInterceptors()); adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors()); return adapter; }
其它都无所谓,至少我们知道这儿会将 RequestMappingHandlerAdapter注入到spring的上下文环境中去. 而RequestMappingHandlerAdapter又是 InitializingBean 接口的一个实现. 看到InitializingBean 接口我们肯定会看看它的实现方法 afterPropertiesSet, 不巧的是RequestMappingHandlerAdapter真的在afterPropertiesSet方法中干了不少的事.
@Override public void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans initControllerAdviceCache(); if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
看initControllerAdviceCache方法, 就会看到下面这段代码
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) { List<ControllerAdviceBean> adviceBeans = new ArrayList<>(); for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) { if (!ScopedProxyUtils.isScopedTarget(name)) { ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class); if (controllerAdvice != null) { // Use the @ControllerAdvice annotation found by findAnnotationOnBean() // in order to avoid a subsequent lookup of the same annotation. adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice)); } } } OrderComparator.sort(adviceBeans); return adviceBeans; }
好吧,前面这一坨其实都是对数据格式的统一的封装, 而如何将java bean转成json格式数据,这块功能其实都是靠HttpMessageConverter来实现的.
springboot统一功能处理(用户登录权限效验-拦截器异常处理数据格式返回)(代码片段)
...格式返回的实现3.2@ControllerAdvice源码分析本篇将要学习SpringBoot统一功能处理模块,这也是AOP的实战环节统一用户登录权限的效验实现接口HandlerInterceptor+WebMvcConfigurer统一异常处理使用注解@RestControllerAdvice+@Excepti... 查看详情
springboot定义统一的返回异常提示数据格式(代码片段)
一描述1.1没有加全局异常处理1.这里设置一个字符串为空指针异常,然后看看返回给前端的信息。 2.返回结果 3.效果看起来不友好的提示1.2 添加全局异常处理1.代码:添加一个全局异常处理类@ControllerAdvicepublicclassGlob... 查看详情
每天用springboot,还不懂restfulapi返回统一数据格式是怎么实现的?
关于Spring的全局处理,我有两方面要说:统一数据返回格式统一异常处理为了将两个问题说明清楚,将分两个章节分别说明,本章主要说第一点有童鞋说,我们项目都做了这种处理,就是在每个API都单独工具类将返回值进行封装... 查看详情
springboot返回统一的json标准格式(代码片段)
自定义状态码枚举类封装返回结果全局异常捕获处理,使用@RestControllerAdvice注解拦截Controller方法的返回值,统一处理返回值/响应体创建Controller,准备测试请求接口,查看响应结果近年来,随着移动互联网... 查看详情
springboot返回统一的json标准格式(代码片段)
自定义状态码枚举类封装返回结果全局异常捕获处理,使用@RestControllerAdvice注解拦截Controller方法的返回值,统一处理返回值/响应体创建Controller,准备测试请求接口,查看响应结果近年来,随着移动互联网... 查看详情
springboot如何统一后端返回格式?老鸟们都是这样玩的!
大家好,我是飘渺。今天我们来聊一聊在基于SpringBoot前后端分离开发模式下,如何友好的返回统一的标准格式以及如何优雅的处理全局异常。首先我们来看看为什么要返回统一的标准格式?为什么要对SpringBoot返回统... 查看详情
springboot统一后端返回格式?老鸟们都是这样玩的!(代码片段)
大家好,我是bigsai。今天我们来聊一聊在基于SpringBoot前后端分离开发模式下,如何友好的返回统一的标准格式以及如何优雅的处理全局异常。首先我们来看看为什么要返回统一的标准格式?为什么要对SpringBoot返回统... 查看详情
接口返回值response统一标准格式(代码片段)
一、为什么要对springboot的接口返回值统一标准格式 springboot默认情况下的response格式:String、Object、void、异常,以上几种情况,如果和客户端开发人员联调接口,他们会很懵逼,因为你给他们的接口没有一个统一的格式,客... 查看详情
springboot如何统一后端返回格式?老鸟们都是这样玩的!(代码片段)
...c;我是只爱教妹学Java的秃头哥。今天我们来聊一聊在基于SpringBoot前后端分离开发模式下,如何友好的返回统一的标准格式以及如何优雅的处理全局异常。首先我们来看看为什么要返回统一的标准格式?为什么要对SpringBoot... 查看详情
springboot自动配置原理浅析(代码片段)
springboot自动配置原理浅析springboot版本2.5.5注解@SpringBootApplication的源码:@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@E 查看详情
统一异常处理这样剖析,安否?(代码片段)
...生活还是计算机世界难免发生异常,上一篇文章RESTfulAPI返回统一JSON数据格式说明了统一返回的处理,这是请求一切正常的情形;这篇文章将说明如何统一处理异常,以及其背后的实现原理,老套路,先实现,后说明原理,有了... 查看详情
springboot定义统一的返回json渲染格式(代码片段)
一 实操案例1.定义map类型packagecom.ljf.spring.boot.demo.common;importjava.util.LinkedHashMap;importjava.util.Map;/***@ClassName:R*@Description:TODO*@Author:liujianfu*@Date:2022/12/03 15:53:28 * 查看详情
springboot2全局统一返回restful风格数据统一异常处理
...vice拦截异常并统一处理。开发环境:IntelliJIDEA2019.2.2jdk1.8SpringBoot2.2.21、创建一个SpringBoot项目,pom.xml引用的依赖包如下<dependency><gr 查看详情
浅析volatile原理及其使用(代码片段)
...手开始写.今天算是迈出了人生的一大步^_^!volatile的定义及其实现定义:如果一个字段被声明成volatile,那么java线程内存模型将确保所有线程看到的这个变量的值都是一致的.从它的定义当中咱们也可以了解到volatile具有可见性的特性... 查看详情
springboot2.0统一返回rest风格数据结构与统一异常处理
参考技术A结果: 查看详情
springboot拦截器使用和常用功能统一封装(代码片段)
文章目录1.拦截器1.1拦截器的使用1.2拦截器的原理2.用户登录权限校验3.统一异常处理4.统一数据返回格式1.拦截器1.1拦截器的使用Spring中提供了拦截器HandlerInteceptor,它的具体使用分为以下两个步骤:创建自定义拦截器ÿ... 查看详情
springboot项目统一结果,统一异常,统一日志,写的太好了。。(代码片段)
作者:永动的图灵机链接:https://juejin.cn/post/6844904033488994317统一结果返回目前的前后端开发大部分数据的传输格式都是json,因此定义一个统一规范的数据格式有利于前后端的交互与UI的展示。统一结果的一般形式是否... 查看详情
cglib动态代理实现及其原理浅析(代码片段)
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采... 查看详情