DevLog ๐Ÿ˜ถ

[Spring] DispatcherServlet์˜ ๋™์ž‘ ๊ณผ์ •๊ณผ HanderMapping, HandlerAdapter, @RequestMapping ๋ณธ๋ฌธ

Back-end/Spring

[Spring] DispatcherServlet์˜ ๋™์ž‘ ๊ณผ์ •๊ณผ HanderMapping, HandlerAdapter, @RequestMapping

dolmeng2 2022. 8. 16. 19:01

- ๊น€์˜ํ•œ ๋‹˜์˜ '์Šคํ”„๋ง MVC 1ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ•ต์‹ฌ ๊ธฐ์ˆ '์„ ๋ณด๊ณ  ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค ๐Ÿ˜Š

 

์Šคํ”„๋ง MVC 1ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ•ต์‹ฌ ๊ธฐ์ˆ  - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ๋•Œ ํ•„์š”ํ•œ ๋ชจ๋“  ์›น ๊ธฐ์ˆ ์„ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ์ดํ•ดํ•˜๊ณ , ์™„์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํ”„๋ง MVC์˜ ํ•ต์‹ฌ ์›๋ฆฌ์™€ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๊ณ , ๋” ๊นŠ์ด์žˆ๋Š” ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋กœ ์„ฑ์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค., -

www.inflearn.com


- ์ง€๋‚œ ํฌ์ŠคํŒ…๊ณผ ์ด์–ด์ง‘๋‹ˆ๋‹ค :D

 

[Spring] MVC ํŒจํ„ด์ด๋ž€, V1~V5๋กœ MVC ํ”„๋ ˆ์ž„์›Œํฌ ๋งŒ๋“ค๊ธฐ

- ๊น€์˜ํ•œ ๋‹˜์˜ '์Šคํ”„๋ง MVC 1ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ•ต์‹ฌ ๊ธฐ์ˆ '์„ ๋ณด๊ณ  ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค ๐Ÿ˜Š ์Šคํ”„๋ง MVC 1ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ•ต์‹ฌ ๊ธฐ์ˆ  - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ๋•Œ ํ•„์š”ํ•œ ๋ชจ๋“  ์›น ๊ธฐ

cl8d.tistory.com

- ์ง€๋‚œ ๊ฐ•์˜์—์„œ 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 ์š”์ฒญ, ์‘๋‹ต ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž.

 

Comments