Java Category/Spring

[Spring MVC] 스프링 MVC - 구조 이해

ReBugs 2024. 2. 18.

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.

2024.02.17 - [Java Category/JSP] - JSP와 서블릿을 이용한 MVC 프레임워크 만들기

 

JSP와 서블릿을 이용한 MVC 프레임워크 만들기

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다. Model, View, Controller 컨트롤러: HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. 그

rebugs.tistory.com

위 글과 이어진 내용입니다.


왼쪽 : JSP로 만든 MVC프레임워크, 오른쪽 : 스프링 MVC프레임워크

 

JSP로 만든 MVC 프레임워크와 스프링 MVC 프레임워크의 구조가 매우 비슷하다는 것을 알 수 있다.

 

핵심은 FrontController와 DispatcherServlet이다.

JSP로 만든 프레임워크와 스프링 MVC 비교
FrontController
DispatcherServlet
handlerMappingMap
HandlerMapping
MyHandlerAdapter
HandlerAdapter
ModelView
ModelAndView
viewResolver
ViewResolver
MyView
View

 

DispatcherServlet

org.springframework.web.servlet.DispatcherServlet

스프링 MVC도 프론트 컨트롤러 패턴으로 구현되어 있다.
스프링 MVC의 프론트 컨트롤러가 바로 디스패처 서블릿(DispatcherServlet)이다. 그리고 이 디스패처 서블릿이 바로 스프링 MVC의 핵심이다.

DispatcherServlet 서블릿 등록

DispatcherServlet 도 부모 클래스에서 HttpServlet 을 상속 받아서 사용하고, 서블릿으로 동작한다.

DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet

스프링 부트는 DispatcherServlet 을 서블릿으로 자동으로 등록하면서 모든 경로( urlPatterns="/")에 대해서 매핑한다.

모든 경로 ( urlPatterns="/" )에 대해서 매핑
더 자세한 경로가 우선순위가 높다. 그래서 기존에 등록한 서블릿도 함께 동작한다.

 

요청 흐름

서블릿이 호출되면 HttpServlet 이 제공하는 serivce() 가 호출된다.

스프링 MVCDispatcherServlet 의 부모인 FrameworkServlet 에서 service() 를 오버라이드 해 두었다.

FrameworkServlet.service() 를 시작으로 여러 메서드가 호출되면서 DispatcherServlet.doDispatch() 가 호출된다.

 

DispatcherServlet 의 핵심인 doDispatch() 코드를 분석해보자. 최대한 간단히 설명하기 위해 예외 처리, 인터셉터 기능은 제외했다.

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        ModelAndView mv = null;
        // 1. 핸들러 조회
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }

        // 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        // 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
     private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
         // 뷰 렌더링 호출
        render(mv, request, response);
    }
    
     protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
     
     View view;
     String viewName = mv.getViewName();
     
     // 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
     view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
     // 8. 뷰 렌더링
     view.render(mv.getModelInternal(), request, response);
 }

 

요청의 전처리

DispatcherServlet이 요청을 받으면, 등록된 여러 HandlerMapping들 중에서 해당 요청을 처리할 수 있는 HandlerMapping을 찾는다.

HandlerMapping은 URL, HTTP 메소드 등 요청의 정보를 기반으로 적절한 핸들러(컨트롤러) 객체를 결정한다.

핸들러 선택

요청에 매핑된 핸들러 객체가 결정되면, 해당 핸들러를 실행할 수 있는 HandlerAdapter를 찾는다.

HandlerAdapter는 다양한 타입의 컨트롤러를 지원할 수 있도록 해준다.

예를 들어, RequestMappingHandlerAdapter는 @RequestMapping 어노테이션이 붙은 메소드를 처리할 수 있다.

 

핸들러 실행

적절한 HandlerAdapter를 통해 핸들러(컨트롤러 메소드)가 실행된다. 이 과정에서 핸들러 메소드의 입력 파라미터는 요청에서 추출하거나 변환된 값이 바인딩되고, 핸들러 메소드의 실행 결과로 ModelAndView 객체가 반환된다.

