티스토리 뷰

출처: https://dreamchaser3.tistory.com/12

 

1. 어플리케이션 구동

Spring Boot 애플리케이션이 구동되면, Spring의 ApplicationContext가 초기화되고, 이를 통해 애플리케이션의 들이 생성됩니다. Spring WebFlux 애플리케이션의 경우, 주로 AnnotationConfigReactiveWebServerApplicationContext 객체가 사용됩니다. 이 객체는 WebFlux 관련 빈들을 초기화하고 관리합니다.

  1. 애플리케이션 실행: SpringApplication.run() 호출로 애플리케이션이 시작됩니다.
  2. ApplicationContext 초기화 및 빈 주입 :
    • AnnotationConfigReactiveWebServerApplicationContext 구현체로 어노테이션 기반으로 모든 빈 주입

2. WebFlux 주요 Bean

 

a. NettyWebServer ( implements WebServer )

  • Netty기반으로 만들어진 HttpServer를 띄우고, 요청을 DispatcherHandler 에 넘긴다.
  • HTTP요청을 HttpServerRequest로 파싱.
package org.springframework.boot.web.embedded.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.springframework.boot.web.embedded.AbstractEmbeddedWebServer;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerException;
import org.springframework.boot.web.server.WebServerInitializer;
import org.springframework.util.Assert;
import reactor.netty.http.server.HttpServer;

public class NettyWebServer extends AbstractEmbeddedWebServer {

    private final HttpServer httpServer;
    private final EventLoopGroup eventLoopGroup;
    private Channel channel;

    public NettyWebServer(HttpServer httpServer) {
        Assert.notNull(httpServer, "HttpServer must not be null");
        this.httpServer = httpServer;
        this.eventLoopGroup = new NioEventLoopGroup();
    }

    @Override
    public void start() throws WebServerException {
        try {
            this.httpServer
                .run((httpServer -> {
                    // 초기화된 HttpServer로 서버를 시작
                    this.channel = httpServer.bindNow();
                    this.channel.onClose()
                        .doFinally(signalType -> this.stop());
                }));
        } catch (Exception ex) {
            throw new WebServerException("Failed to start the server", ex);
        }
    }

    @Override
    public void stop() {
        if (this.channel != null) {
            this.channel.close();
        }
        this.eventLoopGroup.shutdownGracefully();
    }

    @Override
    public int getPort() {
        return this.channel != null ? ((InetSocketAddress) this.channel.localAddress()).getPort() : -1;
    }

    @Override
    public WebServer initialize(WebServerInitializer initializer) {
        // 초기화 과정에서 필요한 작업을 처리
        return this;
    }

    public static class NettyServerBuilder {

        private final HttpServer httpServer;

        public NettyServerBuilder() {
            this.httpServer = HttpServer.create();
        }

        public NettyWebServer build() {
            return new NettyWebServer(this.httpServer);
        }

        public NettyServerBuilder port(int port) {
            this.httpServer.port(port);
            return this;
        }

        public NettyServerBuilder handler(WebHandler handler) {
            this.httpServer.handle((request, response) -> handler.handle(request, response));
            return this;
        }

        public NettyServerBuilder logging(LogLevel logLevel) {
            this.httpServer.doOnBound((server) -> server.tcpConfiguration(tcp -> tcp.option(ChannelOption.SO_RCVBUF, 1024))
                .doOnChannelInit((channel) -> channel.pipeline().addLast(new LoggingHandler(logLevel))));
            return this;
        }
    }
}

 

 


b. DispatcherHandler (implements HttpHandler) 

  • 요청에 맞는 HandlerMapping 탐색 => 요청 처리
  • WebMVC의 DispatcherServlet과 유사
package org.springframework.web.reactive;

import java.util.Collections;
import java.util.List;

import reactor.core.publisher.Mono;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.handler.ExceptionHandlingWebHandler;

/**
 * Spring WebFlux의 핵심 `WebHandler` 구현체로,  
 * HTTP 요청을 적절한 핸들러(Controller)로 매핑하고 실행함.
 * 
 * Spring MVC의 `DispatcherServlet`과 같은 역할을 함.
 */
public class DispatcherHandler implements WebHandler, ApplicationContextAware, InitializingBean {

    @Nullable
    private List<HandlerMapping> handlerMappings;

    @Nullable
    private List<HandlerAdapter> handlerAdapters;

    @Nullable
    private List<WebExceptionHandler> exceptionHandlers;

