Spring MVC Request Life Cycle - HandlerMapping

출처 : https://all-record.tistory.com/164

목차

Spring MVC Request Life Cycle - HandlerMapping

RequestMapping 어노테이션을 처리하는 Handler, Handler Adapter는 RequestMappingHandlerMapping, RequestMappingHandlerAdapter다

HandlerMapping 살펴보기

Request(요청)와 Request를 처리하기 위한 Handler의 Mapping 정보를 관리한다.

HttpServletRequest 객체 Attribute에서 Handler의 정보를 가져올 수 있다.

  • BEST_MATCHING_HANDLER_ATTRIBUTE
    • 요청 URL과 가장 많이 일치하는 Handler를 가져온다.
  • PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
    • 요청을 처리하기 위한 Handler의 URL을 가져온다.
  • BEST_MATCHING_PATTERN_ATTRIBUTE
    • 요청 URL과 가장 많이 일치하는 Handler Pattern(URL)을 가져온다.
  • INTROSPECT_TYPE_LEVEL_MAPPING
  • URI_TEMPLATE_VARIABLES_ATTRIBUTE
    • 요청 URL에 포함된 PathVariable 정보를 가져온다.
  • MATRIX_VARIABLES_ATTRIBUTE
    • 요청 URL에 포함된 MATRIX 변수 정보를 가져온다.
    • Matrix 변수를 사용하기 위해서는 WebMvcConfigurer 를 이용해 설정해줘야 한다.
    • 반환 값은 MultiValueMap 형태의 데이터다.
  • PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
    • HttpServletRequest 객체내 Attribute에서 Handler가 반환하는 Media Type 정보를 가져온다.

Matrix 변수 사용하기 - https://www.baeldung.com/spring-mvc-matrix-variables

HandlerMapping.java

public interface HandlerMapping {

String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";

@Deprecated
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

default boolean usesPathPatterns() {
return false;
}

// 요청에 대한 Handler 와 Interceptor를 포함하는 HandlerExecutionChain을 반환한다.
// 만약 요청에 적절한 Handler가 없을 경우 null을 반환한다.
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

요청을 처리할 수 있는 Handler 가져오기

AbstractHandlerMapping 는 HandlerMapping 을 구현한 추상 클래스, 요청 경로와 Mapping된 Hanlder Interceptor 와 Default Hanlder 를 지원한다.

  • Request를 처리하기 위한 Hander를 가져온다.

AbstractHandlerMapping.java

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// Request를 처리하기 위한 Handler를 가져온다.
Object handler = getHandlerInternal(request);

if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}

// Bean에 등록된 Handler를 가져온다.
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}

// Ensure presence of cached lookupPath for interceptors and others
if (!ServletRequestPathUtils.hasCachedPath(request)) {
initLookupPath(request);
}

HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
logger.debug("Mapped to " + executionChain.getHandler());
}

if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {
config.validateAllowCredentials();
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}

return executionChain;
}

AbstractHandlerMapping.java

protected String initLookupPath(HttpServletRequest request) {
if (usesPathPatterns()) {
request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
String lookupPath = requestPath.pathWithinApplication().value();
return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
}
else {
return getUrlPathHelper().resolveAndCacheLookupPath(request);
}
}

AbstractHandlerMapping.java

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}

AbstractHandlerMethodMapping.java

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}

AbstractHandlerMethodMapping.java

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);

// 완전히 일치하는지 확인한다.
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
// 요청 URL을 포함하는 Handler들이 있는지 확인한다.
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);

// 요청에 대한 Handler가 여러개 있는 경우 가장 많이 일치하는 Handler를 가져온다.
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
for (Match match : matches) {
if (match.hasCorsConfig()) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
}
}
else {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.getHandlerMethod().getMethod();
Method m2 = secondBestMatch.getHandlerMethod().getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
}

// Request 객체에 Handler를 BEST_MATCHING_HANDLER_ATTRIBUTE Key로 저장한다.
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.getHandlerMethod();
}
else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
Share