ModelAndView는 뷰 이름과 뷰를 렌더링할 때 사용할 모델 데이터를 포함한다.

 

뷰 해석

컨트롤러가 반환한 뷰 이름을 바탕으로 ViewResolver를 사용해 실제 뷰 객체를 찾는다.

예를 들어, InternalResourceViewResolver는 JSP 파일 경로를 해석하여 View 객체를 생성한다.

 

뷰 렌더링

찾아진 View 객체는 모델 데이터를 렌더링하는 과정을 거쳐 최종적인 응답을 생성한다. 이 응답은 클라이언트에게 전송된다.

 

 

동작 순서

  1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
  2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
  3. 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다.
  4. 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행한다.
  5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
  6. viewResolver 호출 : 뷰 리졸버를 찾고 실행한다.
  7. View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다. 
  8. 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링 한다.

 

핸들러 매핑과 핸들러 어댑터

 

과거 버전 스프링 컨트롤러 - Controller 인터페이스

org.springframework.web.servlet.mvc.Controller

public interface Controller {
     ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;

}

스프링도 처음에는 이러한 형식의 컨트롤러를 제공했다.

Controller 인터페이스는 @Controller 애노테이션과는 전혀 다르다.

 

OldController 클래스

package hello.servlet.web.springmvc;

//과거 버전 스프링 컨트롤러

//springmvc/old-controller 라는 이름의 스프링 빈으로 등록
//빈의 이름으로 URL을 매핑
@Component("/springmvc/old-controller")
public class OldController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return new ModelAndView("new-form");
    }
}
@Component 는 스프링 프레임워크에서 제공하는 어노테이션 중 하나로, 클래스를 스프링의 빈(bean)으로 등록하기 위해 사용된다.

@Component : 이 컨트롤러는 /springmvc/old-controller 라는 이름의 스프링 빈으로 등록되었다.

빈의 이름으로 URL을 매핑한다.

즉, http://localhost:8080/springmvc/old-controller 에 접속하면 해당 컨트롤러가 실행된다.

 

이 컨트롤러가 호출되려면 다음 2가지가 필요하다.

  • HandlerMapping(핸들러 매핑)
    -핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 한다.
    -예) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
  • HandlerAdapter(핸들러 어댑터)
    -핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.
    -예) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.

스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해두었다. 개발자가 직접 핸들러 매핑과 핸들러 어댑터를 만드는 일은 거의 없다.

 

스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터(실제로는 더 많음)

핸들러 매핑도, 핸들러 어댑터도 모두 아래의 순서대로 찾고 만약 없으면 다음 순서로 넘어간다.

HandlerMapping

순위 핸들러 매핑 설명
0 RequestMappingHandlerMapping 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 BeanNameUrlHandlerMapping 스프링 빈의 이름으로 핸들러를 찾는다.


HandlerAdapter

순위 핸들러 어댑터 설명
0 RequestMappingHandlerAdapter 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 HttpRequestHandlerAdapter HttpRequestHandler 처리
2 SimpleControllerHandlerAdapter Controller 인터페이스(애노테이션X, 과거에 사용) 처리
  1. 핸들러 매핑으로 핸들러 조회
    1. HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
    2. 이경우 빈 이름으로 핸들러를 찾아야하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 OldController 를 반환한다.
  2.  핸들러 어댑터 조회
    1. HandlerAdapter 의 supports() 를 순서대로 호출한다.
    2. SimpleControllerHandlerAdapter 가 Controller 인터페이스를 지원하므로 대상이 된다.
  3. 핸들러 어댑터 실행
    1. 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
    2. SimpleControllerHandlerAdapter 는 핸들러인 OldController 를 내부에서 실행하고, 그 결과를 반환한다.

OldController 를 실행하면서 사용된 객체는 다음과 같다.

- HandlerMapping = BeanNameUrlHandlerMapping

- HandlerAdapter = SimpleControllerHandlerAdapter

 

HttpRequestHandler 컨트롤러

HttpRequestHandler 핸들러(컨트롤러)서블릿과 가장 유사한 형태의 핸들러이다.

 

HttpRequestHandler

