前言
为了统一使用下滑线风格进行设计接口的请求参数以及输出参数,
最近想实现一个把 下划线格式的get请求参数转换成对应的驼峰命名风格实体类。
当然会有朋友说使用Jackson配置里的命名策略就可以了。
对,是没错,但jackson 只支持@ResponseBody 以及@RequestBody,
而对于没带任何注解的实体类参数是没法处理的,所以就想怎么在解释实体参数时可以自定义解释逻辑,
@RestController()
@RequestMapping("/v1/booking/room")
public class BookingRoomController {
//这里的request对象不支持像jackson那样的命名策略由下划线格式转换成驼峰实体类
//附:这里的request参数没加任何注解,spring会当做成@ModelAttribute处理
public PagingResult<BookingRoomDTO> list(BookingRoomSearchRequest request) {
return roomSearchService.searchBookingRooms(request);
}
//这里有@RequestBody会被 Jackson根据命名策略进行处理
public PagingResult<BookingRoomDTO> list2(@RequestBody BookingRoomSearchRequest request) {
return roomSearchService.searchBookingRooms(request);
}
}
关键的类或接口
- HandlerMethodArgumentResolver
- ModelAttributeMethodProcessor(spring boot 中默认加载的子类ServletModelAttributeMethodProcessor)
- WebDataBinder(实现类有ServletRequestDataBinder)
- 注解@ModelAttribute
官方有一个表格描述了controller方法参数里可以支持哪些类型:



请留意 【@ModelAttribute】以及【Any other argument】 这两行;
【Any other argument】 里说到如果判定是 Simple类型的话就当作 @RequestParam处理, 否则当作成 @ModelAttribute 处理。
Simple类型解释:
Check if the given type represents a "simple" property: a primitive, a String or other CharSequence, a Number, a Date, a URI, a URL, a Locale, a Class, or a corresponding array.
当当作@ModelAttribute处理时候,会使用ModelAttributeMethodProcessor处理。
ModelAttributeMethodProcessor实现了HandlerMethodArgumentResolver接口:
public interface HandlerMethodArgumentResolver {
/**
* Whether the given {@linkplain MethodParameter method parameter} is
* supported by this resolver.
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter;
* {@code false} otherwise
*/
boolean supportsParameter(MethodParameter parameter);
/**
* Resolves a method parameter into an argument value from a given request.
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to {@link #supportsParameter} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null} if not resolvable
* @throws Exception in case of errors with the preparation of argument values
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
其中方法resolveArgument 会将当前上下文参数进行转换成Controller方法上对应的参数
ModelAttributeMethodProcessor具体实现:
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
网友评论