서블릿 호출은 DispatcherServlet 클래스를 찾자.
그리고 doDispatch 메소드가 실행된다.
doDispatch 메소드는 파라미터로 HttpServletRequest와 HttpServletResponse 타입을 받는다.
즉, 요청과 응답처리를 하겠다는 말이다.
웹 어플리케이션 서버를 구축하면 우리는 클라이언트와 소통을 할 수 있는 인터페이스를 구축한다. 인터페이스는 클라이언트가 우리 서비스에 접속하도록 연결하는 단자라고 보면 된다.
자, 클라이언트는 URL로 요청을 하면 @Controller가 선언된 클래스에 있는 메소드를 실행하여 응답을 보내준다.
여기서 요청을 받는 메소드는 Handler다. 그리고 이 Handler를 찾는 것은 DispatcherServlet 클래스의 doDispatch 메소드에서 일어난다.

RequestMappingHandlerMapping 클래스는 RequestMappingInfoHandlerMapping 추상클래스를 상속한다.
이미지는 그 상속 관계를 설명하고 있다.
@Controller 가 선언된 방식은 RequestMappingHandlerMapping 클래스로 Handler를 저장하고 가져올 수 있다. 다른 매핑클래스도 존재하지만 언급한 HanlderMapping 클래스를 주로 사용한다.
어플리케이션을 실행해보자.
RequestMappingHandlerMapping 클래스에서 afterProperitesSet 메소드가 실행된다. 이 메소드는 스프링에서 bean으로 등록됐으면 자동으로 호출되는 메소드다.
// RequestMappingHandlerMapping.class
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch());
this.config.setContentNegotiationManager(this.getContentNegotiationManager());
if (this.getPatternParser() != null && this.defaultPatternParser && (this.useSuffixPatternMatch || this.useRegisteredSuffixPatternMatch)) {
this.setPatternParser((PathPatternParser)null);
}
if (this.getPatternParser() != null) {
this.config.setPatternParser(this.getPatternParser());
Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch, "Suffix pattern matching not supported with PathPatternParser.");
} else {
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch());
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch());
this.config.setPathMatcher(this.getPathMatcher());
}
super.afterPropertiesSet();
}
메소드 마지막에서 super 즉, 부모의 afterPropertiesSet() 메소드를 호출한다.
// AbstractHandlerMapping.class
public void afterPropertiesSet() {
this.initHandlerMethods();
}
protected void initHandlerMethods() {
for(String beanName : this.getCandidateBeanNames()) {
if (!beanName.startsWith("scopedTarget.")) {
this.processCandidateBean(beanName);
}
}
this.handlerMethodsInitialized(this.getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = this.obtainApplicationContext().getType(beanName);
} catch (Throwable ex) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && this.isHandler(beanType)) {
this.detectHandlerMethods(beanName);
}
}
protected void detectHandlerMethods(Object handler) {
Class var10000;
if (handler instanceof String beanName) {
var10000 = this.obtainApplicationContext().getType(beanName);
} else {
var10000 = handler.getClass();
}
Class<?> handlerType = var10000;
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {
try {
return this.getMappingForMethod(method, userType);
} catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex);
}
});
if (this.logger.isTraceEnabled()) {
this.logger.trace(this.formatMappings(userType, methods));
} else if (this.mappingsLogger.isDebugEnabled()) {
this.mappingsLogger.debug(this.formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
this.registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
메소드 흐름 속에서 if 조건으로 isHanlder 메소드로 Handler인지 검증을 한다. 이 메소드는 각 구체적인 클래서에 정의되어 있고 RequestMappingHandlerMapping 클래스가 동착하므로 해당 클래스의 메소드를 참고해야 한다.
// RequestMappingHandlerMapping.class
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
}
bean을 순회하면서 @Controller 가 붙은 bean 만을 추출한다.
// AbstractHandlerMethodMapping.class
protected void detectHandlerMethods(Object handler) {
Class var10000;
if (handler instanceof String beanName) {
var10000 = this.obtainApplicationContext().getType(beanName);
} else {
var10000 = handler.getClass();
}
Class<?> handlerType = var10000;
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {
try {
return this.getMappingForMethod(method, userType);
} catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex);
}
});
if (this.logger.isTraceEnabled()) {
this.logger.trace(this.formatMappings(userType, methods));
} else if (this.mappingsLogger.isDebugEnabled()) {
this.mappingsLogger.debug(this.formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
this.registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
이 과정에서 RequestMappingInfo 객체를 생성한다.
// RequestMappingHandlerMapping.class
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMappingInfo requestMappingInfo = null;
RequestCondition var10000;
if (element instanceof Class<?> clazz) {
var10000 = this.getCustomTypeCondition(clazz);
} else {
var10000 = this.getCustomMethodCondition((Method)element);
}
RequestCondition<?> customCondition = var10000;
List<AnnotationDescriptor> descriptors = getAnnotationDescriptors(element);
List<AnnotationDescriptor> requestMappings = descriptors.stream().filter((desc) -> desc.annotation instanceof RequestMapping).toList();
if (!requestMappings.isEmpty()) {
if (requestMappings.size() > 1 && this.logger.isWarnEnabled()) {
this.logger.warn("Multiple @RequestMapping annotations found on %s, but only the first will be used: %s".formatted(element, requestMappings));
}
requestMappingInfo = this.createRequestMappingInfo((RequestMapping)((AnnotationDescriptor)requestMappings.get(0)).annotation, customCondition);
}
List<AnnotationDescriptor> httpExchanges = descriptors.stream().filter((desc) -> desc.annotation instanceof HttpExchange).toList();
if (!httpExchanges.isEmpty()) {
Assert.state(requestMappingInfo == null, () -> "%s is annotated with @RequestMapping and @HttpExchange annotations, but only one is allowed: %s".formatted(element, Stream.of(requestMappings, httpExchanges).flatMap(Collection::stream).toList()));
Assert.state(httpExchanges.size() == 1, () -> "Multiple @HttpExchange annotations found on %s, but only one is allowed: %s".formatted(element, httpExchanges));
requestMappingInfo = this.createRequestMappingInfo((HttpExchange)((AnnotationDescriptor)httpExchanges.get(0)).annotation, customCondition);
}
return requestMappingInfo;
}
ReuqestMapping 어노테이션을 가지고 있는지 확인 후 빌더 패턴으로 Request 정보를 저장한다.
protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(this.resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}
그리고 selectMethods 메소드 호출 결과는 Map을 반환하는데
Key 엔 Method를 Value엔 RequestMappingInfo를 담아서 반환한다.
이 Map을 다시 register로 등록을 하는데 AbstractHandlerMathodMapping 클래스 내부에 있는 MappingRegistry 클래스가 가진 아래의 Map에 RequestMappingInfo를 Key로 넣고 MappingRegistry가 가진 내부 클래스인 MappingRegistration 클래스에 HandlerMethod 를 담아 Value에 넣어서 저장한다.
private final Map<T, MappingRegistration<T>> registry = new HashMap();
// AbstractHandlerMethodMapping.class
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method);
this.validateMethodMapping(handlerMethod, mapping);
handlerMethod = handlerMethod.createWithValidateFlags();
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for(String path : directPaths) {
this.pathLookup.add(path, mapping);
}
String name = null;
if (AbstractHandlerMethodMapping.this.getNamingStrategy() != null) {
name = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping);
this.addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = AbstractHandlerMethodMapping.this.initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
corsConfig.validateAllowPrivateNetwork();
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration(mapping, handlerMethod, directPaths, name, corsConfig != null));
} finally {
this.readWriteLock.writeLock().unlock();
}
}
자, 여기 까지가 스프링이 @Controller에 선언된 Handler를 저장하는 과정이다.