DevLog ๐ถ
[Spring] DispatcherServlet์ ๋์ ๊ณผ์ ๊ณผ HanderMapping, HandlerAdapter, @RequestMapping ๋ณธ๋ฌธ
[Spring] DispatcherServlet์ ๋์ ๊ณผ์ ๊ณผ HanderMapping, HandlerAdapter, @RequestMapping
dolmeng2 2022. 8. 16. 19:01- ๊น์ํ ๋์ '์คํ๋ง MVC 1ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํต์ฌ ๊ธฐ์ '์ ๋ณด๊ณ ์ ๋ฆฌํ ๊ธ์ ๋๋ค ๐
- ์ง๋ ํฌ์คํ ๊ณผ ์ด์ด์ง๋๋ค :D
- ์ง๋ ๊ฐ์์์ MVC ํจํด์ ์ตํ๊ธฐ ์ํด V1~V5๊น์ง ์ ์ง์์ผ๋๊ฐ๋ฉฐ ๊ตฌ์กฐ๋ฅผ ์ดํดํ๋ค.
- ์ด๋ฒ ํฌ์คํ ์์๋ ์คํ๋ง์์ ์ด๋ฌํ MVC ํจํด์ ์ด๋ป๊ฒ ์ ๊ณตํด์ฃผ๊ณ ์๋์ง ์์๋ณด์.
| Spring MVC
- SpringMVC ๊ตฌ์กฐ์ ๋ํด์ ์์๋ณด์.
์ฐ๋ฆฌ๊ฐ ์ง๊ธ๊น์ง ๊ตฌํํ์๋ 'FrontController'๋ ์คํ๋งMVC์์ 'DispatcherServlet'์ผ๋ก ๊ตฌํ๋์ด ์๋ค.
DispatcherServlet์ HttpServlet์ ์์๋ฐ์์ ์ฌ์ฉํ๋ฉฐ, ์๋ธ๋ฆฟ์ผ๋ก ๋์ํ๋ค.
์ ํํ๊ฒ๋ DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet -> FrameworkServlet ์ด๋ ๊ฒ.
์คํ๋ง ๋ถํธ DispatcherServlet์ ์๋์ผ๋ก ๋ฑ๋กํ๋ฉฐ, ๋ชจ๋ ๊ฒฝ๋ก์ ๋ํด์ ๋งคํํ๋ค!
๋จ, ๊ตฌ์ฒด์ ์ธ ๊ฒ๋ณด๋ค ์์ธํ ๊ฒ ๋ ๋จผ์ ๋์ํ๊ธฐ ๋๋ฌธ์ ๊ธฐ์กด์ ๋ฑ๋กํ ์๋ธ๋ฆฟ๋ ํจ๊ป ๋์ํ๋ค.
์คํ๋ง MVC๋ FrameworkServlet์ service()์ ์ค๋ฒ๋ผ์ด๋ฉํ์์ผ๋ฉฐ, ์ด๋ฅผ ์์์ผ๋ก ํด์ DispatcherServlet์ doDispatch()๋ฅผ ํธ์ถํ๋ค.
๐ฉ DispatcherServlet ์ญ์ ์๋ธ๋ฆฟ์ด๊ธฐ ๋๋ฌธ์, ๋ง์ฐฌ๊ฐ์ง๋ก init(), doPost, doGet, service ๊ฐ์ ๋ฉ์๋๊ฐ ์กด์ฌํ๋ค.
๋ถ๋ชจ์ธ FrameworkServlet์ doGet(), doPost()๊ฐ request, response๋ฅผ processRequest๋ฅผ ํตํด ๋๊ฒจ์ฃผ๋ฉฐ,
ํด๋น ํจ์์์ doService()๋ฅผ ํธ์ถํ๋ค. ๊ทธ๋ฆฌ๊ณ doService๋ ๊ฒฐ๊ตญ doDispatch()๋ฅผ ํธ์ถํ๋ค!
[DispatcherServlet - doDispatch()]
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// ####### 1 ########
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// ####### 2 ########
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// ####### 3 ########
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// ####### 4 ########
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// ####### 5 ########
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
- ๋ด๋ถ์ ๋ฉ์๋๋ฅผ ์ดํด๋ณด์.
๐ #### 1๋ฒ ####
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
ํ์ฌ์ ์์ฒญ(request)์ ๋ํ ํธ๋ค๋ฌ๋ฅผ ๊ฒฐ์ ํ๋ ๋ถ๋ถ์ด๋ค.
๋ง์ฝ ํธ๋ค๋ฌ๋ฅผ ์ฐพ์ง ๋ชปํ๋ฉด noHandlerFound๋ฅผ ํธ์ถํ๋๋ฐ, ๋ด๋ถ์ response.sendError(HttpServletResponse.SC_NOT_FOUND);๊ฐ ์๋ค.
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
getHandler์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์๊ฒผ๋ค.
์ฌ๊ธฐ์ HandlerExecutionChain์ด๋ผ๋ ๊ฑธ ๋ณผ ์ ์๋๋ฐ, ์ด๋ ํธ๋ค๋ฌ ๊ฐ์ฒด์ ํธ๋ค๋ฌ ์ธํฐ์ ํฐ๋ก ๊ตฌ์ฑ๋ ์ผ์ข ์ ์ฒด์ธ์ด๋ผ๊ณ ํ๋ค.
this.handlerMappings๋ผ๊ณ ๋์ด ์๋๋ฐ, DispatcherServlet์ด ๋ด๋ถ์์ ์ด๊ธฐํ ์ ๋ฑ๋กํ ์ฌ๋ฌ handlerMapping์ ๋ฆฌ์คํธ์ด๋ค.
๊ฐ ๋ฆฌ์คํธ์ ์์์ ๋ํด์ getHandler๋ฅผ ํตํด (์ด๋ ํธ๋ค๋ฌ ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํด์ฃผ๋ ํจ์์ด๋ค) ์์ฒญ์ ๋ง๋ handler๋ฅผ ๋ฆฌํดํด์ค๋ค.
๐ #### 2๋ฒ ####
- ์, ์ด๋ ๊ฒ ํธ๋ค๋ฌ๋ฅผ ์ฐพ์์ผ๋ ์ด์ ํธ๋ค๋ฌ ์ด๋ํฐ๋ฅผ ์ฐพ์์ผ ํ๋ค.
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
๋ค์๊ณผ ๊ฐ์ด ํ๋ผ๋ฏธํฐ๋ก ํธ๋ค๋ฌ๋ฅผ ๋๊ฒจ์ค ๋ค์์, ํด๋น ํธ๋ค๋ฌ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋์ง๋ฅผ supportํจ์๋ก ํ๋จํ๋ค!
๋ง์ฐฌ๊ฐ์ง๋ก ์ฒ๋ฆฌํ ์ ์๋ค๋ฉด Exception์ ํฐํธ๋ ค์ค๋ค.
๐ #### 3๋ฒ ####
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
๊ทธ๋ฆฌ๊ณ , ์ด์ applyPreHandle์ ํตํด์ ์ธํฐ์ ํฐ๋ฅผ ์คํํด์ค๋ค๊ณ ๋ณด๋ฉด ๋๋ค.
์๊น, mappedHandler = getHandler()๋ฅผ ์คํํ์ ๋ HandlerExecutionChain์ด ๋ฐํ๋ ๊ฑด ๋ฐ๋ก ์ด ์ด์ ๋๋ฌธ์ด๋ค.
-> ์ธํฐ์ ํฐ ์ฒ๋ฆฌ๋ฅผ ์ํด!
์ฌ๊ธฐ์ preHandle์ ์ฌ์ฉํ์๋๋ฐ, ์ด๋ ํธ๋ค๋ฌ๊ฐ ์คํ๋๊ธฐ ์ ์ ๊ฐ๋ก์ฑ์ ์ด๋ ํ ์กฐ๊ฑด์ ๋ง๋์ง ์ฒดํฌํ๋ ์ญํ ์ ์ฃผ๋ก ์คํํ๋ค.
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
- ๋ฐํ๊ฐ์ด true๋ฉด ๋ค์์ผ๋ก, false๋ฉด return ์์ผ๋ฒ๋ฆฐ๋ค.
๐ #### 4๋ฒ ####
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
- ๊ทธ๋ฆฌ๊ณ , ์ด์ ์ค์ ๋ก ํธ์ถ์ ์งํํด์ค๋ค!
- ์์์ ์ฐพ์ ํธ๋ค๋ฌ ์ด๋ํฐ(ha)์ handle() ๋ฉ์๋๋ฅผ ์คํํ์ฌ ModelAndView ๊ฐ์ฒด๋ฅผ ๋ฐํ๋ฐ๋๋ค.
- ๋ณดํต์ HandlerAdapter๋ ์ธํฐํ์ด์ค๋ก ์ ์๋์ด ์๊ธฐ ๋๋ฌธ์, ํธ๋ค๋ฌ์ ๋ง๋ ๊ตฌํ์ฒด๊ฐ ์คํ๋๋ค๊ณ ๋ณผ ์ ์๋ค.
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
- ์ดํ ์ํ์ด ๋๋๋ฉด ์ธํฐ์ ํฐ๋ก applyPostHandle์ ํธ์ถํ๋๋ฐ, ์ด๋ ๋ก์ง์ด ์ข ๋ฃ ํ ์คํ๋๋ ๋ฉ์๋๋ผ๊ณ ๋ณผ ์ ์๋ค.
๐ #### 5๋ฒ ####
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
- ๋ค์์ผ๋ก processDispatchResult๋ฅผ ํธ์ถํ๋ค.
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// ####### 6 ########
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
์ฌ๊ธฐ์ ์ ๋ณด๋ฉด render()๋ฅผ ํธ์ถํ๋ ๊ฑธ ๋ณผ ์ ์๋ค.
๐ #### 6๋ฒ ####
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
๊ทธ๋ฆฌ๊ณ , ๋ด๋ถ์์๋ resolveViewName()๋ฅผ ํธ์ถํ์ฌ view์ ๋ ผ๋ฆฌ ์ด๋ฆ์ ๋ฌผ๋ฆฌ ์ด๋ฆ์ผ๋ก ๋ณํํด์ค๋ค. (์ค์ ๋ทฐ ์ฐพ๊ธฐ)
์ต์ข ์ ์ผ๋ก ๋ ๋๋ง์ ํ๊ธฐ ์ํด์ view.render()๋ฅผ ํตํด์ ์ค์ ๋ก ๋ทฐ์ ๋ ๋๋ง์ ์งํํ๋ค!
view์ ModelAndView.getModelInternal()์ ํ์ฉํด model์ ๋ฐ์ดํฐ๋ฅผ view์ ๋ ๋๋ง์ ํด์ฃผ๋ ๊ฒ์ด๋ค.
์ด๋ ๊ฒ DispatcherServlet์ ์ ๋ฐ์ ์ธ ๊ตฌ์กฐ์ ๋ํด์ ์์๋ณด์๋ค.
์คํ๋ง MVC๋ ์ด๋ฐ DispatcherServlet ์ฝ๋์ ๋ณ๊ฒฝ ์์ด ์ํ๋ ๊ธฐ๋ฅ์ ๋ณ๊ฒฝํ๊ฑฐ๋ ํ์ฅํ ์ ์๋๋ก ์ ๊ณตํ๋ค.
-> ํธ๋ค๋ฌ ๋งคํ (HandlerMapping) / ํธ๋ค๋ฌ ์ด๋ํฐ (HandlerAdapter) / ๋ทฐ ๋ฆฌ์กธ๋ฒ (ViewResolver) / ๋ทฐ (View)
์ด๋ฌํ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ ๋์คํจ์ฒ ์๋ธ๋ฆฟ์ ๋ฑ๋ก๋ง ํด์ฃผ๋ฉด ๋๋ค.
ํ์ง๋ง, ์ง์ ๋ง๋ค ์ผ์ ๊ฑฐ์ ์๋ค. ์๋๋ฉด ์ฌ๋งํ ๊ฑฐ๋ ๋ค ์๊ธฐ ๋๋ฌธ...!
| HandlerMapping / HandlerAdapter / ViewResolver
- ํธ๋ค๋ฌ ๋งคํ๊ณผ ํธ๋ค๋ฌ ์ด๋ํฐ๋ ์ด๋ป๊ฒ ์ฌ์ฉ์ด ๋ ๊น?
- ๊ธฐ๋ณธ์ ์ผ๋ก Controller๋ฅผ ํธ์ถํ๊ธฐ ์ํด์๋ 2๊ฐ์ง๊ฐ ํ์ํ๋ค.
โ HandlerMapping (ํธ๋ค๋ฌ ๋งคํ)
- ์คํ๋ง ๋น์ ์ด๋ฆ์ผ๋ก ํธ๋ค๋ฌ๋ฅผ ์ฐพ์ ์ ์๋ ํธ๋ค๋ฌ ๋งคํ์ด ํ์ํ๋ค.
ex) โญRequestMappingHandlerMapping : @RequestMapping์์ ์ฌ์ฉ
BeanNameUrlHandlerMapping: ์คํ๋ง ๋น์ ์ด๋ฆ์ผ๋ก ํธ๋ค๋ฌ ์ฐพ์
โ HandlerAdapter (ํธ๋ค๋ฌ ์ด๋ํฐ)
- ํธ๋ค๋ฌ ๋งคํ์ ํตํด ์ฐพ์ ํธ๋ค๋ฌ๋ฅผ ์คํํ ์ด๋ํฐ๊ฐ ํ์ํ๋ค.
ex) โญRequestMappingHandlerAdapter : @RequestMapping์์ ์ฌ์ฉ
HttpRequestHandlerAdapter : HttpRequestHandler ์ฒ๋ฆฌ
SimpleControllerHandlerAdapter : Controller ์ธํฐํ์ด์ค์์ ์ฌ์ฉ (@Controller ์๋)
- ๋ชจ๋ RequestMappingHandlerMapping, RequestMappingHandlerAdapter๊ฐ ์ฐ์ ์์๊ฐ ๋๋ค.
๐ฉ [RequestMappingHandlerMapping.java]
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
- ์ฝ๋๋ฅผ ๋ณด๋ฉด ์ด๋ฐ ์์ผ๋ก ์ด๋ ธํ ์ด์ ์ ํด๋์ค๋ฅผ ๋ฐ์ง๋ ๋ถ๋ถ์ด ์๋ค!
๐ฉ ํธ๋ค๋ฌ ๋งคํ์ผ๋ก ํธ๋ค๋ฌ ์กฐํ -> ํธ๋ค๋ฌ ์ด๋ํฐ ์กฐํ -> ํธ๋ค๋ฌ ์ด๋ํฐ ์คํ
โ ViewResolver (๋ทฐ ๋ฆฌ์กธ๋ฒ)
- ์คํ๋ง ๋ถํธ๋ InternalResourceViewResolver๋ผ๋ ๋ทฐ ๋ฆฌ์กธ๋ฒ๋ฅผ ์๋์ผ๋ก ๋ฑ๋กํ๋๋ฐ,
์ด๋ application.yml (ํ๋กํผํฐ)์ ๋ฑ๋กํ spring.mvc.view.prefix / suffix ์ค์ ์ ๋ณด๋ฅผ ์ฌ์ฉํด์ ๋ฑ๋กํ๋ค.
(๋ณดํต prefix์ /WEB-INF/views/๋ฅผ, suffix์ .jsp๋ฅผ ๋์ด์ jsp๋ฅผ ์ฌ์ฉํ๋ ํธ์ด๋ค)
ex) BeanNameViewResolver : ๋น ์ด๋ฆ์ผ๋ก ๋ทฐ๋ฅผ ์ฐพ์์ ๋ฐํํ๋ค
InternalResourceViewResolver : JSP๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ ๋ทฐ๋ฅผ ๋ฐํํ๋ค.
๐ฉ ํธ๋ค๋ฌ ์ด๋ํฐ๋ก ๋ ผ๋ฆฌ ๋ทฐ ์ด๋ฆ ํ๋ -> ๋ทฐ ๋ฆฌ์กธ๋ฒ ํธ์ถ -> ๋ฐํ๋ ๋ทฐ์ render() ํธ์ถ
- ์ฌ๊ธฐ์ JSP์ ๊ฒฝ์ฐ (InternalResourceViewResolver) forward()๋ฅผ ํตํด์ JSP๋ฅผ ์คํ ํ ๋ ๋๋ง์ด ์งํ๋๋ค.
- ๋๋จธ์ง์ ๊ฒฝ์ฐ forward ํ์ ์์ด ๋ฐ๋ก ๋ ๋๋ง์ด ๋๋ค.
- ์ถ๊ฐ์ ์ผ๋ก, JSTL ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์ผ๋ฉด JstlView๋ฅผ ๋ฐํํ๋ฉฐ, ์ฝ๊ฐ์ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ์กฐ๊ธ ๋ ๊ฐ์ง๊ณ ์๋ค.
- ํ์๋ฆฌํ ์ฌ์ฉ ์ ThymeleafViewResolver๋ฅผ ์คํ๋ง ๋ถํธ๊ฐ ์๋์ผ๋ก ๋ฑ๋กํด์ค๋ค.
| Spring MVC
- ์คํ๋ง์ด ์ ๊ณตํ๋ ์ปจํธ๋กค๋ฌ๋ ์ด๋ ธํ ์ด์ ์ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๋ค!
โ @RequestMapping
- ํธ๋ค๋ฌ ๋งคํ์ผ๋ก RequestMappingHandlerMapping, ํธ๋ค๋ฌ ์ด๋ํฐ๋ก RequestMappingHandlerAdapter๋ฅผ ์ฌ์ฉํ๋ค.
- ์ง๋ ํฌ์คํ ์ ์ฌ์ฉํ์๋ ์์ ๋ค์ SpringMVC์์ ์ ๊ณตํ๋ ๋ฐฉ์๋ค๋ก ๋ณ๊ฒฝํด๋ณด์.
[application.properties]
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
[MemberFormController.java]
@Controller
public class SpringMemberFormController {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
- @Controller๋ฅผ ํตํด ์๋์ผ๋ก ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก์ ํด์ฃผ์๋ค.
- @RequestMapping์ ํตํด ํด๋น URI๋ก ๋ค์ด์จ ์์ฒญ์ ํด๋น ๋ฉ์๋๊ฐ ์ฒ๋ฆฌํ ์ ์๋๋ก ํ๋ค.
- Model๊ณผ View ์ ๋ณด๋ฅผ ๋ด์ ModelAndView๋ฅผ ๋ฐํํ๋ค.
๐ฉ RequestMappingHandlerMapping์ ์คํ๋ง ๋น ์ค์์ @RequestMapping๊ณผ @Controller๊ฐ ํด๋์ค ๋ ๋ฒจ์ ๋ถ์ด ์๋ ๊ฒฝ์ฐ ๋งคํ ์ ๋ณด๋ก ์ธ์ํ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์, @Controller ๋์ ์ @Component๊ณผ @RequestMapping์ ํด๋์ค ๋ ๋ฒจ์ ๋ถ์ฌ์ค๋ ๋๋ค!
- ํน์, ์คํ๋ง ๋น์ผ๋ก ์ง์ ๋ฑ๋กํด๋ ๋๋ค (@Bean ์ด์ฉ) - ํ์ง๋ง... ๊ท์ฐฎ์ผ๋๊น.
[SpringMemberSaveController.java]
@Controller
public class SpringMemberSaveController {
private final SMemberRepository repository
= SMemberRepository.getRepository();
@RequestMapping("/springmvc/v1/members/save")
public ModelAndView process(HttpServletRequest req, HttpServletResponse resp) {
String username = req.getParameter("username");
int age = Integer.parseInt(req.getParameter("age"));
SMember member = new SMember(username, age);
repository.save(member);
ModelAndView mv = new ModelAndView("save-result");
mv.addObject("member", member);
return mv;
}
}
- model์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ๋๋ ModelAndView์ addObject๋ฅผ ์ฌ์ฉํด์ฃผ๋ฉด ๋๋ค!
[SpringMemberListController.java]
@Controller
public class SpringMemberListController {
private final SMemberRepository repository
= SMemberRepository.getRepository();
@RequestMapping("/springmvc/v1/members")
public ModelAndView process() {
List<SMember> members = repository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return mv;
}
}
- ๋ง์ฐฌ๊ฐ์ง๋ก ์คํํด๋ณด๋ฉด ๋งค์ฐ ์ ๋์ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
- ๊ทธ๋ฌ๋, ์ด๋ฐ ์์ผ๋ก ํ์ผ์ด ๋ง์ด ์กด์ฌํ๋ ๊ฑด ์๋นํ ๋ถํธํ๋ค.
- ํ๋์ ํ์ผ์๋ค๊ฐ ์ฎ๊ฒจ๋ณด์.
[SpringMemberController.java]
@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberController {
private final SMemberRepository repository
= SMemberRepository.getRepository();
@RequestMapping("/new-form")
public ModelAndView newForm() {
return new ModelAndView("new-form");
}
@RequestMapping("/save")
public ModelAndView save(HttpServletRequest req, HttpServletResponse resp) {
String username = req.getParameter("username");
int age = Integer.parseInt(req.getParameter("age"));
SMember member = new SMember(username, age);
repository.save(member);
ModelAndView mv = new ModelAndView("save-result");
mv.addObject("member", member);
return mv;
}
@RequestMapping()
public ModelAndView members() {
List<SMember> members = repository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return mv;
}
}
- ์ด๋ฐ ์์ผ๋ก ํ๋์ ํ์ผ์๋ค๊ฐ ์ค์ผ ์ ์๋ค. ํนํ, ์ค๋ณต๋๋ ๊ฒฝ๋ก๋ ํด๋์ค ๋จ์์, ์ธ๋ถ ๊ฒฝ๋ก๋ ๋ฉ์๋ ๋จ์์ ๋ฌ์์ฃผ๋ฉด ๋๋ค :D
- ๊ทธ๋ฌ๋, ์ด ์ฝ๋๋ ๋ ๋ค์ ModelAndView๋ฅผ ๊ฐ๋ฐ์๊ฐ ์ง์ ์์ฑํด์ค์ผ ํ๋ค๋ ๋จ์ ์ด ์๋ค.
- ์ด๋ฅผ ๋ ์ค์ฌ๋ณด์!
[SpringMemberController.java] - ์์
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberController extends RequestMappingHandlerMapping {
private final SMemberRepository repository
= SMemberRepository.getRepository();
@GetMapping("/new-form")
public String newForm() {
return "new-form";
}
@PostMapping("/save")
public String save(@RequestParam("username") String username,
@RequestParam("age") int age,
Model model) {
SMember member = new SMember(username, age);
repository.save(member);
model.addAttribute("member", member);
return "save-result";
}
@GetMapping
public String members(Model model) {
List<SMember> members = repository.findAll();
model.addAttribute("members", members);
return "members";
}
}
Model์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๊ณ , ๋ทฐ์ ๋ ผ๋ฆฌ ์ด๋ฆ์ ๋ฐํํ๋ค.
๋ํ, ์์ฒญ ํ๋ผ๋ฏธํฐ๋ฅผ @RequestParam์ผ๋ก ๋ฐ์ ์ ์๋๋ฐ, @RequestParam("username") = HttpServletRequest.getParameter("username")์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
์ถ๊ฐ์ ์ผ๋ก, ๋ฉ์๋ ๋จ์์์ ๋จ์ํ @RequestMapping์ ์ฌ์ฉํ์ง ์๊ณ @PostMapping / @GetMapping์ ์ฌ์ฉํ์๋ค.
๊ธฐ์กด์ requestMapping(method="")์ method๋ฅผ ํตํด Http Method๋ฅผ ๊ตฌ๋ถ์ง์ ์ ์์์ง๋ง, ๋ ํธ๋ฆฌํ๊ฒ ์ด๋ ธํ ์ด์ ์ผ๋ก ๋ถ๋ฆฌ ๊ฐ๋ฅํ๋ค. @GetMapping์ ๊ฒฝ์ฐ Http Method๊ฐ Get์ด๋ฉด์ ํด๋น URI์ ๊ฐ์ง๋ ์์ฒญ์ ๋ฐ๋๋ค.
์ฌ๋ฌ ๊ฐ์ง ๋ ํผ๋ฐ์ค ๋ณด๋ค๊ฐ ๊ฐ์๊ธฐ ์๊ฐ๋์ ์ ๋ ๊ฑฐ...
๐ HTTP API vs REST API
- Http API๋ HTTP๋ฅผ ์ฌ์ฉํ์ฌ ์ ํด๋ ์คํ์ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ผ๋ฉฐ ํต์ ํ๋ ๊ฒ.
- REST๋ ์ฌ๊ธฐ์ 4๊ฐ์ง์ ์ ์ฝ ์กฐ๊ฑด์ ์ถ๊ฐํ ๊ฒ.
1) ์์์ ์๋ณ
- ์์ฒญ ๋ด์ ๊ธฐ์ ๋ ์์์ ์๋ณํ ์ ์์ด์ผ ํ๋ฉฐ, URI์ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ทธ ์์์ด๋ค.
- ์๋ฒ๋ DB์ ์๋ฃ๋ฅผ HTML, JSON ๊ฐ์ ํ์์ผ๋ก ์ ์กํ๋ค.
2) ๋ฉ์์ง๋ฅผ ํตํ ๋ฆฌ์์ค ์กฐ์
- ํด๋ผ์ด์ธํธ๊ฐ ์์์ ์ง์นญํ๋ ๋ฉ์์ง์ ๋ฉํ๋ฐ์ดํฐ๋ง ์์ผ๋ฉด ์๋ฒ ์์ ์์์ ๋ณ๊ฒฝ ๋ฐ ์ญ์ ํ ์ ์๋ค.
3) ์๊ธฐ์์ ์ ๋ฉ์์ง
- ๊ฐ ๋ฉ์์ง๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌํด์ผ ํ๋์ง์ ๋ํ ์ ๋ณด๋ฅผ ํฌํจํด์ผ ํ๋ค.
์ธํฐ๋ท ๋ฏธ๋์ด ํ์ ์ด๋ผ๋ฉด ์ด๋ ํ ํ์๋ฅผ ์จ์ผ ํ๋์ง์ ๋ํ ์ ๋ณด๋ ๋ค์ด๊ฐ์ผ ํ๋ค.
4) ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ์ ๋ํ ์์ง์ผ๋ก์ ํ์ดํผ๋ฏธ๋์ด
- ํด๋ผ์ด์ธํธ๊ฐ ๋ฆฌ์์ค์ ์ ๊ทผํ๊ณ ์ถ๋ค๋ฉด, ๋ฆฌํด๋๋ ์ง์์์์ ๊ตฌ๋ณํ ์ ์์ด์ผ ํ๋ค.
- HTML ๊ฐ์ ํ์ดํผ๋งํฌ๊ฐ ์ถ๊ฐ๋์ด, ๋ค์์ ์ด๋ค API๋ฅผ ํธ์ถํด์ผ ํ๋์ง ๋งํฌ๋ฅผ ํตํด ๋ฐ์ ์ ์์ด์ผ ํ๋ค๋ ์ .
- ์ด๋ฌํ 4๊ฐ์ง ์ ์ฝ ์กฐ๊ฑด์ ๋ค ์งํจ ๊ฒ RESTful API์ง๋ง, ์ค๋ฌด์์๋ ์ด๋ ๊ฒ ๋ค ๋ง์กฑํ๊ธฐ ์ด๋ ต๋ค.
- ๊ทธ๋์ ์์ฆ์ ๋๋ถ๋ถ HTTP API๋ฅผ ๊ทธ๋ฅ Restful API์ ๋์ผํ๊ฒ ์๊ฐํ๋ค๊ณ ํ๋ค...!
- ์๋ฌดํผ, ๋ค์ ํฌ์คํ ์์๋ ๋ณธ๊ฒฉ์ ์ผ๋ก ์คํ๋ง MVC์์ ์ ๊ณตํ๋ ๋ค์ํ ์ด๋ ธํ ์ด์ ๊ณผ HTTP ์์ฒญ, ์๋ต ๋ฉ์์ง์ ๋ํด ์์๋ณด์.