SpringBoot - 다양한 Content - Type 을 동시에 매개 변수 바 인 딩 처리 방법

11449 단어
배경
SpringBoot 버 전 2.1.1 - RELEASE.작업 중 에 이러한 특수 한 수 요 를 만 났 습 니 다. 프론트 데스크 에서 들 어 오 는 인 자 를 받 고 인 자 를 받 으 며 대상 을 밀봉 한 후에 후속 적 인 처 리 를 해 야 합 니 다.기 존 논리 에 따 르 면 프론트 데스크 톱 에서 http 인 터 페 이 스 를 요청 하 는 Content - type 은 두 가지 가 있 습 니 다. application / json 과 application / x - ww - form - urlencoded 입 니 다.현재 두 가지 요청 방식 이 모두 매개 변수 바 인 딩 을 할 수 있 도록 요구 합 니 다.Handler MethodArgument Resolver 를 사용자 정의 해서 실현 하고 싶 습 니 다.
2. 매개 변수 바 인 딩 의 원리
테스트 코드 1:
@RestController
@RequestMapping("/test")
@Slf4j
public class TestWebController {

    @RequestMapping("/form")
    public Person testFormData(Person person, HttpServletRequest request) {
        String contentType = request.getHeader("content-type");
        log.info("Content-Type:" + contentType);
        log.info("   :{}", JSON.toJSONString(person));
        return person;
    }
}

요청 인자:
curl --request POST \
  --url http://localhost:8080/test/form \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data 'name=test&age=19'

콘 솔 출력 결과:
TestWebController     : Content-Type:application/x-www-form-urlencoded
TestWebController     :    :{"age":19,"name":"test"}

폼 이 제출 한 매개 변 수 는 필드 이름 에 따라 Person 대상 에 자동 으로 연결 되 어 있 음 을 알 수 있 습 니 다.
원본 코드 를 보면 http 요청 이 Dispatcher Servlet 에 들 어 가 는 doDispatch 방법 을 알 수 있 습 니 다. 방법 을 통 해
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

현재 요청 한 RequestMappingHandlerAdapter 대상 ha 를 가 져 온 후 방법 을 실 행 했 습 니 다.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

이 방법 에서 AbstractHandler MethodAdapter 추상 류 의 기본 방법 handle 을 실 행 했 고 기본 방법 은 ha 의 handle Internal 방법 을 호출 했다.그 다음 에 방법 형 참 을 통 해 들 어 오 는 HandlerMethod 대상 (HandlerMethod 대상 은 사실 우리 Controller 에서 직접 쓴 testformData 의 Method 대상) 을 통 해 실행 가능 한 방법 인 Invocable HandlerMethod 를 얻 을 수 있 습 니 다.이 어 실행 가능 한 방법 대상 의 getMethodArgument Values 방법 을 실 행 했 습 니 다.
MethodParameter[] parameters = getMethodParameters();

방법 에서 현재 방법의 모든 형 삼 을 얻 었 다.그 다음 에 이 형 삼 을 반복 해서 옮 겨 다 니 며 HandlerMethodArgument Resolver 인터페이스의 실현 류 를 통 해 이 형 삼 을 처리 합 니 다.여기 서 우 리 는 현재 의 resolvers 가 하나의 조합 대상 이라는 것 을 발견 했다.이 조합 대상 도 이 인 터 페 이 스 를 실 현 했 고 이 대상 은 개인 적 인 구성원 변수 가 있 습 니 다. 인터페이스의 실현 류 의 집합 입 니 다.파 라 메 터 를 처리 할 때 현재 resolver 의 집합 을 옮 겨 다 니 며 인터페이스 방법 슈퍼 ports Parameter 를 통 해 현재 인삼 의 MethodParameter 대상 을 검증 합 니 다.true 로 돌 아 왔 을 때 현재 resolver 가 현재 의 형 삼 을 지원 한 다 는 것 을 증명 하고 현재 의 resolver 를 선택 하여 현재 의 형 삼 을 처리 합 니 다.해당 resolver 에 처음 일치 한 후에 메모리 등급 의 캐 시 를 진행 합 니 다.다음 에 같은 유형의 인삼 을 resolver 로 선택 할 때 집합 을 옮 겨 다 니 지 않 습 니 다.
    /**
	 * Find a registered {@link HandlerMethodArgumentResolver} that supports
	 * the given method parameter.
	 */
	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
				if (methodArgumentResolver.supportsParameter(parameter)) {
					result = methodArgumentResolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

해당 resolver 를 선택 한 후 방법 을 통 해 들 어 오 는 request 대상 은 resolver 의 resolve Argument 방법 을 실행 하여 형 삼 의 값 을 봉 합 니 다.
조합 대상 을 관찰 한 결과 26 개의 내 장 된 대상 이 각각 장면 에 따라 형 삼 의 처 리 를 담당 하 는 것 으로 나 타 났 다.여기 에는 왜 controller 의 형 삼 위치 에 HttpServletRequest, HttpServletResponse 등 대상 을 자동 으로 주입 하 는 지 설명 한다.
실행 과정 을 관찰 한 결과, Content - type 이 application / x - ww - form - urlencoded 일 때, 형 삼 을 처리 하 는 resolver 는 ServletModelAttributeMethodProcessor 임 을 발견 하 였 다.
테스트 코드 2:
@RequestMapping("/entity")
    public Person testFromEntity(@RequestBody Person person, HttpServletRequest request) {
        String contentType = request.getHeader("content-type");
        log.info("Content-Type:" + contentType);
        log.info("   :{}", JSON.toJSONString(person));
        return person;
    }

요청 인자:
curl --request GET \
  --url http://localhost:8080/test/entity \
  --header 'Content-Type: application/json' \

형 삼 이 @ RequestBody 주석 에 표 시 될 때 요청 체 에 들 어 오지 않 으 면 오 류 를 보고 하 는 것 을 발견 할 수 있 습 니 다.위 와 같은 절 차 를 통 해 형 삼 이 @ RequestBody 주석 으로 표 시 될 때 SpringBoot 에서 선택 한 resolver 는 RequestResponse Body MethodProcessor 임 을 알 수 있 습 니 다.
요청 체 를 통 해 제 이 슨 에 게 전 달 될 때:
curl --request GET \
  --url http://localhost:8080/test/entity \
  --header 'Content-Type: application/json' \
  --data '{
"name": "json",
"age": 20
}'

 관찰 할 수 있다