public interface HttpRequestHandler {
     void handleRequest(HttpServletRequest request, HttpServletResponse response)
}

 

 

MyHttpRequestHandler

@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("MyHttpRequestHandler.handleRequest");
    }
}

http://localhost:8080/springmvc/request-handler 이 경로로 접속하면 해당 컨트롤러가 실행된 것이다.

 

  1. 핸들러 매핑으로 핸들러 조회
    1. HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
    2. 이 경우 빈이름으로 핸들러를 찾아야하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 MyHttpRequestHandler 를 반환한다.
  2. 핸들러 어댑터 조회
    1. HandlerAdapter 의 supports() 를 순서대로 호출한다.
    2. HttpRequestHandlerAdapter 가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.
  3. 핸들러 어댑터 실행
    1. 디스패처 서블릿이 조회한 HttpRequestHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
    2. HttpRequestHandlerAdapter 는 핸들러인 MyHttpRequestHandler 를 내부에서 실행하고, 그 결과를 반환한다.

 

MyHttpRequestHandler 를 실행하면서 사용된 객체는 다음과 같다.

-HandlerMapping = BeanNameUrlHandlerMapping

-HandlerAdapter = HttpRequestHandlerAdapter

 

 

뷰 리졸버

@Component("/springmvc/old-controller")
public class OldController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return new ModelAndView("new-form");
    }
}

이 코드를 실행하면 논리적 경로인 "new-form"가 뷰 리졸버로 전달되고, 뷰 리졸버(View Resolver)의 주된 역할은 컨트롤러가 반환한 뷰 이름을 바탕으로 실제 뷰의 경로를 찾아내고, 이를 뷰 객체에 전달하여 최종적으로 렌더링할 수 있도록 하도록 한다.

이 상황에서는 뷰 리졸버가 실제 뷰의 경로를 찾을 수 없기 때문에 발생하는 오류이다.

 

application.properties에 아래의 코드를 추가시키면 뷰 리졸버가 실제 뷰의 경로를 찾을 수 있게 해준다.

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

 

InternalResourceViewResolver - JSP 처리 뷰

스프링 부트는 InternalResourceViewResolver 라는 뷰 리졸버를 자동으로 등록하는데, 이때 application.properties 에 등록한 spring.mvc.view.prefix , spring.mvc.view.suffix 설정 정보를 사용해서 등록한다.

참고로 권장하지는 않지만 설정 없이 다음과 같이 전체 경로를 주어도 동작하기는 한다.
return new ModelAndView("/WEB-INF/views/new-form.jsp");

 

스프링 부트가 자동 등록하는 뷰 리졸버(실제로는 더 많음)

순위
리졸버
설명
1
BeanNameViewResolver
빈 이름으로 뷰를 찾아서 반환한다. (: 엑셀 파일 생성 기능 에 사용)
2
InternalResourceViewResolver
JSP를 처리할 수 있는 뷰를 반환한다.

 

  1. 핸들러 어댑터 호출
    -핸들러 어댑터를 통해 new-form 이라는 논리 뷰 이름을 획득한다.
  2. ViewResolver 호출
    -new-form이라는 뷰 이름으로 viewResolver를 순서대로 호출한다.
    -BeanNameViewResolver 는 new-form 이라는 이름의 스프링 빈으로 등록된 뷰를 찾아야 하는데 없다.
    -그래서 InternalResourceViewResolver 가 호출된다.
  3. InternalResourceViewResolver
    -이 뷰 리졸버는 InternalResourceView 를 반환한다.
  4. 뷰 - InternalResourceView
    -InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용한다.
  5. view.render()
    -view.render()가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다.

 

참고
다른 뷰는 실제 뷰를 렌더링하지만, JSP의 경우 forward() 통해서 해당 JSP로 이동(실행)해야 렌더링이 된다. JSP를 제외한 나머지 뷰 템플릿들은 forward() 과정 없이 바로 렌더링 된다.


Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver 를 등록해야 한다. 최근에는 라이브러리만 추 가하면 스프링 부트가 이런 작업도 모두 자동화해준다.

 

 

스프링 MVC로 리팩토링