    @Nullable
    private ApplicationContext applicationContext;

    /**
     * `ApplicationContextAware` 인터페이스 구현 (Spring 컨텍스트 주입)
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * `InitializingBean` 구현체로, Bean 생성 후 자동으로 실행됨.
     * `HandlerMapping`, `HandlerAdapter`, `WebExceptionHandler`를 가져옴.
     */
    @Override
    public void afterPropertiesSet() {
        if (this.applicationContext == null) {
            throw new IllegalStateException("ApplicationContext가 설정되지 않았습니다.");
        }

        this.handlerMappings = initStrategies(this.applicationContext, HandlerMapping.class);
        this.handlerAdapters = initStrategies(this.applicationContext, HandlerAdapter.class);
        this.exceptionHandlers = initStrategies(this.applicationContext, WebExceptionHandler.class);
    }

    /**
     * HTTP 요청을 처리하는 핵심 메서드
     * @param exchange HTTP 요청 및 응답을 포함하는 ServerWebExchange 객체
     */
    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        return getHandler(exchange)
            .flatMap(handler -> getHandlerAdapter(handler)
                .flatMap(adapter -> adapter.handle(exchange, handler)))
            .switchIfEmpty(Mono.defer(() -> handleNoMatch(exchange)))
            .onErrorResume(ex -> handleDispatchException(exchange, ex));
    }

    /**
     * `HandlerMapping`을 통해 요청을 처리할 적절한 핸들러 찾기
     */
    private Mono<Object> getHandler(ServerWebExchange exchange) {
        return Flux.fromIterable(this.handlerMappings != null ? this.handlerMappings : Collections.emptyList())
            .concatMap(mapping -> mapping.getHandler(exchange))
            .next();
    }

    /**
     * `HandlerAdapter`를 통해 적절한 핸들러 실행
     */
    private Mono<HandlerAdapter> getHandlerAdapter(Object handler) {
        return Flux.fromIterable(this.handlerAdapters != null ? this.handlerAdapters : Collections.emptyList())
            .filter(adapter -> adapter.supports(handler))
            .next()
            .switchIfEmpty(Mono.error(() -> new IllegalStateException("HandlerAdapter를 찾을 수 없습니다: " + handler)));
    }

    /**
     * 핸들러를 찾을 수 없을 때 (404 Not Found)
     */
    private Mono<Void> handleNoMatch(ServerWebExchange exchange) {
        exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
        return exchange.getResponse().setComplete();
    }

    /**
     * 요청 처리 중 예외 발생 시 `WebExceptionHandler`를 통해 처리
     */
    private Mono<Void> handleDispatchException(ServerWebExchange exchange, Throwable ex) {
        return Flux.fromIterable(this.exceptionHandlers != null ? this.exceptionHandlers : Collections.emptyList())
            .concatMap(handler -> handler.handle(exchange, ex))
            .next();
    }

    /**
     * ApplicationContext에서 특정 전략 빈 리스트를 가져오는 메서드
     */
    private <T> List<T> initStrategies(ApplicationContext context, Class<T> strategyType) {
        return context.getBeanProvider(strategyType).orderedStream().toList();
    }
}

 


c. HandlerMapping:

  • RouterFunction 기반, 어노테이션 기반으로 된 요청정보 <->  핸들러정보 가지고있는 객체
