티스토리 뷰
1. 요구 사항
컨트롤러
1 2 3 4 5 6 | @GetMapping(value={"/api/brands/{brandCd}/stores"}) public BaseResult stores(StoreParam pStoreParam) { // ... return null; } | cs |
요청 URI : "/brands/bn0001/stores?brandCd=1234"
2. 스프링에서 Pathvariable에 값 조회 확인
1 2 3 4 5 6 7 | @SuppressWarnings("unchecked") private Map<String, String> getCurrentRequestUriVariables(HttpServletRequest request) { String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; Map<String, String> uriVars = (Map<String, String>) request.getAttribute(name); return (uriVars != null) ? uriVars : Collections.<String, String> emptyMap(); } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | package org.springframework.web.servlet.mvc.method.annotation; import java.util.Map; import javax.servlet.ServletRequest; import org.springframework.beans.MutablePropertyValues; import org.springframework.lang.Nullable; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.servlet.HandlerMapping; /** * Subclass of {@link ServletRequestDataBinder} that adds URI template variables * to the values used for data binding. * * @author Rossen Stoyanchev * @since 3.1 */ public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder { /** * Create a new instance, with default object name. * @param target the target object to bind onto (or {@code null} * if the binder is just used to convert a plain parameter value) * @see #DEFAULT_OBJECT_NAME */ public ExtendedServletRequestDataBinder(@Nullable Object target) { super(target); } /** * Create a new instance. * @param target the target object to bind onto (or {@code null} * if the binder is just used to convert a plain parameter value) * @param objectName the name of the target object * @see #DEFAULT_OBJECT_NAME */ public ExtendedServletRequestDataBinder(@Nullable Object target, String objectName) { super(target, objectName); } /** * Merge URI variables into the property values to use for data binding. */ @Override @SuppressWarnings("unchecked") protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) { String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr); if (uriVars != null) { uriVars.forEach((name, value) -> { if (mpvs.contains(name)) { if (logger.isWarnEnabled()) { logger.warn("Skipping URI variable '" + name + "' because request contains bind value with same name."); } } else { mpvs.addPropertyValue(name, value); } }); } } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Override @SuppressWarnings("unchecked") protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) { String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr); if (uriVars != null) { uriVars.forEach((name, value) -> { if(value != null && !value.isEmpty()) { mpvs.addPropertyValue(name, value); } }); } } | cs |
3. 관련 소스 확인
WebMvcConfigurationSupport.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); adapter.setContentNegotiationManager(mvcContentNegotiationManager()); adapter.setMessageConverters(getMessageConverters()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); adapter.setCustomArgumentResolvers(getArgumentResolvers()); adapter.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice())); adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice())); } AsyncSupportConfigurer configurer = new AsyncSupportConfigurer(); configureAsyncSupport(configurer); if (configurer.getTaskExecutor() != null) { adapter.setTaskExecutor(configurer.getTaskExecutor()); } if (configurer.getTimeout() != null) { adapter.setAsyncRequestTimeout(configurer.getTimeout()); } adapter.setCallableInterceptors(configurer.getCallableInterceptors()); adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors()); return adapter; } /** * Protected method for plugging in a custom subclass of * {@link RequestMappingHandlerAdapter}. * @since 4.3 */ protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() { return new RequestMappingHandlerAdapter(); } | cs |
RequestMappingHandlerAdapter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | @Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]"; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } } private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { Class<?> handlerType = handlerMethod.getBeanType(); Set<Method> methods = this.initBinderCache.get(handlerType); if (methods == null) { methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS); this.initBinderCache.put(handlerType, methods); } List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>(); // Global methods first this.initBinderAdviceCache.forEach((clazz, methodSet) -> { if (clazz.isApplicableToBeanType(handlerType)) { Object bean = clazz.resolveBean(); for (Method method : methodSet) { initBinderMethods.add(createInitBinderMethod(bean, method)); } } }); for (Method method : methods) { Object bean = handlerMethod.getBean(); initBinderMethods.add(createInitBinderMethod(bean, method)); } return createDataBinderFactory(initBinderMethods); } protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods) throws Exception { return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer()); } | cs |
createDataBinderFactory에서 ServletRequestDataBinderFactory를 생성하는 부분을 확인할 수 있습니다.
ServletRequestDataBinderFactory.java 소스 확인
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory { /** * Create a new instance. * @param binderMethods one or more {@code @InitBinder} methods * @param initializer provides global data binder initialization */ public ServletRequestDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, @Nullable WebBindingInitializer initializer) { super(binderMethods, initializer); } /** * Returns an instance of {@link ExtendedServletRequestDataBinder}. */ @Override protected ServletRequestDataBinder createBinderInstance( @Nullable Object target, String objectName, NativeWebRequest request) throws Exception { return new ExtendedServletRequestDataBinder(target, objectName); } } | cs |
4. Tasks
1. CustomExtendedServletRequestDataBinder.java 생성
2. CustomServletRequestDataBinderFactory.java 생성
3. CustomRequestMappingHandlerAdapter.java 생성
4. WebMvcConfigurationSupport Override 하기
1. CustomExtendedServletRequestDataBinder.java 생성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class CustomExtendedServletRequestDataBinder extends ExtendedServletRequestDataBinder{ public CustomExtendedServletRequestDataBinder(@Nullable Object target) { super(target); } public CustomExtendedServletRequestDataBinder(@Nullable Object target, String objectName) { super(target, objectName); } @Override @SuppressWarnings("unchecked") protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) { String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr); if (uriVars != null) { uriVars.forEach((name, value) -> { if(value != null && !value.isEmpty()) { mpvs.addPropertyValue(name, value); } }); } } } | cs |
필요한 로직을 입맛대로 구현하면 됩니다.
2. CustomServletRequestDataBinderFactory.java 생성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class CustomServletRequestDataBinderFactory extends ServletRequestDataBinderFactory{ public CustomServletRequestDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, @Nullable WebBindingInitializer initializer) { super(binderMethods, initializer); } /** * Returns an instance of {@link ExtendedServletRequestDataBinder}. */ @Override protected ServletRequestDataBinder createBinderInstance( @Nullable Object target, String objectName, NativeWebRequest request) throws Exception { return new CustomExtendedServletRequestDataBinder(target, objectName); } } | cs |
3. CustomRequestMappingHandlerAdapter.java 생성
1 2 3 4 5 6 7 8 9 10 11 12 | public class CustomRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter{ public CustomRequestMappingHandlerAdapter() { super(); } protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods) throws Exception { return new CustomServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer()); } } | cs |
4. WebMvcConfigurationSupport Override 하기
1 2 3 4 5 6 7 8 9 10 | @Configuration public class WebMvcConfiguration extends WebMvcConfigurationSupport { @Override protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() { return new CustomRequestMappingHandlerAdapter(); } ... ... } | cs |
springboot 1.5.9에서 2.1.1로 업데이트할 때 기존 WebMvcConfigurerAdapter.java를 상속하여 구현 하던 것을 WebMvcConfigurer.java를 implements하거나 WebMvcConfigurationSupport.java를 상속받아 구현해야 합니다.
ExtendedServletRequestDataBinder를 커스트마이징하는 것인데 생각보다 작업해야 할게 좀 많았던 거 같습니다.
@InitBinder를 이용하는 방법을 찾고 싶었으나 생각 보다 쉽지 않았던거 같습니다.
이외 구글 검색하다가 애노테이션을 이용해서 데이터 바인딩시 이름을 바꾸는 내용이 있었는데 나름 재밌었습니다.
다만 제가 구현할려는 거와 안 맞았지만 나중에 필요 시 참고하면 좋을 거 같습니다.
참고
'Spring Frameworks' 카테고리의 다른 글
Spring boot - 빌드 profile 설정 (0) | 2019.02.20 |
---|---|
Spring Boot - 복수 데이터소스 설정하기 (0) | 2019.02.14 |
Spring Boot에서 에러 페이지 처리하기 (3) | 2019.02.14 |
springboot 2.x @PutMapping 값 전달 안되는 에러 처리 (0) | 2019.02.13 |
spring boot 기반 프로젝트 공통 개발 (0) | 2019.01.30 |
- Total
- Today
- Yesterday
- 샘플
- 타임리프
- mapToList
- example
- 그리드
- 설정
- 스프링
- spring
- springboot
- RESTful
- SHEETJS
- listToMap
- AG-GRID
- 메시지
- 예제
- mybatis
- REST
- Javascript
- Spring Boot
- lombok
- restful서비스
- sample
- 엑셀
- java
- cache
- UI
- ag grid
- 스프링부트
- oracle
- thymeleaf
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |