乐趣区

关于springboot:聊聊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<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

退出移动版