/*
 * Copyright 2002-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.reactive.handler;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

import org.springframework.beans.BeansException;
import org.springframework.util.CollectionUtils;

/**
 * Implementation of the {@link org.springframework.web.reactive.HandlerMapping}
 * interface to map from URLs to request handler beans. Supports both mapping
 * to bean instances and mapping to bean names; the latter is required for
 * non-singleton handlers.
 *
 * <p>The "urlMap" property is suitable for populating the handler map with
 * bean instances. Mappings to bean names can be set via the "mappings"
 * property, in a form accepted by the {@code java.util.Properties} class,
 * as follows:
 *
 * <pre class="code">
 * /welcome.html=ticketController
 * /show.html=ticketController</pre>
 *
 * <p>The syntax is {@code PATH=HANDLER_BEAN_NAME}. If the path doesn't begin
 * with a slash, one is prepended.
 *
 * <p>Supports direct matches, for example, a registered "/test" matches "/test", and
 * various Ant-style pattern matches, for example, a registered "/t*" pattern matches
 * both "/test" and "/team", "/test/*" matches all paths under "/test",
 * "/test/**" matches all paths below "/test". For details, see the
 * {@link org.springframework.web.util.pattern.PathPattern} javadoc.
 *
 * @author Rossen Stoyanchev
 * @author Sam Brannen
 * @since 5.0
 */
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {

	private final Map<String, Object> urlMap = new LinkedHashMap<>();


	/**
	 * Create a {@code SimpleUrlHandlerMapping} with default settings.
	 */
	public SimpleUrlHandlerMapping() {
	}

	/**
	 * Create a {@code SimpleUrlHandlerMapping} using the supplied URL map.
	 * @param urlMap map with URL paths as keys and handler beans (or handler
	 * bean names) as values
	 * @since 5.2
	 * @see #setUrlMap(Map)
	 */
	public SimpleUrlHandlerMapping(Map<String, ?> urlMap) {
		setUrlMap(urlMap);
	}

	/**
	 * Create a {@code SimpleUrlHandlerMapping} using the supplied URL map and order.
	 * @param urlMap map with URL paths as keys and handler beans (or handler
	 * bean names) as values
	 * @param order the order value for this {@code SimpleUrlHandlerMapping}
	 * @since 5.2
	 * @see #setUrlMap(Map)
	 * @see #setOrder(int)
	 */
	public SimpleUrlHandlerMapping(Map<String, ?> urlMap, int order) {
		setUrlMap(urlMap);
		setOrder(order);
	}


	/**
	 * Map URL paths to handler bean names.
	 * This is the typical way of configuring this HandlerMapping.
	 * <p>Supports direct URL matches and Ant-style pattern matches. For syntax details,
	 * see the {@link org.springframework.web.util.pattern.PathPattern} javadoc.
	 * @param mappings properties with URLs as keys and bean names as values
	 * @see #setUrlMap
	 */
	public void setMappings(Properties mappings) {
		CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
	}

	/**
	 * Set a Map with URL paths as keys and handler beans (or handler bean names)
	 * as values. Convenient for population with bean references.
	 * <p>Supports direct URL matches and Ant-style pattern matches. For syntax details,
	 * see the {@link org.springframework.web.util.pattern.PathPattern} javadoc.
	 * @param urlMap map with URLs as keys and beans as values
	 * @see #setMappings
	 */
	public void setUrlMap(Map<String, ?> urlMap) {
		this.urlMap.putAll(urlMap);
	}

	/**
	 * Allow {@code Map} access to the URL path mappings, with the option to add or
	 * override specific entries.
	 * <p>Useful for specifying entries directly, for example via "urlMap[myKey]".
	 * This is particularly useful for adding or overriding entries in child
	 * bean definitions.
	 */
	public Map<String, ?> getUrlMap() {
		return this.urlMap;
	}


	/**
	 * Calls the {@link #registerHandlers} method in addition to the
	 * superclass's initialization.
	 */
	@Override
	public void initApplicationContext() throws BeansException {
		super.initApplicationContext();
		registerHandlers(this.urlMap);
	}

	/**
	 * Register all handlers specified in the URL map for the corresponding paths.
	 * @param urlMap a Map with URL paths as keys and handler beans or bean names as values
	 * @throws BeansException if a handler couldn't be registered
	 * @throws IllegalStateException if there is a conflicting handler registered
	 */
	protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
		if (urlMap.isEmpty()) {
			logger.trace("No patterns in " + formatMappingName());
		}
		else {
			for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
				String url = entry.getKey();
				Object handler = entry.getValue();
				// Prepend with slash if not already present.
				if (!url.startsWith("/")) {
					url = "/" + url;
				}
				// Remove whitespace from handler bean name.
				if (handler instanceof String handlerName) {
					handler = handlerName.trim();
				}
				registerHandler(url, handler);
			}
			logMappings();
		}
	}

	private void logMappings() {
		if (mappingsLogger.isDebugEnabled()) {
			mappingsLogger.debug(formatMappingName() + " " + getHandlerMap());
		}
		else if (logger.isDebugEnabled()) {
			logger.debug("Patterns " + getHandlerMap().keySet() + " in " + formatMappingName());
		}
	}

}

d. HandlerAdapter:

  • HandlerMapping를 통해, 넘어온 핸들러 정보를 사용하여 실제작업을 처리 후 결과리턴
