전에 작성한 글이 길어져서, 포스팅을 분리했다
동작 흐름
ArgumentResolver는 아래와 같은 순서로 호출된다
- Handler의 각 Parameter 리스트 순회한다
- Argument Resolver 리스트를 순회하면서 1번의 Parameter의 타입에 일치하는 것을 찾는다
즉 O(N^2)이다
InvocableHandlerMethod 를 보도록 하자 (HandlerMethod의 자손)
먼저 파리미터들 목록을 가져온다
그 후 arguemntResolver들의 목록을 순회한다
언제 ModelAttributeMethodProcessor를 거치는가
결론: 커스텀 클래스인 모든 경우. (단, 커스텀 클래스에 대응하는 ArgumentResolver가 없는 경우)
먼저, 들어가기전에 간단히 알고 넘어갈 게 있다
ServletInvocableHandlerMethod의 상속 관계도이다
가장 상위에 HandlerMethod가 있다
(나의 경우 이전에 DispatcherServlet.doDisptach(..) 관련 소스를 많이 봐서 그런지 굉장히 익숙한 클래스이다)
여기서 핵심은 InvocableHandlerMethod이다,
resolvers가 중요한 property인데 타입이 HandlerMethodArgumentResolverComposite 이다 (중요)
호출 Flow
자, 이제 호출 순서를 보면
(다들 잘 아시겠지만… 뻔한 것도 짚어 넘어가겠다)
디스페쳐 서블릿에서 핸들러(컨트롤러)를 찾는다
타입은 HandlerExecutionChain인데, 내부에 hanlder와 interceotList가 있다 (참고로 핸들러를 타기 전에 이 핸들러에 해당하는 인터셉터 리스트를 순회한다)
그 후 Handler Adpater 찾은 후
실행한다
추상 클래스인 AbstractHandlerMethodAdapter 의 handle메서드가 실행되는데,
다시 handleInternal 메서드 실행한다
그러면 이제 친숙한 RequestMappingHandlerAdapter로 이동한다
(참고로 상속 관계도는 아래와 같다)
다시 돌아가서, 아래 메서드를 실행
(일부 과정에서 그림은 생략한다)
RequestMappingHandlerAdpater의 invokeHandlerMethod(..)
(호출 위임) → ServletInvocableHandlerMethod의 invokeAndHandle(..) → invokeForRequest(..)
(부모로 이동) → InvocableHandlerMethod의 invokeForRequest(..) → getMethodArgumentValues(..)
ServletInvocableHandlerMethod의 상속 관계도
InvocableHandlerMethod 클래스
InvocableHandlerMethod 가 HandlerMethodArgumentResolverComposite 를 호출한다
자, 다 왔다.
저기있는 this.resolvers.supportsPrameter(..)를 통해, 파라미터의 타입에 따라 어떤 ArgumentResolver가 선택되는지 알 수 있다
다시 돌아가, resolvers에는 두 개의 ServletModelAttributeMethodProcessor가 등록돼 있다
자세히 보면 annotationNotRequiored가 각각 다르다
ServletModelAttributeMethodProcessor의 suportParameter를 해석하자
- ModelAtrribute어노테이션이 있거나
- 어노테이션이 필수가 아니고 단순 속성이 아니면
ModelAttributeMethodProcessor에 해당한다
나의 경우 (Arguemnt가 User)
1은 해당 안돼고
2는 annotationRequired가 참/거짓 모두다 스프링에 등록이 돼 있으니 무관하니
단순 속성이 아닌 케이스로 해당한다
BeanUtils.isSimpleProperty(..)를 자세히 보자
Check if the given type represents a "simple" value type: a primitive or primitive wrapper, an enum, a String or other CharSequence, a Number, a Date, a Temporal, a URI, a URL, a Locale, or a Class.
Void and void are not considered simple value types
[번역]
주어진 타입이 프리미티브 또는 프리미티브 래퍼, 열거형, 문자열 또는 기타 CharSequence, 숫자, 날짜, 임시, URI, URL, 로캘 또는 클래스 등 "단순한" 값 타입을 나타내는지 확인합니다.
무효 및 공백은 단순한 값 유형으로 간주되지 않습니다.
즉 결론은 단순 타입이 아닌 커스텀 Argument를 컨트롤러에서 주입 받을때, 커스텀 ArgumentResovler를 등록하지 않으면 ServletModelAttributeMethodProcessor (ModelAttributeMethodProcessor 의 자손)이 선택되며 Bean 생성된다
나의 경우는 ModelAttributeMethodProcessor 가 인스턴스 생성 요청을 했기 때문에 알 수 없는 비 직관적인 에러 로그를 확인했던 것…
추가 결론
- @RequestBody 어노테이션이 있으면 RequestResponseBodyMethodProcessor (A.R. 구현체)
- @Header가 있으면 RequestHeaderMethodArgumentResolver
- 개발자가 만든 클래스인데, 이에 대응하는 ArguementResolver가 없다? ServletModelAttributeMethodProcessor 를 거친다 (ModelAttributeMethodProcessor 구현체)
'Back-end > Spring Boot, JPA' 카테고리의 다른 글
Spring Data Repository 확장 (짧음) (0) | 2023.12.13 |
---|---|
Thread Safe하게 처리해보자 (feat. synchronized , 낙관, 비관, named) (0) | 2023.06.27 |
Argument Resolver 등록 관련 디버깅 (feat. Spring MVC 흐름) (0) | 2023.06.20 |
p6spy로 로그 포맷팅 (0) | 2023.06.16 |
Spring Boot 3.x에서 H2 DB 설정 (0) | 2023.05.12 |
hi hello... World >< 가장 아름다운 하나의 해답이 존재한다
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!