赞
踩
具体用法参考:@ControllerAdvice与@RestControllerAdvice
除了上面文章说到了用法,这里再补充下@InitBinder
和@ResponseBodyAdvice
@InitBinder
的来源有两个:
所以,在RequestMappingHandlerAdapter执行控制器方法前,肯定要全部解析好@InitBinder,因为要对控制器方法进行增强。
看下RequestMappingHandlerAdapter源码有如下两个属性
private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<>(64);
private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache = new LinkedHashMap<>();
initBinderCache
:就是用来存储@Controller 中 @InitBinder 标注的方法initBinderAdviceCache
:用来存储@Controller 中 @InitBinder 标注的方法当解析一个@InitBinder 时,就加到这两个缓存里,后面再遇到,直接从这两个缓存里找即可,不用每次解析了。
下面看一个实例,体会下两种@InitBinder用法的区别
首先沿用上一章的MyDateFormatter:
package com.cys.spring.chapter12; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.format.Formatter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class MyDateFormatter implements Formatter<Date> { private static final Logger log = LoggerFactory.getLogger(MyDateFormatter.class); private final String desc; public MyDateFormatter(String desc) { this.desc = desc; } @Override public String print(Date date, Locale locale) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd"); return sdf.format(date); } @Override public Date parse(String text, Locale locale) throws ParseException { log.debug(">>>>>> 进入了: {}", desc); SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd"); return sdf.parse(text); } }
创建一个WebConfig
package com.cys.spring.chapter12; import com.cys.spring.chapter12.MyDateFormatter; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Controller; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.InitBinder; @Configuration public class WebConfig { @ControllerAdvice static class MyControllerAdvice { @InitBinder public void binder3(WebDataBinder webDataBinder) { webDataBinder.addCustomFormatter(new MyDateFormatter("binder3 转换器")); } } @Controller static class Controller1 { @InitBinder public void binder1(WebDataBinder webDataBinder) { webDataBinder.addCustomFormatter(new MyDateFormatter("binder1 转换器")); } public void foo() { } } @Controller static class Controller2 { @InitBinder public void binder21(WebDataBinder webDataBinder) { webDataBinder.addCustomFormatter(new MyDateFormatter("binder21 转换器")); } @InitBinder public void binder22(WebDataBinder webDataBinder) { webDataBinder.addCustomFormatter(new MyDateFormatter("binder22 转换器")); } public void bar() { } } }
测试类:
package com.cys.spring.chapter12; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.web.method.ControllerAdviceBean; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; public class TestControllerAdvice { private static final Logger log = LoggerFactory.getLogger(TestControllerAdvice.class); public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter(); handlerAdapter.setApplicationContext(context); handlerAdapter.afterPropertiesSet(); log.debug("1. 刚开始..."); showBindMethods(handlerAdapter); Method getDataBinderFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory", HandlerMethod.class); getDataBinderFactory.setAccessible(true); log.debug("2. 模拟调用 Controller1 的 foo 方法时 ..."); getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo"))); showBindMethods(handlerAdapter); log.debug("3. 模拟调用 Controller2 的 bar 方法时 ..."); getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("bar"))); showBindMethods(handlerAdapter); context.close(); } @SuppressWarnings("all") private static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException, IllegalAccessException { Field initBinderAdviceCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache"); initBinderAdviceCache.setAccessible(true); Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderAdviceCache.get(handlerAdapter); log.debug("全局的 @InitBinder 方法 {}", globalMap.values().stream() .flatMap(ms -> ms.stream().map(m -> m.getName())) .collect(Collectors.toList()) ); Field initBinderCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache"); initBinderCache.setAccessible(true); Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>) initBinderCache.get(handlerAdapter); log.debug("控制器的 @InitBinder 方法 {}", controllerMap.entrySet().stream() .flatMap(e -> e.getValue().stream().map(v -> e.getKey().getSimpleName() + "." + v.getName())) .collect(Collectors.toList()) ); } }
运行结果如下:
12:00:31.528 [main] DEBUG com.cys.spring.chapter12.TestControllerAdvice - 1. 刚开始...
12:00:31.531 [main] DEBUG com.cys.spring.chapter12.TestControllerAdvice - 全局的 @InitBinder 方法 [binder3]
12:00:31.533 [main] DEBUG com.cys.spring.chapter12.TestControllerAdvice - 控制器的 @InitBinder 方法 []
12:00:31.534 [main] DEBUG com.cys.spring.chapter12.TestControllerAdvice - 2. 模拟调用 Controller1 的 foo 方法时 ...
12:00:31.540 [main] DEBUG com.cys.spring.chapter12.TestControllerAdvice - 全局的 @InitBinder 方法 [binder3]
12:00:31.541 [main] DEBUG com.cys.spring.chapter12.TestControllerAdvice - 控制器的 @InitBinder 方法 [Controller1.binder1]
12:00:31.541 [main] DEBUG com.cys.spring.chapter12.TestControllerAdvice - 3. 模拟调用 Controller2 的 bar 方法时 ...
12:00:31.542 [main] DEBUG com.cys.spring.chapter12.TestControllerAdvice - 全局的 @InitBinder 方法 [binder3]
12:00:31.542 [main] DEBUG com.cys.spring.chapter12.TestControllerAdvice - 控制器的 @InitBinder 方法 [Controller2.binder21, Controller2.binder22, Controller1.binder1]
ResponseBodyAdvice
是 Spring Framework 提供的一个接口,用于在控制器方法返回响应体之前,对响应体进行自定义处理。通过实现这个接口,你可以拦截并修改响应体的内容,或者添加额外的逻辑,比如加密、序列化、添加日志等。
ResponseBodyAdvice
接口定义了两个方法:
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType)
: 这个方法用于判断当前的 ResponseBodyAdvice
是否支持处理给定的返回类型和转换器类型。如果返回 true
,则 beforeBodyWrite
方法将会被调用;如果返回 false
,则不会调用。Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response)
: 这个方法在实际写入响应体之前被调用,你可以在这里对响应体 body
进行处理,并返回处理后的结果。示例:
假如我们有一个统一的返回类型Result<T>
,那么我们想在控制器返回值做检查,如果返回的不是Result类型,我们就注定包装一层。
Result:
package com.cys.spring.chapter12; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @JsonInclude(JsonInclude.Include.NON_NULL) public class Result { private int code; private String msg; private Object data; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } @JsonCreator private Result(@JsonProperty("code") int code, @JsonProperty("data") Object data) { this.code = code; this.data = data; } private Result(int code, String msg) { this.code = code; this.msg = msg; } public static Result ok() { return new Result(200, null); } public static Result ok(Object data) { return new Result(200, data); } public static Result error(String msg) { return new Result(500, "服务器内部错误:" + msg); } }
创建一个控制器:
package com.cys.spring.chapter12; import org.springframework.context.annotation.Configuration; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotationUtils; 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.stereotype.Controller; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @Configuration public class WebConfig2 { @ControllerAdvice static class MyControllerAdvice implements ResponseBodyAdvice<Object> { // 满足条件才转换 @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { // 先写死返回true return true; } // 将 User 或其它类型统一为 Result 类型 @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { System.out.println("开始返回值包装"); if (body instanceof Result) { return body; } return Result.ok(body); } } @Controller public static class MyController { @ResponseBody public User user() { return new User("王五", 18); } } public static class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } }
测试类:
package com.cys.spring.chapter12; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.method.ControllerAdviceBean; import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver; import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver; import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite; import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.mvc.method.annotation.*; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; public class TestResponseBodyAdvice { // {"name":"王五","age":18} // {"code":xx, "msg":xx, data: {"name":"王五","age":18} } public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig2.class); ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod( context.getBean(WebConfig2.MyController.class), WebConfig2.MyController.class.getMethod("user") ); handlerMethod.setDataBinderFactory(new ServletRequestDataBinderFactory(Collections.emptyList(), null)); handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer()); handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context)); handlerMethod.setHandlerMethodReturnValueHandlers(getReturnValueHandlers(context)); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); ModelAndViewContainer container = new ModelAndViewContainer(); handlerMethod.invokeAndHandle(new ServletWebRequest(request, response), container); System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8)); } public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) { HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); composite.addResolvers( new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false), new PathVariableMethodArgumentResolver(), new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()), new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()), new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()), new ServletRequestMethodArgumentResolver(), new ServletModelAttributeMethodProcessor(false), new RequestResponseBodyMethodProcessor(Collections.singletonList(new MappingJackson2HttpMessageConverter())), new ServletModelAttributeMethodProcessor(true), new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true) ); return composite; } public static HandlerMethodReturnValueHandlerComposite getReturnValueHandlers(AnnotationConfigApplicationContext context) { // 添加 advice List<ControllerAdviceBean> annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context); List<Object> collect = annotatedBeans.stream().filter(b -> ResponseBodyAdvice.class.isAssignableFrom(b.getBeanType())) .collect(Collectors.toList()); HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite(); composite.addHandler(new ModelAndViewMethodReturnValueHandler()); composite.addHandler(new ViewNameMethodReturnValueHandler()); composite.addHandler(new ServletModelAttributeMethodProcessor(false)); composite.addHandler(new HttpEntityMethodProcessor(Collections.singletonList((new MappingJackson2HttpMessageConverter())))); composite.addHandler(new HttpHeadersReturnValueHandler()); composite.addHandler(new RequestResponseBodyMethodProcessor(Collections.singletonList((new MappingJackson2HttpMessageConverter())), collect)); composite.addHandler(new ServletModelAttributeMethodProcessor(true)); return composite; } }
运行结果如下:
开始返回值包装
{"code":200,"data":{"name":"王五","age":18}}
可以看到成功包装了一层,结果成为Result。
下面优化一下,让他只有加了@ResponseBody
,才去包装,修改判断方法。
修改MyControllerAdvice如下:
@ControllerAdvice static class MyControllerAdvice implements ResponseBodyAdvice<Object> { // 满足条件才转换 @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { // ResponseBody注解加载方法上或类上,或者有RestController注解,都返回true // 使用 AnnotationUtil工具类 if (returnType.getMethodAnnotation(ResponseBody.class) != null || AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null) { // returnType.getContainingClass().isAnnotationPresent(ResponseBody.class)) { return true; } return false; } // 将 User 或其它类型统一为 Result 类型 @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { System.out.println("开始返回值包装"); if (body instanceof Result) { return body; } return Result.ok(body); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。