@Controller, @RequestMapping

@RequestMapping

  • RequestMappingHandlerMapping -> 핸들러 맵핑
  • RequestMappingHandlerAdapter -> 핸들러 어댑터

 

@Controller

클래스 레벨에서 사용되며, 해당 클래스가 웹 요청을 처리하는 컨트롤러임을 스프링에게 알린다.

-> RequestMappingHandlerMapping
@Controller가 붙은 클래스는 스프링 MVC에서 HTTP 요청을 처리하는 데 필요한 여러 핸들러 메소드를 포함할 수 있으며, 스프링의 컴포넌트 스캔 기능에 의해 자동으로 탐지되어(내부에 @Component 있음) 스프링 애플리케이션 컨텍스트에 빈으로 등록된다.

 

@RequestMapping

어노테이션은 메소드 레벨에서 사용되며, 특정 HTTP 요청(URL 경로 및 HTTP 메소드)을 컨트롤러 내의 메소드에 매핑한다.
이를 통해 해당 메소드가 어떤 요청을 처리할 책임이 있는지를 정의한다.
@RequestMapping은 path또는 value와 method 속성을 통해 요청을 매핑하며, 다양한 속성을 제공하여 요청을 세밀하게 처리할 수 있다.
@RequestMapping은 클래스 레벨에도 사용될 수 있으며, 이 경우 해당 클래스 내의 모든 메소드에 공통적으로 적용되는 기본 URL 경로를 정의한다.

애노테이션을 기반으로 동작하기 때문에, 메서드의 이름은 임의로 지으면 된다.

 

정리

@Controller는 클래스가 웹 요청을 처리하는 컨트롤러임을 선언하는 데 사용되며, @RequestMapping은 해당 컨트롤러 내의 특정 메소드가 처리할 요청을 매핑하는 데 사용된다.
@Controller는 컨트롤러의 역할을 정의하는 반면, @RequestMapping은 컨트롤러 내에서 특정 요청을 어떻게 처리할지를 세부적으로 지정한다.
둘은 함께 사용되어 스프링 MVC에서 효율적으로 요청을 처리하고 응답을 생성하는 구조를 만든다.

 

SpringMemberFormControllerV1 - 회원 등록 폼

package hello.servlet.web.springmvc.old.v1;

import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller //스프링이 자동으로 스프링 빈으로 등록한다. (내부에 @Component 애노테이션이 있어서 컴포넌트 스캔의 대상이 됨)
public class SpringMemberFormControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members/new-form") //해당 URL이 호출되면 이 메서드가 호출된다. 애노테이션을 기반으로 동작하기 때문에, 메서드의 이름은 임의로 지으면 된다.
    public ModelAndView process(){
        return new ModelAndView("new-form"); //모델과 뷰 정보를 담아서 반환
    }
}

 

http://localhost:8080/springmvc/v1/members/new-form 에 들어가면 정상적으로 작동하는 것을 확인할 수 있다.

 

SpringMemberSaveControllerV1 - 회원 저장

package hello.servlet.web.springmvc.old.v1;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SpringMemberSaveControllerV1 {
    private MemberRepository memberRepository = MemberRepository.getInstance();
    @RequestMapping("/springmvc/v1/members/save")
    public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        Member member = new Member(username, age);

        System.out.println("member = " + member);
        memberRepository.save(member);

        ModelAndView mv = new ModelAndView("save-result");
        mv.addObject("member", member); //스프링이 제공하는 ModelAndView 를 통해 Model 데이터를 추가할 때는 addObject() 를 사용하면 된다. 이 데이터는 이후 뷰를 렌더링 할 때 사용된다.
        return mv;
    }
}

mv.addObject("member", member)
스프링이 제공하는 ModelAndView 를 통해 Model 데이터를 추가할 때는 addObject() 를 사용하면 된다. 이 데이터는 이후 뷰를 렌더링 할 때 사용된다.

 

SpringMemberListControllerV1 - 회원 목록

package hello.servlet.web.springmvc.old.v1;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
public class SpringMemberListControllerV1 {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members")
    public ModelAndView process() {
        List<Member> members = memberRepository.findAll();
        ModelAndView mv = new ModelAndView("members");
        mv.addObject("members", members);
        return mv;
    }
}

 