/*
 * Copyright 2002-2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.function.support;

import java.util.List;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;

import org.springframework.core.Ordered;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.web.context.request.async.AsyncWebRequest;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.function.HandlerFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

/**
 * {@code HandlerAdapter} implementation that supports {@link HandlerFunction}s.
 *
 * @author Arjen Poutsma
 * @since 5.2
 */
public class HandlerFunctionAdapter implements HandlerAdapter, Ordered {

	private static final Log logger = LogFactory.getLog(HandlerFunctionAdapter.class);

	private int order = Ordered.LOWEST_PRECEDENCE;

	private @Nullable Long asyncRequestTimeout;

	/**
	 * Specify the order value for this HandlerAdapter bean.
	 * <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
	 * @see org.springframework.core.Ordered#getOrder()
	 */
	public void setOrder(int order) {
		this.order = order;
	}

	@Override
	public int getOrder() {
		return this.order;
	}

	/**
	 * Specify the amount of time, in milliseconds, before concurrent handling
	 * should time out. In Servlet 3, the timeout begins after the main request
	 * processing thread has exited and ends when the request is dispatched again
	 * for further processing of the concurrently produced result.
	 * <p>If this value is not set, the default timeout of the underlying
	 * implementation is used.
	 * <p>A value of 0 or less indicates that the asynchronous operation will never
	 * time out.
	 * @param timeout the timeout value in milliseconds
	 */
	public void setAsyncRequestTimeout(long timeout) {
		this.asyncRequestTimeout = timeout;
	}

	@Override
	public boolean supports(Object handler) {
		return handler instanceof HandlerFunction;
	}

	@Override
	public @Nullable ModelAndView handle(HttpServletRequest servletRequest,
			HttpServletResponse servletResponse,
			Object handler) throws Exception {

		WebAsyncManager asyncManager = getWebAsyncManager(servletRequest, servletResponse);
		servletResponse = getWrappedResponse(asyncManager);

		ServerRequest serverRequest = getServerRequest(servletRequest);
		ServerResponse serverResponse;

		if (asyncManager.hasConcurrentResult()) {
			serverResponse = handleAsync(asyncManager);
		}
		else {
			HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
			serverResponse = handlerFunction.handle(serverRequest);
		}

		if (serverResponse != null) {
			return serverResponse.writeTo(servletRequest, servletResponse, new ServerRequestContext(serverRequest));
		}
		else {
			return null;
		}
	}

	private WebAsyncManager getWebAsyncManager(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
		AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(servletRequest, servletResponse);
		asyncWebRequest.setTimeout(this.asyncRequestTimeout);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(servletRequest);
		asyncManager.setAsyncWebRequest(asyncWebRequest);
		return asyncManager;
	}

	/**
	 * Obtain response wrapped by
	 * {@link org.springframework.web.context.request.async.StandardServletAsyncWebRequest}
	 * to enforce lifecycle rules from Servlet spec (section 2.3.3.4)
	 * in case of async handling.
	 */
	private static HttpServletResponse getWrappedResponse(WebAsyncManager asyncManager) {
		AsyncWebRequest asyncRequest = asyncManager.getAsyncWebRequest();
		Assert.notNull(asyncRequest, "No AsyncWebRequest");

		HttpServletResponse servletResponse = asyncRequest.getNativeResponse(HttpServletResponse.class);
		Assert.notNull(servletResponse, "No HttpServletResponse");

		return servletResponse;
	}

	private ServerRequest getServerRequest(HttpServletRequest servletRequest) {
		ServerRequest serverRequest =
				(ServerRequest) servletRequest.getAttribute(RouterFunctions.REQUEST_ATTRIBUTE);
		Assert.state(serverRequest != null, () -> "Required attribute '" +
				RouterFunctions.REQUEST_ATTRIBUTE + "' is missing");
		return serverRequest;
	}

	private @Nullable ServerResponse handleAsync(WebAsyncManager asyncManager) throws Exception {
		Object result = asyncManager.getConcurrentResult();
		asyncManager.clearConcurrentResult();
		LogFormatUtils.traceDebug(logger, traceOn -> {
			String formatted = LogFormatUtils.formatValue(result, !traceOn);
			return "Resume with async result [" + formatted + "]";
		});
		if (result instanceof ServerResponse response) {
			return response;
		}
		else if (result instanceof Exception exception) {
			throw exception;
		}
		else if (result instanceof Throwable throwable) {
			throw new ServletException("Async processing failed", throwable);
		}
		else if (result == null) {
			return null;
		}
		else {
			throw new IllegalArgumentException("Unknown result from WebAsyncManager: [" + result + "]");
		}
	}


