[스프링 MVC - 백엔드 웹 개발 기술] 11. 스프링 MVC 구조의 이해
by ymkim01. 스프링 MVC 전체 구조
- 직접 만든 MVC Framework와 스프링 MVC를 비교해보자
01-1. 직접 만든 MVC 프레임워크 구조
01-2. 스프링 MVC 구조
직접 만든 프레임워크 → 스프링 MVC와 비교
- FrontController → DispatcherServlet (FrontController Pattern)
- DispatcherServlet이 스프링에서 가장 중요한 개념 중 하나
- FrontController Pattern을 구현한 FrontController를 DispatcherServlet이라 지칭함
- handlerMappingMap → HandlerMapping
- MyHandlerAdapter → HandlerAdapter
- ModelView → ModelAndView
- viewResolver → ViewResolver
- MyView → View
01-3. DispatcherServlet 구조 살펴보기
org.springframework.web.servlet.DispatcherServlet
- 스프링 MVC도 프론트 컨트롤러 패턴으로 구현되어 있다
- 스프링 MVC의 프론트 컨트롤러가 바로 디스패처 서블릿(DispatcherServlet)이다
- 디스패처 서블릿이 스프링의 MVC의 핵심이다
01-4. DispatcherServlet 서블릿 등록
- DispatcherServlet 도 부모 클래스에서 HttpServlet 을 상속받아 사용하고, 서블릿으로 동작한다
- DispatcherServlet → FrameworkServlet → HttpServletBean → HttpServlet
- 스프링 부트(Spring Boot)는 DispatcherServlet을 서블릿으로 자동 등록하면서 모든 경로(”urlPatterns=”/”)에 대해서 매핑 한다
- 참고 : 더 자세한 경로가 우선순위가 높다. 그래서 기존에 등록한 서블릿도 함께 동작
- DispatcherServlet이 우선순위가 더 낮다
01-5. 요청 흐름
- 서블릿 호출 → HttpServlet이 제공하는 service()가 호출 된다
- 스프링 MVC는 DispatcherServlet의 부모인 FrameworkServlet에서 service() 를 오버라이드 해두었음
- FrameworkServlet.service() 를 시작으로 여러 메서드가 호출되면서, DispatcherServlet.doDispatch() 가 최종적으로 호출된다
지금부터 DispatcherServlet의 핵심인 doDispatch() 코드를 분석해보자.
최대한 간단히 설명하기 위해 예외처리, 인터셉터 기능은 제외함.
01-6. 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 (mapperHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 실행 -> 5. ModelAndView 실행
mv = ha.handle(processedRequest, response, mappedHandler, mv, dispatchException);
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);
}
01-7. Spring MVC 구조
동작 순서
- 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러) 조회
- 핸들러 어댑터 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회
- 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다
- 핸들러 실행 : 핸들러 어댑터가 실제 핸들러(컨트롤러) 실행한다
- ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환하여 반환
- viewResolver 호출 : 뷰 리졸버를 찾고 실행한다
- JSP의 경우 : InternalResourceViewResolver 가 자동으로 등록되고, 사용 된다
- View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 랜더링 역할을 하는 뷰 객체 반환
- JSP의 경우 : InternalResourceView(JstlView) 를 반환하는데, 내부에 forward() 있음
- 뷰 랜더링 : 뷰를 통해 뷰를 랜더링
인터페이스 살펴보기
- 스프링 MVC의 큰 강점은 DispatcherServlet 코드 변경없이, 기능 확장 및 변경이 가능. 지금까지 설명한 대부분을 확장 가능할 수 있게 인터페이스 제공
- 이 인터페이스만 구현해서 DispatcherServlet에 등록하면 나만의 컨트롤러 생성 가능
주요 인터페이스 목록
- 핸들러 매핑 : org.springframework.web.servlet.HandlerMapping
- 핸들러 어댑터 : org.springframework.web.servlet.HandlerAdapter
- 뷰 리졸버 : org.springframework.web.servlet.ViewResolver
- 뷰 : org.springframework.web.servlet.View
정리
- 스프링 MVC는 코드도 많고, 복잡해서 내부 구조 파악이 쉽지 않음
- 사실 나만의 컨트롤러를 만들일은 없음, 이미 다 만들어져 있음
- 지금은 전체적인 구조가 어떻게 되어 있는지만 이해하면 된다
02. 핸들러 매핑과 핸들러 어댑터
- 핸들러 매핑과 핸들러 어댑터 종류로 어떤 것들이 존재하는지 알아보자
- 지금은 사용 안하지만, 과거에 스프링이 주로 사용한 컨트롤러로 핸들러 매핑과 어댑터를 이해하자
- @Controller 어노테이션이 나오기 전에 사용한 방식
02-1. Controller 인터페이스
과거 버전 스프링 컨트롤러
// org.springframework.web.servlet.mvc.Controller
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
- 스프링도 처음에는 위와 같이 딱딱한 형식의 컨트롤러를 제공함
- Controller 인터페이스는 @Controller 어노테이션과는 전혀 무관함
간단하게 구현 해보자
02-1. OldController
package hello.servlet.web.springmvc.old;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component("/springmvc/old-controller") // bean의 이름을 /springmvc/old-controller로 지정
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("OldController.handleRequest");
return null;
}
}
- @Component
- 해당 컨트롤러를 /springmvc/old-controller 라는 이름의 스프링 빈으로 등록
- 빈의 이름으로 URL을 매핑할 것이다
실행
- http://localhost:8080/springmvc/old-controller
- OldController.handleRequest가 출력되면 끝이다
02-2. 해당 컨트롤러가 어떻게 호출이 될까?
해당 컨트롤러가 호출되려면 아래 2가지가 필요함
- HandlerMapping(핸들러 매핑)
- 핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 한다
- 예) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요함
- HandlerAdapter(핸들러 어댑터)
- 핸들러 매핑을 통해 찾은 핸들러(컨트롤러)를 실행할 수 있는 핸들러 어댑터가 필요하다
- 예) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다
- 스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현함
- 개발자가 직접 만들 이유가 없음
- 스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터
HanlderMapping
0 = RequestMappingHandlerMapping : 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다
- RequestMapping…xx
- BeanNameUrl…xxx
HandlerAdapter
0 = RequestMappingHandlerAdapter : 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스(어노테이션 x, 과거에 사용) 처리
- RequestMapping…xx
- HttpRequest…xx
- SimpleController…xx
핸들러 매핑, 핸들러 어댑터 모두 순서대로 찾고 만약 없으면 다음 순서로 넘어간다
1. 핸들러 매핑으로 핸들러 조회
- HandlerMapping을 순서대로 실행해서, 핸들러를 찾는다
- 이 경우 빈 이름으로 핸들러를 찾아야 하기에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping(스프링 빈 이름 기반)가 실행에 성공하고 핸들러인 OldController를 반환한다
2. 핸들러 어댑터 조회
- HandlerAdapter의 supports() 를 순서대로 호출한다
- @Override public boolean supports(Object handler) { return (handler instanceof ControllerV4); }
- SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원하므로 대상이 된다
3. 핸들러 어댑터 실행
- 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨준다
- SimpleControllerHandlerAdapter는 핸들러인 OldController를 내부에서 실행 후 결과를 반환한다
정리 - OldController 핸들러매핑, 어댑터
- OldController 를 실행하면서 사용된 객체는 아래와 같다
- HandlerMapping = BeanNameUrlHandlerMapping
- HandlerAdapter = SimpleControllerHandlerAdapter
HttpRequestHandler
package hello.servlet.web.springmvc.old;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@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
- 웹 브라우저에 빈 화면 나오고 MyHttpRequestHandler.handleRequest 나오면 성공
1. 핸들러 매핑으로 핸들러 조회
- HandlerMapping을 순서대로 실행, 핸들러를 찾는다
- 이 경우 빈 이름으로 핸들러를 찾아야 하기에 이름 그래도 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping이 실행에 성공하고 핸들러인 MyHttpRequestHandler를 반환한다
2. 핸들러 어댑터 조회
- HandlerAdapter의 supports() 를 순서대로 호출한다
- HttpRequestHandlerAdapter가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다
3. 핸들러 어댑터 실행
- 디스패처 서블릿이 조회한 HttpRequestHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘긴다
- HttpRequestHandlerAdapter는 핸들러인 MyHttpRequestHandler를 내부에서 실행하고, 그 결과를 반환
정리 - MyHttpRequestHandler 핸들러매핑, 어댑터
- MyHttpRequestHandler 를 실행하면서 사용된 객체는 다음과 같다
- HandlerMapping = BeanNameUrlHandlerMapping
- HandlerAdapter = HttpRequestHandlerAdapter
@RequestMapping
- 조금 뒤에 설명하겠지만, 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는 RequestMappingHandlerMapping, RequestMappingHandlerAdapter이다
- @RequestMapping 의 앞글자를 따서 만든 이름인데, 이것이 바로 지금 스프링에서 주로 사용하는 어노테이션 기반의 컨트롤러를 지원하는 매핑과 어댑터이다.
03. 뷰 리졸버
이번 시간에는 뷰 리졸버에 대해서 자세히 알아본다.
03-1. OldController - View 조회할 수 있도록 변경
package hello.servlet.web.springmvc.old;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@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");
}
}
- View를 사용할 수 있도록 아래 코드를 추가
- return new ModelAndView(”new-form”);
실행
- http://localhost:8080/springmvc/old-controller
- 웹 브라우저에 Whitelabel Error Page 출력 + OldController.handleRequest 이 출력됨
2023-09-28 18:19:12.070 INFO 10328 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet
: Initializing Servlet 'dispatcherServlet' 2023-09-28 18:19:12.071
INFO 10328 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet
: Completed initialization in 1 ms OldController.handleRequest <- 로깅 정보
- 실행 하면 컨트롤러는 정상 호출, Whitelabel Error Page 404 오류 발생
- 로그 상에는 OldController.handleRequest 텍스트가 정상 출력되는 것을 확인 할 수 있다
- 이러한 404 Not Found 오류를 막기위해서는 아래와 같은 설정이 잡혀야 한다
application.properties
# 404 Not Found를 방지하기 위한 Spring Boot 설정
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
prefix, suffix 등록 필요
SpringBoot는 application.properties에 등록된 설정 정보(spring.mvc.view…)를 기반으로 InternalResourceViewResolver라는 뷰 리졸버를 자동으로 등록한다
- 뷰 리졸버 - InternalResourceViewResolver
- 스프링 부트는 InternalResourceViewResolver 라는 뷰 리졸버를 자동으로 등록한다
- 이 때 application.properties에 등록한 설정 정보를 사용하여 등록한다
- spring.mvc.view.prefix
- spring.mvc.view.suffix
- 참고로 권장하지는 않지만 전체 경로를 주어도 동작은 한다
- return new ModelAndView(”/WEB-INF/views/new-form.jsp”);
내부적으로는 SpringBoot가 다음과 같이 처리한다
@ServletComponentScan // 서블릿 스캔 후 자동 등록
@SpringBootApplication
public class ServletApplication {
public static void main(String[] args) {
SpringApplication.run(ServletApplication.class, args);
}
@Bean
InternalResourceViewResolver internalResourceViewResolver() {
return new InternalResourceViewResolver("/WEB-INF/views/", ".jsp");
}
}
- 반환 타입 : InternalResourceViewResolver
- 반환 내용 : new InternalResourceViewResolver("/WEB-INF/views/", ".jsp");
03-2. 뷰 리졸버의 동작 방식
- 기존 방식 메커니즘은 동일(설명 중략..)
- ModelAndView를 반환하고 해당 정보를 통해 논리적인 이름(”new-form”)으로 viewResolver를 호출한다
- 스프링부트는 자동으로 여러 리졸버를 등록한다
뷰 리졸버 종류는 아래 2가지
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아 반환 (예: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다
다음으로는 ViewResolver가 호출되는 과정을 알아보자.
핸들러 어댑터의 호출 → view.render() 의 수행과정은 아래와 같다.
1. 핸들러 어댑터 호출
- 핸들러 어댑터를 통해 new-form 이라는 논리 뷰 이름 획득
- 핸들러 어댑터를 호출하게 되면 실제 핸들러를 호출한 후에 해당 데이터(Model)과 논리적인 뷰 이름(”new-form”)을 얻게 된다. 후에 해당 논리 이름을 통해 ViewResolver를 호출한다.
2. ViewResolver 호출
- new-form 이라는 뷰 이름으로 viewResolver를 순서대로(BeanNameView | Internal.. ) 호출
- BeanNameViewResolver : 빈 이름으로 view를 찾아 반환
- InternalResourceViewResolver : JSP를 처리할 수 있는 viewResolver 반환
- BeanNameViewResolver 는 new-form 이라는 빈 이름으로 등록된 뷰를 찾아야 하는데 없음
- 여기서는 InternalResourceViewResolver 가 호출된다
3. InternalResourceViewResolver
- 이 뷰 리졸버가 InternalResourceView를 반환
4. 뷰 - InternalResourceView
- InternalResourceView 는 JSP 처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우 사용
- 즉, InternalResourceView 는 forward() 방식으로 내부 로직 처리
5. view.render()
view.render()가 호출되고, InternalResourceView 는 forward() 사용해서 JSP 실행
참고 01. InternalResourceViewResolver 는 JSTL lib가 있으면 InternalResourceView를 상속받은 JstlView를 반환. JstlView → JSTL 태그 사용 → 약간의 부가기능 추가.
참고 02. 다른 뷰는 실제 뷰를 랜더링하지만, JSP의 경우 forward() 통해서 해당 JSP로 이동(실행)해야 랜더링이 됨. JSP 제외 나머지 뷰 템플릿은 forward() 과정 없이 바로 랜더링 한다
참고 03. Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver 를 등록해야함. 최근에는 lib만 추가하면 스프링 부트가 이런 작업 모두 자동화.
04. 스프링 MVC - 시작하기
- 스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작, 매우 유연하고 실용적임
- 과거에는 자바 언어에 애노테이션이 없었음, 스프링도 처음부터 이런 유연한 컨트롤러 제공한 것은 아님
04-1. @RequestMapping 등장
- 스프링은 애노테이션을 활용한 유연하고 실용적인 컨트롤러를 만들었음
- @RequestMapping 애노테이션을 사용하는 컨트롤러
- 과거에는 스프링 F/W가 MVC 부분이 약해서 MVC 기술은 스트럿츠 같은 F/W 사용
- @RequestMapping 기반 애노테이션 컨트롤러가 등장하면서 스프링의 완승으로 끝남
- @RequestMapping
- 위 어노테이션(@RequestMapping)이 있으면 아래 핸들러 매핑, 어댑터 사용
- 앞서 보았듯이 우선순위가 가장 높은 핸들러 매핑, 어댑터는 다음과 같다
- @RequestMappingHandlerMapping
- @RequestMappingHandlerAdapter
- 이것이 바로 스프링에서 주로 사용하는 애노테이션 기반의 컨트롤러를 지원하는 핸들렁 매핑, 어댑터
- 실무에서는 99.9% 위 방식의 컨트롤러 사용
이제 본격적으로 애노테이션(@RequestMapping) 기반의 컨트롤러를 만들어본다.
지금까지 만들었던 F/W에서 사용했던 컨트롤러를 @RequestMapping 기반으로 변경한다.
04-2. SpringMemberFormControllerV1 - 회원 등록 폼
package hello.servlet.web.springmvc.v1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
- @Controller
- 스프링이 자동으로 스프링 빈으로 등록 ( 내부에 @Component 어노테이션 존재, 자동 컴포넌트 스캔 )
- 스프링 MVC에서 어노테이션 기반 컨트롤러로 인식
- RequestMappingHandlerMapping에서 꺼내 사용할 수 있는 대상이 되도록 표기
- @RequestMapping
- 요청 정보 매핑하고, 해당 URL이 호출되면 해당 메서드가 호출 되도록 지정
- 어노테이션 기반으로 동작하기에 메서드 이름을 임의로 지으면 됨
- RequestMappingHandlerMapping은 스프링 빈 중에서 @RequestMapping 또는 @Controller가 클래스 레벨에 붙어 있는 경우 매핑 정보로 인식한다
RequestMappingHandlerMapping isHandler 함수
/**
* {@inheritDoc}
* <p>Expects a handler to have either a type-level @{@link Controller}
* annotation or a type-level @{@link RequestMapping} annotation.
*/
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
- 클래스 레벨에 Controller, RequestMapping이 붙어있는 경우만 인식
또한 다음과 같은 코드로 변경하여도 동일하게 동작한다.
@Component와 @RequestMapping 클래스 레벨에서 사용
@Component
@RequestMapping
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
- @Component + @RequestMapping 어노테이션 사용
@RequestMapping은 클래스 레벨 + Bean 수동 등록
@RequestMapping
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
@Bean
TestController testController() {
return new TestController();
}
- @Component 어노테이션 제거 후 직접 Bean 등록
@Bean vs @Component
@Bean | @Component |
메서드 레벨에 사용 | 클래스 레벨에 사용 |
개발자가 컨트롤 불가능한 외부 라이브러리 사용시 사용 (ex. PasswordEncoder by Security) |
개발자가 직접 컨트롤이 가능한 내부 클래스에 사용 |
수동 빈 등록 | 자동 빈 등록 |
04-3. SpringMemberSaveControllerV1
@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);
memberRepository.save(member);
ModelAndView mv = new ModelAndView("save-result");
mv.addObject("member", member);
return mv;
}
}
04-4. SpringMemberListControllerV1
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;
}
}
05. 스프링 MVC - 컨트롤러 통합
@RequestMapping을 보면 클래스 단위가 아니라 메서드 단위에 적용된 것을 확인 가능.
따라서 컨트롤러 클래스를 유연하게 하나로 통합 가능하다.
결론은 클래스 레벨에 @RequestMapping 선언하여 공통 URL 통합 시키는 부분
05-1. SpringMemberControllerV2
package hello.servlet.web.springmvc.v2;
...
@Controller
public class SpringMemberControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@RequestMapping("/springmvc/v2/members/new-form")
public ModelAndView newForm() {
return new ModelAndView("new-form");
}
@RequestMapping("/springmvc/v2/members/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 mv = new ModelAndView("save-result");
mv.addObject("member", member);
return mv;
}
@RequestMapping("/springmvc/v2/members")
public ModelAndView members() {
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return mv;
}
}
- @RequestMapping은 메서드 단위로 수행이 가능하기에 연관성을 가지는 API를 하나의 컨트롤러에 구성 가능
- 하지만 현재 중복이 발생한다. 이러한 코드는 아래와 같이 수정이 가능하다
- /springmvc/v2/members
@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 mv = new ModelAndView("save-result");
mv.addObject("member", member);
return mv;
}
@RequestMapping
public ModelAndView members() {
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return mv;
}
}
- 기존 중복된 URL 매핑을 메서드 레벨에서 제거하고 클래스 레벨에 지정한다
- @RequestMapping("/springmvc/v2/members")
06. 스프링 MVC - 실용적인 방식
- 스프링 MVC는 개발자가 편리하게 개발할 수 있도록 많은 기능을 제공
06-1. SpringMemberControllerV3
package hello.servlet.web.springmvc.v3;
...
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@RequestMapping("/new-form")
public String newForm() {
return "new-form";
}
@RequestMapping("/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";
}
@RequestMapping
public String members(Model model) {
List<Member> members = memberRepository.findAll();
model.addAttribute("members", members);
return "members";
}
}
- 스프링 MVC는 ModelAndView를 반환해도 되고, String(문자 view 이름)을 반환해도 된다
- 여기서는 ModelAndView를 반환 값으로 사용하지 않고 String을 반환 값으로 사용 하면서 Model 객체를 매개변수 값으로 받아서 해당 Model에 값을 등록하여 사용자에게 전달하는 방식을 사용한다
06-2. SpringMemberControllerV3 - Method 지정
@RequestMapping(value = "/new-form", method = RequestMethod.GET)
public String newForm() {
return "new-form";
}
- 위와 같이 특정 Method를 지정하여 처리하는 것이 가능
- 하지만 위와 같은 방식도 일일이 지정을 해야 한다는 불편함이 있음
06-3. GetMapping, PostMapping.. 사용
@GetMapping(value = "/new-form")
public String newForm() {
return "new-form";
}
@PostMapping(value = "/save")
- 위와 같이 어노테이션 기반도 사용 가능
06-4. 부가 기능 정리
Model 파라미터 사용
@RequestMapping("/save")
public String save(
@RequestParam("username") String username,
@RequestParam("age") int age,
Model model) {
...중략
}
- Model을 파라미터로 받는 것을 확인 가능한데, 스프링 MVC도 이런 편의 기능 제공
ViewName 직접 반환 가능
@GetMapping(value = "/new-form")
public String newForm() {
return "new-form";
}
- ModelAndView 사용 안하고 ViewName 직접 반환 가능
@RequestParam
- 스프링은 HTTP 요청 파라미터를 @RequestParam 로 받을 수 있음
- request.getParameter(”username”) 과 거의 같은 코드라 보면 됨
- GET query parameter, POST Form 방식 둘다 지원
@RequestMapping → GetMapping, PostMapping
- HTTP Method 변환 가능
'Spring MVC > 스프링 MVC - 백엔드 웹 개발 기술' 카테고리의 다른 글
[스프링 MVC - 백엔드 웹 개발 기술] 13. 컨트롤러의 요청 매핑 방식과 HTTP 요청 기본 헤더 값 조회 방법 (1) | 2024.01.05 |
---|---|
[스프링 MVC - 백엔드 웹 개발 기술] 12. 프로젝트 생성과 로깅 간단히 알아보기 (2) | 2024.01.05 |
[스프링 MVC - 백엔드 웹 개발 기술] 10. MVC 프레임워크 만들기 (1) | 2023.09.11 |
[스프링 MVC - 백엔드 웹 개발 기술] 09. 서블릿, JSP, MVC 패턴 (0) | 2023.08.26 |
[스프링 MVC - 백엔드 웹 개발 기술] 08. HttpServletResponse와 HTTP 응답 방식 - TEXT, HTML, API JSON (0) | 2023.08.13 |
블로그의 정보
기록하고, 복기하고
ymkim