统一回包格式比较优雅的实现

1,需求

需要统一回包格式,用比较优雅的方式处理。也就是返回格式为{code, data, msg}。

2,前言

这个需求的暴力实现是使用拦截器HandlerInterceptorAdapter,配合自定义注解。但是不够优雅,后面一直有在看这个,但是奈何没有找到合适的方法。虽然已经发现实现HandlerMethodReturnValueHandler接口,添加支持类型并更改回包,然后在添加到RequestMappingHandlerAdapter中去生效。但是一顿操作之后,发现这个自定义的返回值处理器没有生效,甚至原因也找到了,自定义的优先级太低,spring有默认的returnValueHandler,并且处理逻辑是只会被一个处理器处理。所以就失败了。虽然后边想着可以用反射或者是继承RequestMappingHandlerAdapter这个类,然后重新注册为Bean,但是这样更不优雅,因此就停滞了。直到昨天又看见了这个没有完成的实验,因此就来看了下。

3,分析

使用ModelAndViewMethodReturnValueHandler的原因是我们会使用@RestController或者是@ResponseBody告诉Spring返回的是ModelAndView,接口返回为Json。那我们可以使用@Controller替代@RestController。然后再配合我们自定义的返回值处理器。就可以实现返回值类型的封装。

4,实验开始:

(4.1)这里是RequestMappingHandlerAdapter的获取返回值处理器的方法。这里会加入我们自定义的返回值处理器

	private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
		List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();

		// Single-purpose return value types
		handlers.add(new ModelAndViewMethodReturnValueHandler());
// 第一个放进来的是ModelAndViewReturnValueHandler,并且处理的逻辑是,被一个处理后,就不会给其他的returnValueHandler来处理。
		handlers.add(new ModelMethodProcessor());
		handlers.add(new ViewMethodReturnValueHandler());
		handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
				this.reactiveRegistry, this.taskExecutor, this.contentNegotiationManager));
		handlers.add(new StreamingResponseBodyReturnValueHandler());
		handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
				this.contentNegotiationManager, this.requestResponseBodyAdvice));
		handlers.add(new HttpHeadersReturnValueHandler());
		handlers.add(new CallableMethodReturnValueHandler());
		handlers.add(new DeferredResultMethodReturnValueHandler());
		handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

		// Annotation-based return value types
		handlers.add(new ModelAttributeMethodProcessor(false));
		handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
				this.contentNegotiationManager, this.requestResponseBodyAdvice));

		// Multi-purpose return value types
		handlers.add(new ViewNameMethodReturnValueHandler());
		handlers.add(new MapMethodProcessor());

		// Custom return value types
		if (getCustomReturnValueHandlers() != null) {
			handlers.addAll(getCustomReturnValueHandlers());
		}// 我们自定义的处理器在这里,优先级很低,所以执行不到

		// Catch-all
		if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
			handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
		}
		else {
			handlers.add(new ModelAttributeMethodProcessor(true));
		}

		return handlers;
	}

(4.2)这个是我们自定义的回包格式,如果回包格式不统一的话,需要包装一下。

public class ReturnValueHandlerTest implements HandlerMethodReturnValueHandler {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        RequestMappingTest annotation = returnType.getAnnotatedElement().getDeclaredAnnotation(RequestMappingTest.class);
// 实际上,这里的注解可是自定义,我这里时为了方便使用的路由映射注解
        return annotation != null;
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        mavContainer.setRequestHandled(true);// 这一步标识这个request已经被处理过了,不然会重复处理。这个很重要的

        Object result = returnValue;
        if (!(returnValue instanceof CResponse)) {// 这里判断一下回包是不是通用的格式,如果不是,需要封装
            result = new CResponse<>(returnValue);
        }

        HttpServletResponse nativeResponse = webRequest.getNativeResponse(HttpServletResponse.class);
        assert nativeResponse != null;
        byte[] bytes = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(result);

        nativeResponse.resetBuffer();
        nativeResponse.getOutputStream().write(bytes);// 这里直接使用流写出
        nativeResponse.getOutputStream().flush();
    }
}

 (4.3)添加到RequestMappingHandlerAdapter中去

@Configuration
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public HandlerMapping handlerMappingTest() {
        HandlerMappingHandlerTest handlerTest = new HandlerMappingHandlerTest();
        handlerTest.setInterceptors(new InterceptorTest());
        return handlerTest;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new InterceptorTest());
    }

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
        handlers.add(new ReturnValueHandlerTest());//添加到RequestMappingHandlerAdapter中
    }
}

 5,结果验证

@Controller
@RequestMappingTest("/1")
@RequestMapping("/2")// 这两个注解可以同时放在这里生效
public class ControllerMappingTest {

    @RequestMappingTest("/test1")
    @ResponseBody
    public Object test1(@ModelAttribute A a, @ModelAttribute B b) {
        System.out.println(a.getId());
        System.out.println(b.getId());
        return CResponse.of(true);
    }

    @RequestMappingTest("/test2")
    public Object test2(@ModelAttribute A a, @ModelAttribute B b) {
        System.out.println(a.getId());
        System.out.println(b.getId());
        return true;//这里的返回值为true,不是CResponse的格式,需要包装
    }

    @RequestMapping("/test3")
    @ResponseBody// 如果要使用ModleAndViewReturnValueHandler,需要加这个,不然出错。
    public Object test(@ModelAttribute A a, @ModelAttribute B b) {
        System.out.println(a.getId());
        System.out.println(b.getId());
        return CResponse.of(true);
    }
}

结果:

可以看见成功被包装为通用格式了。

5,后续

demo连接,这里是和自定义路由注解揉在一块儿的,看起来可能不够整洁,但好在整体比较简单。大家可以自己动手试一试。

这一套写法还是比较符合Spring的规范的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值