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