聊聊springboot项目如何优雅的修改或者填充请求参数

前言

之前我们的文章记一次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 argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
        List 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 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;
    }
}

聊聊springboot项目如何优雅的修改或者填充请求参数_第1张图片

方法二:自定义RequestBodyAdvice

1、自定义RequestBodyAdvice
@RestControllerAdvice
public class ProductRequestBodyAdvice implements RequestBodyAdvice {


    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) {
        return methodParameter.hasParameterAnnotation(RequestBody.class) &&
               Product.class.isAssignableFrom(methodParameter.getParameterType());
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) throws IOException {
        return inputMessage;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> 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> 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;
    }
}

聊聊springboot项目如何优雅的修改或者填充请求参数_第2张图片

方法三:自定义过滤器 + 自定义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;
    }
}

聊聊springboot项目如何优雅的修改或者填充请求参数_第3张图片

方法四:自定义拦截器 + 自定义过滤器 + 自定义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、测试

测试示例同方法三

聊聊springboot项目如何优雅的修改或者填充请求参数_第4张图片

方法五 通过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 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;
    }
}

聊聊springboot项目如何优雅的修改或者填充请求参数_第5张图片

总结

本文介绍了5种修改或者填充请求参数的方法,这边有几个小细节点需注意一下,通过自定义HandlerMethodArgumentResolver这种方式,如果方法同时存在spring默认自带的HandlerMethodArgumentResolver和自定义HandlerMethodArgumentResolver,如果直接通过重写WebMvcConfigurer添加argumentResolver这种方式,则自定义HandlerMethodArgumentResolver会失效。其次通过RequestBodyAdvice这种方式只适用于方法参数加了@RequestBody 或 HttpEntity 方法参数。最后上面这几种方式,除了用来修改或者填充参数,他还可以用来做请求参数的校验,感兴趣的朋友可以自己扩展一下

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-argument-resolver

你可能感兴趣的:(springboot)