SpringMvc 统一入参管理

在写 controller 方法时要接收入参,get 请求对应注解 @RequestParam,post 请求对应 @RequestBody,而且只能用一次@RequestBody,碰到 post 请求有分页参数的情况就不实用了。还有@RequestHeader@PathVariable@ModelAttribute众多参数注解,每一个对应一个默认的实现类。

其中 HandlerMethodArgumentResolver 接口就是实现这些默认的功能,我们也可以自己实现,这样不用每个控制器都加那么多注解。

需求:

统一给入参进行封装多一层,如传参 "id": 123,统一封装为 {"params":{"id": 123}},在代码调用时直接使用 id进行接收。参数无需每个都 request.getParameter 获取,本示例显示如何 优雅地 将传入的信息转化成自定义的实体传入controller方法。

1.拦截器实现拦截

统一将 body 数据进行处理,把处理的数据放到内存中。

/**
 * @Description: 拦截器给对象注入数据,保存到request.setAttribute中
 * @author: LinQin
 * @date: 2019/12/21
 */
public class ParamHandle implements HandlerInterceptor {
    private static final String JSON_REQUEST_BODY = "JSON_REQUEST_BODY";

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求数据
        ServletServerHttpRequest servletServerHttpRequest = new ServletServerHttpRequest(request);
        RequestDataWrapper requestDataWrapper = new RequestDataWrapper(true);
        requestDataWrapper.parseJsonNode(read(servletServerHttpRequest, (HandlerMethod) handler));
        request.setAttribute(JSON_REQUEST_BODY, requestDataWrapper);
        return true;
    }

    /**
     * 求体输入流中读取数据
     * 解析成IsonNode对象
     *
     * @param inputMessage
     * @param handlerMethod
     * @return
     * @throws IOException
     */
    public JsonNode read(@NotNull HttpInputMessage inputMessage, @NotNull HandlerMethod handlerMethod) throws IOException {
        EmptyBodyCheckingHttpInputMessage message;
        try {
            message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
            if (message.hasBody()) {
                return objectMapper.readTree(message.getBody());
            }
            return null;
        } catch (JsonParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}

2.入参统一处理

通过实现接口 HandlerMethodArgumentResolver 进行入参解析。具体请求 body 再从上面的请求中获取。

 * @Description: 自定义入参解析器
 * @author: LinQin
 * @date: 2019/12/21
 */
@Component
public class CostomHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    private static final String JSON_REQUEST_BODY = "JSON_REQUEST_BODY";

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return true;
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        // 多个参数,会多次经过这里处理
        RequestDataWrapper requestDataWrapper = getRequestBody(nativeWebRequest);
        if (requestDataWrapper == null) {
            String path = null;
//            Assert.isInstanceOf(nativeWebRequest.getClass(), ServletWebRequest.class);
            if (nativeWebRequest instanceof ServletWebRequest) {
                path = ((ServletWebRequest) nativeWebRequest).getRequest().getServletPath();
            }
            throw new IllegalArgumentException(String.format("无法注入'%s'参数,当前不是JSON请求, path=%s", methodParameter.getParameterName(), path != null ? path : methodParameter.getMethod()));
        }
        methodParameter = methodParameter.nestedIfOptional();
        Object arg = readWithRequestData(requestDataWrapper, methodParameter, methodParameter.getNestedGenericParameterType());
        arg = handleNullValue(methodParameter.getParameterName(), arg, methodParameter.getParameterType());

        return adaptArgumentIfNecessary(arg, methodParameter);
    }
    ...
 }

3.注册到配置

/**
 * Description: 注册
 * author: LinQin
 * date: 2018/07/05
 */
@Configuration
public class SessionConfiguration implements WebMvcConfigurer {
    @Bean
    public CostomHandlerMethodArgumentResolver costomHandlerMethodArgumentResolver() {
        return new CostomHandlerMethodArgumentResolver();
    }

    /**
     * 注册入参管理
     * @param resolvers
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(costomHandlerMethodArgumentResolver());
    }

    @Bean
    public ParamHandle paramHandle() {
        return new ParamHandle();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration interceptor = registry.addInterceptor(paramHandle());
        /**
         * 添加拦截的路径
         * /为根路径
         * /*为一级路径
         * /** 为所有路径包括多级
         */
        interceptor.addPathPatterns("/**");
    }
}

4.控制器测试

接收时候可以直接使用对象进行接收参数,无需自己手动转换。但是本例子 json 传 Map 需要用 Object 来接收。

@RestController
public class LoginController {

    @Autowired
    private UserDao userDao;

    @RequestMapping("/login")
    public String login(User user, Object map) {
        System.out.println("--------");
        System.out.println(user.toString());
        // map 参数要用Object来接收,
        Map map1 = (Map) map;
        System.out.println(map1.entrySet());
        return "login";
    }
 }

项目地址:GitHub

上次更新时间: 2024/5/7 05:59:02