컨트롤러 통합

@RequestMapping 을 잘 보면 클래스 단위가 아니라 메서드 단위에 적용된 것을 확인할 수 있다. 따라서 컨트롤러 클래스를 유연하게 하나로 통합할 수 있다.

package hello.servlet.web.springmvc.v2;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
/**
 * 클래스 단위 -> 메서드 단위
 * @RequestMapping 클래스 레벨과 메서드 레벨 조합
 */
@Controller
@RequestMapping("/springmvc/v2/members") //클래스 단위 -> 기본 경로
public class SpringMemberControllerV2 {
    private MemberRepository memberRepository = MemberRepository.getInstance();
    @RequestMapping("/new-form")
    public ModelAndView newForm() {
        return new ModelAndView("new-form");
    }
    
    @RequestMapping("/save")
    public ModelAndView save(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        Member member = new Member(username, age);
        memberRepository.save(member);
        ModelAndView mav = new ModelAndView("save-result");
        mav.addObject("member", member);
        return mav;
    }
    
    @RequestMapping //어노테이션에 추가적인 값을 할당하지 않으면 클래스 단위 기본 경로값으로 자동 지정
    public ModelAndView members() {
        List<Member> members = memberRepository.findAll();
        ModelAndView mav = new ModelAndView("members");
        mav.addObject("members", members);
        return mav;
    }
}

 

클래스 레벨에 다음과 같이 @RequestMapping 을 두면 메서드 레벨과 조합이 된다.

@Controller
@RequestMapping("/springmvc/v2/members")
 public class SpringMemberControllerV2 {}

클래스 레벨 @RequestMapping("/springmvc/v2/members")

  • 메서드 레벨 @RequestMapping("/new-form") -> /springmvc/v2/members/new-form
  • 메서드 레벨 @RequestMapping("/save") -> /springmvc/v2/members/save
  • 메서드 레벨 @RequestMapping -> /springmvc/v2/members

 

실용적인 방식

package hello.servlet.web.springmvc.v3;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/** * v3
 * Model 도입
 * ViewName 직접 반환
 * @RequestParam 사용
 * @RequestMapping -> @GetMapping, @PostMapping
 */
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @GetMapping("/new-form")
    public String newForm() {
        return "new-form";
    }

    @PostMapping("/save")
    public String save(
            @RequestParam("username") String username,
            @RequestParam("age") int age,
            Model model) {
        Member member = new Member(username, age);
        memberRepository.save(member);
        model.addAttribute("member", member);
        return "save-result";
    }

    @GetMapping
    public String members(Model model) {
        List<Member> members = memberRepository.findAll();
        model.addAttribute("members", members);
        return "members";
    }
}

 

Model 파라미터

  • save() , members() 를 보면 Model을 파라미터로 받는 것을 확인할 수 있다. 스프링 MVC도 이런 편의 기능을 제공한다.

 

ViewName 직접 반환

  • 뷰의 논리 이름을 String 타입으로 반환할 수 있다.

 

@RequestParam 사용

  • 스프링은 HTTP 요청 파라미터를 @RequestParam 으로 받을 수 있다.
  • @RequestParam("username") 은 request.getParameter("username") 와 거의 같은 코드라 생각하면 된다.
    물론 GET 쿼리 파라미터, POST Form 방식을 모두 지원한다.  

 

@RequestMapping -> @GetMapping, @PostMapping

  • @RequestMapping 은 URL만 매칭하는 것이 아니라, HTTP Method도 함께 구분할 수 있다.
  • 예를 들어서 URL이 /new-form 이고, HTTP Method가 GET인 경우를 모두 만족하는 매핑을 하려면 다음과 같이 처리하면 된다.
@RequestMapping(value = "/new-form", method = RequestMethod.GET)
  • 이것을 @GetMapping , @PostMapping 으로 더 편리하게 사용할 수 있다.
  • 물론 Get, Post, Put, Delete, Patch 모두 어노테이션이 있다.

 

댓글