TestWebController     : Content-Type:application/json
TestWebController     :    :{"age":20,"name":"json"}

콘 솔 에서 성공 적 으로 연 결 된 인 자 를 출력 하 였 습 니 다. 
또한, argument Resolvers 집합 을 관찰 한 결과 RequestResponse Body MethodProcessor 의 순 서 는 ServletModelAttributeMethodProcessor 보다 훨씬 높 고, ServletModelAttributeMethodProcessor 는 마지막 resolver 인 것 으로 나 타 났 다.
따라서 @ RequestBody 주석 이 표 시 된 형 삼 은 ServletModelAttribute MethodProcessor 를 통 해 데이터 바 인 딩 을 실현 할 기회 가 없 을 것 입 니 다. url 이후 주소 연결 매개 변 수 를 통 해 서버 에 요청 하 더 라 도.비어 있 거나 해석 할 수 없 는 제 이 슨 에 들 어 갈 때 400 의 오류 에 직접 응답 합 니 다.
3. 사용자 정의 PostEntityHandlerMethodArgument Resolver
상기 테스트 를 통 해 형 삼 파라미터 연결 을 처리 하 는 resolver 는 모두 HandlerMethodArgument Resolver 인터페이스의 실현 클래스 임 을 알 수 있 습 니 다.그래서 저 는 이러한 실현 류 를 사용자 정의 하여 우리 가 처리 해 야 할 형 삼 에 대해 매개 변수 바 인 딩 처 리 를 할 생각 입 니 다.
사용자 정의 resolver 를 새로 만 들 고 인 터 페 이 스 를 실현 한 후에 두 가지 방법 이 필요 합 니 다. 슈퍼 ports Parameter 와 resolveArgument 입 니 다.
슈퍼 ports Parameter 방법 은 resolver 조합 대상 이 형 삼 을 통 해 resolver 를 선택 할 때 판단 하 는 방법 입 니 다.이 방법 이 true 로 되 돌아 오 면 이 해석 기 를 대표 하여 이 유형의 형 참 을 처리 할 수 있 습 니 다.그렇지 않 으 면 false 로 돌아 가 다음 선택 을 반복 합 니 다.따라서 여기에서 성공 적 으로 포착 할 수 있 도록 사용자 정의 형 삼 을 표시 해 야 합 니 다.
내 방법 은 빈 인 터 페 이 스 를 사용자 정의 하 는 것 이다.
public interface PostEntity {
}

우리 의 실체 류 로 하여 금 이 인 터 페 이 스 를 실현 하 게 하지만, 아무것도 할 필요 가 없다. 슈퍼 ports Parameter 방법 에서 들 어 오 는 유형 이 PostEntity 의 실현 클래스 인지 판단 합 니 다.실현 클래스 라면 true 로 돌아 갑 니 다. 그렇지 않 으 면 false 로 돌아 가 다른 유형의 인삼 값 의 주입 에 영향 을 주지 않 습 니 다.
resolveArgument 방법 에 대해 서 는 Content - Type 에 따라 위 에서 언급 한 두 개의 resolver 를 직접 호출 하면 됩 니 다. 스스로 이 논 리 를 실현 할 필요 가 없습니다.또한 매개 변수 처리 전역 의 일치 성 을 확보 할 수 있다.Content - Type 에 의존 하 는 값 을 판단 하기 때문에 호출 자가 Content - Type 에 들 어가 야 합 니 다.
@Slf4j
@AllArgsConstructor
public class PostEntityHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

    private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor;

    private ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor;

    private static final String APPLICATION_JSON = "application/json";

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class> parameterType = parameter.getParameterType();
        String parameterName = parameter.getParameterName();
        if (PostEntity.class.isAssignableFrom(parameterType)) {
            log.info("name:{},type:{}", parameterName, parameterType.getName());
            log.info("matched");
            return true;
        }
        return false;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        assert request != null;
        String contentType = request.getContentType();
        log.debug("Content-Type:{}", contentType);
        if (APPLICATION_JSON.equalsIgnoreCase(contentType)) {
            return requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        } else {
            return servletModelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }
    }
}

