前言
之前咱们的文章记一次 springboot 我的项目自定义 HandlerMethodArgumentResolver 不失效起因与解法开端留了一个思考题:在咱们我的项目中如何优雅批改或者填充申请参数,本期就来揭晓这个谜底
办法一:自定义 HandlerMethodArgumentResolver
执行步骤:
1、自定义 HandlerMethodArgumentResolver 类
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private HandlerMethodArgumentResolver handlerMethodArgumentResolver;
public UserHandlerMethodArgumentResolver(HandlerMethodArgumentResolver handlerMethodArgumentResolver) {this.handlerMethodArgumentResolver = handlerMethodArgumentResolver;}
@Override
public boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(RequestBody.class) &&
User.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {User user = (User) handlerMethodArgumentResolver.resolveArgument(parameter,mavContainer,webRequest,binderFactory);
if(StringUtils.isBlank(user.getId())){HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String id = request.getHeader("id");
user.setId(id);
}
System.out.println(user);
return user;
}
}
2、将自定义的 HandlerMethodArgumentResolver 增加进行 argumentResolvers
@Configuration
public class HandlerMethodArgumentResolverAutoConfiguration implements InitializingBean {
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@Override
public void afterPropertiesSet() throws Exception {List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> customArgumentResolvers = new ArrayList<>();
for (HandlerMethodArgumentResolver argumentResolver : argumentResolvers) {if(argumentResolver instanceof RequestResponseBodyMethodProcessor){customArgumentResolvers.add(new UserHandlerMethodArgumentResolver(argumentResolver));
}
customArgumentResolvers.add(argumentResolver);
}
requestMappingHandlerAdapter.setArgumentResolvers(customArgumentResolvers);
}
}
至于为啥这么搞,而不是通过
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add();
}
}
答案就在记一次 springboot 我的项目自定义 HandlerMethodArgumentResolver 不失效起因与解法这篇文章中
3、测试
public class MetaInfo {
private String id;
public String getId() {return id;}
public void setId(String id) {this.id = id;}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User extends MetaInfo{private String username;}
@RestController
@RequestMapping("user")
public class UserController {@PostMapping("add")
public User add(@RequestBody User user){return user;}
}
办法二:自定义 RequestBodyAdvice
1、自定义 RequestBodyAdvice
@RestControllerAdvice
public class ProductRequestBodyAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return methodParameter.hasParameterAnnotation(RequestBody.class) &&
Product.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {return inputMessage;}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {Product product = (Product) body;
if(StringUtils.isBlank(product.getId())){String id = inputMessage.getHeaders().getFirst("id");
product.setId(id);
}
System.out.println(product);
return product;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return body;}
}
2、测试
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Product extends MetaInfo{private String productName;}
@RestController
@RequestMapping("product")
public class ProductController {@PostMapping("add")
public Product add(@RequestBody Product product){return product;}
}
办法三:自定义过滤器 + 自定义 HttpServletRequestWrapper
1、自定义 HttpServletRequestWrapper
public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
private String body;
@SneakyThrows
public CustomHttpServletRequestWrapper(HttpServletRequest request) {super(request);
// 获取申请 body
byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
body = new String(bodyBytes, request.getCharacterEncoding());
}
@Override
public ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {return false;}
@Override
public boolean isReady() {return false;}
@Override
public void setReadListener(ReadListener readListener) { }
@Override
public int read() throws IOException {return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {return this.body;}
public void setBody(String body) {this.body = body;}
}
2、自定义过滤器
public class OrderFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException { }
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(servletRequest instanceof HttpServletRequest) {HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
requestWrapper = new CustomHttpServletRequestWrapper(httpServletRequest);
// 当 header 的 type 为 filter,由 filter 负责填充,否则由拦截器负责
if(Constant.HEADER_VALUE_TYPE_FILTER.equalsIgnoreCase(httpServletRequest.getHeader(Constant.HEADER_KEY_TYPE))){System.out.println(">>>>>>>>>>> fillBodyWithId by OrderFilter");
RequestBodyUtil.fillBodyWithId((CustomHttpServletRequestWrapper) requestWrapper);
}
}
if(requestWrapper == null) {
// 避免流读取一次就没有了, 将流传递上来
filterChain.doFilter(servletRequest, servletResponse);
} else {filterChain.doFilter(requestWrapper, servletResponse);
}
}
@Override
public void destroy() {}
}
批改申请体外围代码
public final class RequestBodyUtil {private RequestBodyUtil(){}
public static void fillBodyWithId(CustomHttpServletRequestWrapper customHttpServletRequestWrapper){String body = customHttpServletRequestWrapper.getBody();
if(JSONUtil.isJson(body)){Order order = JSON.parseObject(body, Order.class);
if(ObjectUtil.isNotEmpty(order) && StringUtils.isBlank(order.getId())){String id = ((HttpServletRequest)customHttpServletRequestWrapper.getRequest()).getHeader(Constant.HEADER_KEY_ID);
order.setId(id);
String newBody = JSON.toJSONString(order);
customHttpServletRequestWrapper.setBody(newBody);
System.out.println(">>>>>>>>>>>>> newBody---->" + newBody);
}
}
}
}
3、注册 filter
@Bean
public FilterRegistrationBean servletRegistrationBean() {OrderFilter orderFilter = new OrderFilter();
FilterRegistrationBean bean = new FilterRegistrationBean<>();
bean.setFilter(orderFilter);
bean.setName("orderFilter");
bean.addUrlPatterns("/order/*");
bean.setOrder(Ordered.LOWEST_PRECEDENCE);
return bean;
}
4、测试
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Order extends MetaInfo{private String orderName;}
@RestController
@RequestMapping("order")
public class OrderController {@PostMapping("add")
public Order add(@RequestBody Order order){return order;}
}
办法四:自定义拦截器 + 自定义过滤器 + 自定义 HttpServletRequestWrapper
1、自定义 HttpServletRequestWrapper
代码同办法三,他的作用在办法四次要起到批改 body 参数的作用
2、自定义过滤器
代码同办法三,他的作用次要解决 Required request body is missing: 问题
3、自定义拦截器
public class OrderHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if(handler instanceof HandlerMethod){HandlerMethod handlerMethod = (HandlerMethod) handler;
for (MethodParameter methodParameter : handlerMethod.getMethodParameters()) {if(Order.class.isAssignableFrom(methodParameter.getParameterType())){if(request instanceof CustomHttpServletRequestWrapper){CustomHttpServletRequestWrapper customHttpServletRequestWrapper = (CustomHttpServletRequestWrapper) request;
RequestBodyUtil.fillBodyWithId(customHttpServletRequestWrapper);
}
}
}
}
return true;
}
}
4、配置拦截器
public class OrderHandlerInterceptorAutoConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(orderHandlerInterceptor()).addPathPatterns("/order/**");
}
@Bean
@ConditionalOnMissingBean
public OrderHandlerInterceptor orderHandlerInterceptor(){return new OrderHandlerInterceptor();
}
}
5、测试
测试示例同办法三
办法五 通过 AOP 实现
1、编写 AOP 切面
@Aspect
@Component
public class MemberAspect {
/**
*
* @param pjp
* @return
*
* @within 和 @target: 带有相应标注的所有类的任意办法,比方 @Transactional
* @annotation: 带有相应标注的任意办法,比方 @Transactional
* @within 和 @target 针对类的注解,@annotation 针对办法的注解
*
* @args: 参数带有相应标注的任意办法,比方 @Transactiona
*/
@SneakyThrows
@Around(value = "@within(org.springframework.web.bind.annotation.RestController)")
public Object around(ProceedingJoinPoint pjp){MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
HandlerMethod handlerMethod = new HandlerMethod(pjp.getTarget(),methodSignature.getMethod());
MethodParameter[] methodParameters = handlerMethod.getMethodParameters();
MethodParameterUtil.fillParamValueWithId(methodParameters,pjp.getArgs(), Member.class);
Object result = pjp.proceed();
return result;
}
}
批改参数的外围代码
public final class MethodParameterUtil {private MethodParameterUtil(){}
public static void fillParamValueWithId(MethodParameter[] methodParameters,Object[] args,Class<? extends MetaInfo> clz){if(ArrayUtil.isNotEmpty(methodParameters)){for (MethodParameter methodParameter : methodParameters) {if (methodParameter.getParameterType().isAssignableFrom(clz)
&& methodParameter.hasParameterAnnotation(InjectId.class)) {Object obj = args[methodParameter.getParameterIndex()];
if(obj instanceof MetaInfo){MetaInfo metaInfo = (MetaInfo) obj;
if(StringUtils.isBlank(metaInfo.getId())){ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String id = servletRequestAttributes.getRequest().getHeader(Constant.HEADER_KEY_ID);
metaInfo.setId(id);
System.out.println(">>>>>>>>>>>>> newObj---->" + JSON.toJSONString(obj));
}
}
}
}
}
}
}
2、测试
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Member extends MetaInfo{private String memberName;}
@RestController
@RequestMapping("member")
public class MemberController {@PostMapping("add")
public Member add(@RequestBody @InjectId Member member){return member;}
}
总结
本文介绍了 5 种批改或者填充申请参数的办法,这边有几个小细节点需注意一下,通过自定义 HandlerMethodArgumentResolver 这种形式,如果办法同时存在 spring 默认自带的 HandlerMethodArgumentResolver 和自定义 HandlerMethodArgumentResolver,如果间接通过重写 WebMvcConfigurer 增加 argumentResolver 这种形式,则自定义 HandlerMethodArgumentResolver 会生效。其次通过 RequestBodyAdvice 这种形式只实用于办法参数加了 @RequestBody 或 HttpEntity 办法参数。最初下面这几种形式,除了用来批改或者填充参数,他还能够用来做申请参数的校验,感兴趣的敌人能够本人扩大一下
demo 链接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-argument-resolver