	private static class ServerRequestContext implements ServerResponse.Context {

		private final ServerRequest serverRequest;


		public ServerRequestContext(ServerRequest serverRequest) {
			this.serverRequest = serverRequest;
		}

		@Override
		public List<HttpMessageConverter<?>> messageConverters() {
			return this.serverRequest.messageConverters();
		}
	}
}

 

 

 

 


3. 애플리케이션 구동, HTTP 요청 처리 과정

1) 빈 초기화:

WebFlux 관련 빈들이 어플리케이션 컨텍스트에 등록.

WebServer, HttpHandler, DispatcherHandler, HandlerMapping, HandlerAdapter

  • DispatcherHandler는 HTTP 요청을 처리하는 핵심 처리기이며, HandlerMapping 빈들이 매핑을 담당합니다. HandlerAdapter 빈들이 요청 처리 방식을 정의합니다.

2) 요청이 들어왔을 때:

클라이언트가 HTTP 요청을 보내면, **DispatcherHandler**가 해당 요청을 받아서 처리하기 위한 핸들러를 찾습니다. 이 과정은 비동기적으로 이루어집니다.

  • DispatcherHandler는 HandlerMapping을 통해 URL과 HTTP 메서드에 맞는 핸들러를 찾습니다.
  • HandlerAdapter가 적절한 핸들러 메서드를 실행합니다.
  • 실행된 핸들러가 반환한 데이터는 Mono 또는 **Flux**로 감싸져 클라이언트에 비동기적으로 응답됩니다.

3) 핸들러 매핑과 어댑터:

요청이 들어오면 DispatcherHandler는 핸들러 매핑을 사용하여 핸들러를 찾습니다. 찾은 핸들러는 핸들러 어댑터를 통해 실행됩니다.

  • HandlerMapping은 URL 경로와 HTTP 메서드(GET, POST 등)를 기반으로 요청을 처리할 핸들러 메서드를 찾습니다.
  • HandlerAdapter는 해당 핸들러 메서드를 실행하는 역할을 하며, 이때 비동기 방식으로 결과를 반환합니다.

4) 예외 처리:

요청 처리 중 예외가 발생하면, **ExceptionHandler**가 해당 예외를 처리합니다. ExceptionHandlerExceptionResolver가 등록된 예외 핸들러를 찾아 실행합니다.


4. 핸들러 및 어댑터 초기화

  • RequestMappingHandlerMapping: @RequestMapping과 같은 애노테이션을 기반으로 요청 URL과 핸들러를 매핑합니다.
  • RequestMappingHandlerAdapter: @RequestMapping을 처리하는 어댑터로, 요청에 맞는 핸들러 메서드를 실행합니다.
  • WebFilter와 HandlerInterceptor: 웹 필터와 인터셉터는 요청이 처리되기 전에 특정 작업을 수행합니다. 필터는 요청을 처리하기 전에 실행되고, 인터셉터는 핸들러 메서드 호출 전후로 실행됩니다.

5. 요청 처리 흐름 예시

  1. HTTP 요청 수신: 클라이언트가 /api/example로 GET 요청을 보냅니다.
  2. DispatcherHandler: DispatcherHandler가 요청을 받아들이고, HandlerMapping을 사용하여 해당 요청을 처리할 핸들러를 찾습니다.
  3. 핸들러 매핑: RequestMappingHandlerMapping이 요청 URL(/api/example)과 메서드(GET)에 맞는 핸들러 메서드를 찾습니다.
  4. 핸들러 어댑터: RequestMappingHandlerAdapter가 해당 핸들러 메서드를 실행하고, 결과를 Mono로 반환합니다.
  5. 응답 처리: 비동기적으로 실행된 핸들러 메서드가 결과를 클라이언트로 전송합니다.

 

정리

Spring WebFlux 기반 애플리케이션은 요청 처리와 빈 초기화가 비동기적이고 논블로킹 방식으로 이루어집니다. HTTP 요청이 들어오면 **DispatcherHandler**가 이를 처리하고, **HandlerMapping**과 **HandlerAdapter**를 통해 적절한 핸들러를 찾아 실행합니다. 빈 초기화 과정에서 DispatcherHandler, HandlerMapping, HandlerAdapter 빈들이 등록되고, HTTP 요청은 비동기적으로 처리됩니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
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
글 보관함