4. 사용자 정의 HandlerMethodArgument Resolver 등록
구조 가 완 료 된 후에 사용자 정의 resolver 를 resolver 의 조합 대상 에 추가 해 야 합 니 다.모든 사전 로드 resolvers 는 시작 과정 에서 Request Mapping HandlerAdapter 대상 에 설정 되 어 있 습 니 다.
WebMvcConfigurer 인 터 페 이 스 를 구현 하 는 설정 클래스 를 정의 하고 구성원 변수 위치 에 RequestMappingHandlerAdapter 대상 을 주입 합 니 다.주입 성공 을 확보 한 후 @ PostConstruct 의 init 방법 을 정의 합 니 다. 먼저 getArgument Resolvers 방법 으로 모든 resolvers 를 얻 은 다음 이 집합 을 옮 겨 다 니 며 우리 가 필요 로 하 는 두 개의 resolvers 를 얻 습 니 다.필요 한 인 자 를 가 져 온 후 사용자 정의 PostEntity Handler MethodArgument Resolver 를 구성 합 니 다.
resolver 집합 을 가 져 오 는 방법 소스 코드 를 보면 다음 과 같 습 니 다.
return Collections.unmodifiableList(this.argumentResolvers);

이 방법 이 되 돌아 오 는 집합 은 변 할 수 없 는 집합 으로 새로운 요 소 를 추가 할 방법 이 없다.따라서 우 리 는 새로운 집합 을 만들어 야 합 니 다. 크기 는 원래 의 집합 크기 + 1 이 고 사용자 정의 resolver 를 집합 첫 번 째 에 추가 한 다음 에 ha 대상 을 통 해 다시 설정 해 야 합 니 다.이렇게 하면 사용자 정의 resolver 등록 이 완 료 됩 니 다.
@Configuration
@Slf4j
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Autowired
    private RequestMappingHandlerAdapter ha;

    private ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor = null;
    private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null;

    @PostConstruct
    private void init() {
        List argumentResolvers = ha.getArgumentResolvers();
        for (HandlerMethodArgumentResolver argumentResolver : argumentResolvers) {
            if (argumentResolver instanceof ServletModelAttributeMethodProcessor) {
                servletModelAttributeMethodProcessor = (ServletModelAttributeMethodProcessor) argumentResolver;
            } else if (argumentResolver instanceof RequestResponseBodyMethodProcessor) {
                requestResponseBodyMethodProcessor = (RequestResponseBodyMethodProcessor) argumentResolver;
            }
            if (servletModelAttributeMethodProcessor != null && requestResponseBodyMethodProcessor != null) {
                break;
            }
        }
        PostEntityHandlerMethodArgumentResolver postEntityHandlerMethodArgumentResolver = new PostEntityHandlerMethodArgumentResolver(requestResponseBodyMethodProcessor, servletModelAttributeMethodProcessor);
        List newList = new ArrayList<>(argumentResolvers.size() + 1);
        newList.add(postEntityHandlerMethodArgumentResolver);
        newList.addAll(argumentResolvers);
        ha.setArgumentResolvers(newList);
    }

}

 
결론
테스트 를 통 해 두 가지 요청 방식 이 모두 같은 방법 으로 형 삼 된 매개 변수 바 인 딩 을 실현 한 것 을 알 수 있다.이 기능 도 이렇게 복잡 한 실현 방식 없 이도 할 수 있 지만 이 문제 에 대한 연 구 를 통 해 spring 의 소스 코드 를 읽 고 매개 변수 바 인 딩 데이터 의 유통 과정 을 더욱 잘 알 게 되 었 다.
바 인 딩 이 필요 한 형 삼 은 외부 의존 vo 로 사용자 정의 인 터 페 이 스 를 실현 할 수 없고 사용자 정의 주 해 를 실현 할 수 있 으 며 사용자 정의 resolver 에서 도 포착 할 수 있 으 며 사용자 정의 처 리 를 할 수 있 습 니 다.
또 하나의 유용 한 장면 이 있 습 니 다. 이 방식 을 통 해 콘 텐 츠 - type 을 사용자 정의 하여 왜 그 랬 는 지 모 르 는 수 요 를 실현 할 수 있 습 니 다 ~
데모 코드:https://github.com/daegis/multi-content-type-demo

좋은 웹페이지 즐겨찾기