스프링 파라미터 UnderScore To CamelCase (@ModelAttribute)

·2022년 3월 24일
0

API input parameter는 underscore가 많지만 Java는 camelcase가 convention이다.

@RequestBody의 경우 Jackson의 프로퍼티 네이밍 전략 설정을 통해 자동으로 처리되나 @ModelAttribute를 사용하는 경우는 다르다.

  • 추천 : setter 메서드를 underscore에 맞춰서 작성 -> 심플하고 확실하다.

  • RequestMappingHandlerAdapter 확장 : @ModelAttribute 바인딩 커스터마이징

@Configuration
protected static class WebMvcRegistrationsProvider {

	@Bean
	public WebMvcRegistrations requestMappingHandlerAdapterProvider() {

		return new WebMvcRegistrations() {
			@Override
			public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
				return new BaseRequestMappingHandlerAdapter();
			}
		};
	}
}

@SuppressWarnings("squid:MaximumInheritanceDepth")
static class BaseRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {

	@Override
	protected InitBinderDataBinderFactory createDataBinderFactory(final List<InvocableHandlerMethod> bm) {

		return new ServletRequestDataBinderFactory(bm, super.getWebBindingInitializer()) {
			@Override
			protected ServletRequestDataBinder createBinderInstance(@Nullable final Object o, final String n, final NativeWebRequest wr) {
				return new CamelCaseRequestDataBinder(o, n);
			}
		};
	}
}

@Slf4j
static class CamelCaseRequestDataBinder extends ExtendedServletRequestDataBinder {

	private static final CaseConverter CONVERTER = new CaseConverter();

	CamelCaseRequestDataBinder(@Nullable final Object target, final String objectName) {
		super(target, objectName);
	}

	@Override
	protected void bindMultipart(final Map<String, List<MultipartFile>> mp, final MutablePropertyValues mpvs) {

		mp.forEach((k, v) -> {
			final String converted = CONVERTER.convert(k);

			if (v.size() == 1) {
				final MultipartFile value = v.get(0);
				if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
					mpvs.add(converted, value);
				}
			} else {
				mpvs.add(converted, v);
			}
		});
	}

	@Override
	protected void addBindValues(final MutablePropertyValues mpvs, final ServletRequest request) {

		@SuppressWarnings("unchecked")
		final Map<String, String> pathVariables = (Map<String, String>)request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);

		if (Objects.nonNull(pathVariables)) {
			pathVariables.forEach((k, v) -> {
				if (mpvs.contains(k)) {
					log.warn("PathVariable '{}' Found In RequestParameters", k);
					log.warn("Spring's Default Is 'Skip Overwriting' But We Overwrite !!");
				}
				mpvs.addPropertyValue(k, v);
			});
		}
	}
}
  • 추가 : HttpServletRequestWrapper를 이용하여 getParameter 등 동작 재정의
static class ParameterNameWrapper extends HttpServletRequestWrapper {

	private final Map<String, String[]> converted;

	ParameterNameWrapper(final HttpServletRequest request, final CaseConverter converter) {
		super(request);
		converted = convertParameters(request.getParameterMap(), converter);
	}

	@Nullable @Override
	public String getParameter(final String name) {
		return Optional.ofNullable(converted.get(name)).map(v -> v[0]).orElse(null);
	}

	@Override
	public Map<String, String[]> getParameterMap() {
		return converted;
	}

	@Override
	public Enumeration<String> getParameterNames() {
		return Collections.enumeration(converted.keySet());
	}

	@Nullable @Override
	public String[] getParameterValues(final String name) {
		return converted.get(name);
	}

	private Map<String, String[]> convertParameters(final Map<String, String[]> source, final CaseConverter converter) {
		return source.entrySet().stream().collect(Collectors.toMap(e -> converter.convert(e.getKey()), Map.Entry::getValue));
	}
}

static class ParameterNameFilter implements Filter {

	private static final CaseConverter CONVERTER = new CaseConverter();

	@Override
	public void init(final FilterConfig filterConfig) { /* No Operation Here */ }

	@Override
	public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
		chain.doFilter(request instanceof HttpServletRequest ? new ParameterNameWrapper((HttpServletRequest)request, CONVERTER) : request, response);
	}

	@Override public void destroy() { /* No Operation Here */ }
}

@Configuration
protected static class FilterRegistererConfig {

	@Bean
	public FilterRegistrationBean<ParameterNameFilter> parameterNameFilterRegisterer() {

		final FilterRegistrationBean<ParameterNameFilter> bean  =  new FilterRegistrationBean<>();
		bean.setFilter(new ParameterNameFilter()); bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 11);

		return bean;
	}
}
profile
Java 일용 노동자.

0개의 댓글