해당 정리는 인프런 김영한 선생님의 스프링 MVC 1편 강의를 수강하고 정리하게 되었습니다.
강의를 통해 이해한 내용을 기반으로 정리하지만 라이브 코딩을 기반으로 코드를 이해하시면서 학습하시는 걸 추천합니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
1. 웹 애플리케이션 이해
스프링 MVC를 공부하기 이전에 웹 애플리케이션에 대한 기본적인 이해가 필요하다.
웹은 HTTP 기반으로 클라이언트가 서버가 인터넷에서 통신한다.
HTTP 에 관한 내용은 다음 게시물을 통해 정리해두었다.
https://five-sun.tistory.com/142
- 웹 서버?
HTTP 기반으로 동작한다.
정적 리소스를 제공하고 기타 부가 기능이 존재한다.
정적 HTML, CSS, JS, 이미지, 영상 등을 정적 리소스라고 한다.
웹 서버로는 NGINX, APACHE 등이 있다.
- 웹 애플리케이션 서버?
HTTP 기반으로 동작한다.
웹 서버 기능을 포함하고 프로그램 코드를 실행해서 애플리케이션 로직을 수행한다.
동적 HTML, HTTP API(JSON), 서블릿, JSP, 스프링 MVC 이루어진다.
웹 애플리케이션 서버로는 Tomcat, Jetty, Undertow 등이 있다.
- 웹 서버와 웹 애플리케이션 서버의 차이?
웹 서버는 정적 리소스, 웹 애플리케이션 서버는 애플리케이션 로직 이라고 이해하면 용이하다.
둘의 용어도 경계도 모호하다.
웹 서버도 프로그램을 실행하는 기능을 포함하기도 하고 웹 애플리케이션 서버도 웹 서버의 기능을 제공하기 때문이다.
자바는 서블릿 컨테이너 기능을 제공하면 웹 애플리케이션 서버라고 하지만 서블릿 없이 자바코드를 실행하는 서버 프레임워크도 존재한다.
웹 애플리케이션 서버가 애플리케이션 코드를 실행하는데 더욱 특화되어 있다.
- 웹 시스템 구성?
WAS, DB만으로 시스템을 구성 가능하지만 WAS가 너무 많은 역할을 당담하면 서버 과부하의 우려가 있다.
가장 비싼 애플리케이션 로직이 정적 리소스 때문에 수행이 어려울 수 있고 WAS만 존재할 경우 장애시 오류 화면을 노출할 수 없다.
WEB, WAS, DB로 시스템을 구성하게 되면 정적 리소스는 웹 서버가 처리하고 중요한 애플리케이션 로직 처리는 WAS가 담당하게 할 수 있다.
이로서 효율적인 리소스 관리가 가능하고 서버의 확장도 쉽게 판단할 수 있다.
정적 리소스만 제공하는 웹 서버는 잘 죽지 않고 WAS, DB 장애시 웹 서버가 오류 화면을 제공할 수 있다.
- 서블릿?
웹 애플리케이션 서버를 직접 구현할 수 있지만 서블릿을 지원하는 WAS를 사용하면 상당히 편리하다.
urlPatterns의 URL이 호출되면 서블릿 코드가 실행된다.
HTTP 요청 정보를 편리하게 사용할 수 있는 HttpServletRequest
HTTP 응답 정보를 편리하게 제공할 수 있는 HttpServletResponse
개발자는 Http 스펙을 매우 편리하게 사용할 수 있다.
Tomcat 처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 한다.
서블릿 컨테이너는 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기를 관리한다.
서블릿 객체는 싱글톤으로 관리하는데 요청이 올 때마다 객체를 생성하는 것은 비효율적이므로 최초 로딩 시점에 객체를 생성하고 재활용한다.
- 멀티 쓰레드
서블릿 컨테이너는 동시 요청을 위한 멀티 쓰레드 처리를 지원한다.
애플리케이션 코드를 하나하나 순차적으로 실행하는 것이 쓰레드다.
쓰레드가 없다면 자바 애플리케이션 실행이 불가능하다.
동시 처리가 필요하면 쓰레드를 추가로 생성한다.
요청마다 쓰레드를 생성하면 동시 요청 처리 가능, 리소스의 허용 범위까지 처리 가능, 하나의 쓰레드가 지연되어도 나머지 쓰레드는 정상 동작한다는 장점이 있다.
하지만 쓰레드의 생성 비용은 매우 비싸고 컨텍스트 스위칭 비용이 발생하고 너무 많은 요청이 발생하면 서버가 죽을 수 있는 단점이 있다.
이 문제를 쓰레드 풀이 쓰레드를 관리하면 단점을 보완한다.
살무에선 적절한 max thread를 설정하는 것이 중요하다.
- HTML, HTTP API, CSR, SSR
정적 리소스: 고정된 HTML 파일, CSS, JS, 이미지, 영상 등
HTTP API: HTML이 아닌 주로 JSON 형식의 데이터 전달
SSR: 서버 사이드 렌더링, HTML의 최종 결과를 서버에서 만들어 웹 브라우저에 전달 - JSP, 타임리프 등
CSR: 클라이언트 사이드 렌더링, HTML 결과를 자바 스크립트를 사용해 웹 브라우저에서 동적으로 생성 및 적용 - React, Vue.js 등
2. 서블릿
서블릿은 톰캣 같은 웹 애플리케이션 서버를 직접 설치하고 그 위에 서블릿 코드를 클래스 파일로 빌드해서 올린 다음, 톰캣 서버를 실행하면 된다. 하지만 스프링 부트는 톰캣 서버를 내장하고 있으므로, 톰캣 서버 설치 없이 편리하게 서블릿 코드를 실행할 수 있다.
@SevletComponentScan: 스프링 부트는 서블릿 직접 등록해서 사용할 수 있도록 해당 어노테이션을 지원한다.
@WebServlet: 서블릿 어노테이션
ㄴname: 서블릿 이름
ㄴurlPatterns: 매핑 URL
@Override
protected void service(): HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 해당 메서드를 실행한다.
- 서블릿 컨테이너 동작 방식
스프링 부트의 경우, 내장 톰캣 서버를 생성하면 웹 애플리케이션 서버와 서블릿 컨테이너가 생성되고 이 내장 톰캣 서버가 서블릿을 생성하고 클라이언트의 HTTP 요청 메시지를 기반으로 request를 생성하고 서블릿 컨테이너에서 관리하는 컨텍스트에 접근하고 종료되면 response 객체 정보로 HTTP 응답을 생성해서 클라이언트에게 응답한다.
- HTTP 요청 데이터
주로 3가지 방법을 사용한다.
1. GET - 쿼리 파라미터: 메시지 바디 없이, URL 쿼리 파라미터를 사용해서 데이터를 전달한다.
2. POST - HTML FORM: content-type -> application/x-www-form-urlencoded, 메시지 바디에 쿼리 파라미터 형식으로 데이터 전달한다.
3. HTTP message body에 데이터를 직접 담아서 요청: 데이터 형식은 주로 JSON을 사용하고 POST, PUT, PATCH 등에 사용한다.
#GET URL 쿼리 파라미터 형식은 메시지 바디를 사용하지 않기 때문에 content-type이 없고 POST HTML Form 형식은 content-type을 꼭 지정해야 한다.
#Servlet에서 JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 Jackson, Gson 같은 JSON 라이브러리를 추가해서 사용해야 한다. 스프링 MVC를 선택하면 기본으로 Jackson 라이브러리를 함께 제공한다.
3. 서블릿, JSP, MVC 패턴
- 싱글톤 패턴은 객체를 단 하나만 생성해서 공유하므로 생성자를 private 접근자로 막아둔다.
- 템플릿 엔진이란?
정적인 HTML 문서라면 화면이 계속 달라지는 결과 또는 목록 같은 동적인 HTML을 만드는 일은 불가능할 것이다. 그런데, 코드에서 보듯이 이것은 매우 복잡하고 비효율적이다. 자바 코드로 HTML을 만들어 내는 것보다 차라리 HTML 문서에 동적으로 변경해야 하는 부분만 자바 코드를 넣을 수 있다면 더 편리할 것이다. 템플릿 엔진에는 JSP, Thymeleaf, Freemarker, Velocity 등이 있다.
- JSP를 사용하려면 라이브러리 추가가 필요하다.
- 서블릿과 JSP의 한계
서블릿으로 개발할 때는 뷰 화면을 위한 HTML을 만드는 작업이 자바 코드에 섞여서 지저분하고 복잡했다. 코드의 상위 절반은 비즈니스 로직이고, 나머지는 뷰 영역이기 때문이다. 이렇게 되면 다양한 코드가 모두 JSP에 노출되어 있고 너무 많은 역할을 한다.
- MVC 패턴의 등장
비즈니스 로직은 서블릿처럼 다른곳에서 처리하고, JSP는 목적에 맞게 HTML로 화면을 그리는 일에 집중하도록 MVC패턴이 등장하였다.
- MVC 패턴의 개요
1. 너무 많은 역할
2. 변경의 라이프 사이클: 변경의 라이프 사이클이 다른 부분을 하나의 코드로 관리하는 것은 유지보수하기 좋지 않다
3. 기능 특화: JSP 같은 뷰 템플릿은 화면을 렌더링 하는데 최적화 되어 있기 때문에 이 부분의 업무만 담당하는 것이 좋다.
-Model View Controller
컨트롤러: HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다
모델: 뷰에 출력할 데이터를 담아둔다. 화면을 렌더링 하는 일에 집중할 수 있게 해준다.
뷰: 모델에 담겨있는 데이터를 사용해서 화면을 그리는 일을 한다.
- /WEB-INF
이 경로 안에 있는 JSP는 외부에서 직접 호출할 수 없고 컨트롤러를 통해서 호출할 수 있다.
- redirect / forward
리다이렉트는 실제 클라이언트에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 요청을 보내는 형식이다. 따라서 클라이언트가 인지할 수 있고, URL 경로도 실제로 변경된다. 반면 포워드는 서버 내부에서 일어나는 호출이기 때문에 클라이언트는 인지할 수 없다.
- MVC 패턴의 한계
MVC 패턴을 적용한 덕분에 컨트롤러의 역할과 뷰를 렌더링하는 역할을 명확하게 구분할 수 있게 되었다.
그런데 컨트롤러는 중복이 많고, 필요하지 않는 코드들이 존재한다. 기능이 복잡해질수록 컨트롤러에서 공통으로 처리해야 하는 부분이 점점 더 많아진다. HttpServletRequest, HttpServletResponse를 사용하는 코드는 테스트 케이스를 작성하기도 어렵다.
이 문제를 해결하려면 컨트롤러 호출 전에 먼저 공통 기능을 처리해야 한다. 소위 수문장 역할을 하는 기능이 필요하다. Front Controller 패턴을 도입하면 이런 문제를 깔끔하게 해결할 수 있다.
스프링 MVC의 핵심도 바로 이 Front Controller에 있다.
4. MVC 프레임워크 만들기
- 프론트 컨트롤러 패턴 특징
프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받는다.
프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출한다.
공통 처리가 가능하고 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서브릿을 사용하지 않아도 된다.
- 스프링 웹 MVC와 프론트 컨트롤러
스프링 웹 MVC의 핵심도 바로 Front Controller이고 DispatcherServlet이 FrontController 패턴으로 구현되어 있다.
이번 챕터에서는 v1 ~ v5로 점진적으로 프레임워크를 발전시켰다.
- V1: 프론트 컨트롤러 도입
- V2: View 분류: 단순 반복 되는 뷰 로직 분리
- V3: Model 추가: 서블릿 종속성 제거, 뷰 이름 중복 제거
- V4: 구현 입장에서 ModelView를 직접 생성해서 반환하지 않도록 편리한 인터페이스 제공
- V5: 유연한 컨트롤러: 어댑터 도입, 어댑터를 추가해서 프레임워크를 유연하고 확장성 있게 설계
- 어노테이션을 사용해서 컨트롤러를 편리하게 사용할 수 있도록 어노테이션을 지원하는 어댑터를 추가하면 된다.
- 스프링 MVC는 위의 내용과 거의 같은 구조를 가지고 있다.
5. 스프링 MVC - 구조 이해
- Spring MVC 구조
- DispatcherServlet?
- 스프링 MVC의 프론트 컨트롤러의 역할 = DispatcherServlet이다.
- 이 DispatcherServlet이 바로 스프링 MVC의 핵심이다.
- DispatcherServlet도 부모 클래스에서 HttpServlet을 상속 받아서 사용하고, 서블릿으로 동작한다
- ㄴDispatcherServelt > FrameworkServlet > HttpServletBean > HttpServlet
- 스프링 부트는 DispatcherServlet을 서블릿으로 자동으로 등록하면서 모든 경로(urlPatterns="/")에 대해서 매핑한다.
- 요청 흐름?
- 서블릿이 호출되면 HttpServlet이 제공하는 service()가 호출된다.
- 스프링 MVC는 DispatcherServlet의 부모인 FrameworkServlet에서 service()를 오버라이드 해두었다.
- FrameworkServlet.service()를 시작으로 여러 메서드가 호출되면서 DispatcherServlet.doDispatch()가 호출된다.
- doDispatch() 코드 흐름에 따른 Spring MVC 동작 순서
1. 핸들러 조회: 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러를 조회한다.
2. 핸들러 어댑터 조회: 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
3. 핸들러 어댑터 실행: 핸들러 어댑터를 실행한다.
4. 핸들러 실행: 핸들러 어댑터가 실제 핸들러를 실행한다.
5. ModelAndView 반환: 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
6. viewResolver 호출: 뷰 리졸버를 찾고 실행한다.
ㄴJSP의 경우, InternalResourceViewResolver가 자동 등록되고 사용된다.
7. View 반환: 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
ㄴJSP의 경우, InternalResourceView(JstlView)를 반환하는데, 내부에 forward()로직이 있다.
8. 뷰 렌더링: 뷰를 통해서 뷰를 렌더링 한다.
- 스프링 MVC의 큰 강점은 DispatcherServlet 코드의 변경 없이, 원하는 기능을 변경하거나 확장할 수 있다는 점이다. 인터페이스들만 구현해서 DispatcherServlet에 등록하면 컨트롤러를 만들 수 있다.
- HandlerMapping
핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 한다.
스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
*우선순위
- RequestMappingHandler: 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
- BeanNameUrlHandlerMapping: 스프링 빈의 이름으로 핸들러를 찾는다.
- HandlerAdapter
핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.
Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.
*우선순위
- RequestMappingHandlerAdapter: 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
- HttpRequestHandlerAdapter: HttpRequestHandler 처리
- SimpleControllerHandlerAdapter: Controller 인터페이스(어노테이션 X, 과거에 사용)
- 스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해두었다.
- 뷰 리졸버
스프링은 InternalResourceViewResolver라는 뷰 리졸버를 자동으로 등록하는데, 이때, application.properties에 등록한 spring.mvc.view.prefix와 spring.mvc.view.suffix 설정 정보를 사용해서 등록한다.
- 뷰 리졸버 동작 방식
- 핸들러 어댑터 호출
- ViewResolver 호출
- InternalResourceViewResolver 반환
- 뷰 - InternalResourceViewRsolver: forward()호출
- view.render 호출
- RequestMapping?
스프링은 어노테이션을 활용한 매우 유연하고, 실용적인 컨트롤러를 만들었는데 이것이 바로 @RequestMapping 어노테이션을 사용하는 컨트롤러이다. 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터를 사용한다. 실무에서 99.9% 이 방식의 컨트롤러를 사용한다.
- 스프링 3.0 (스프링 6.0) 부터는 클래스 레벨에 @RequestMapping이 있어도 스프링 컨트롤러로 인식하지 않고 오직 @Controller가 있어야 스프링 컨트롤러로 인식한다.
- @RequestParam은 GET 쿼리 파라미터, POST Form 방식을 모두 지원한다.
6. 스프링 MVC - 기본 기능
- Logging
- 스프링 부트는 기본으로 다음 로깅 라이브러리를 사용: SLF4J, Logback
*로그 선언 방법
- private logger log = LoggerFactory.getLogger(getClass());
- private static final Logger log = LoggerFactory.getLogger(Xxx.class)
- @Slf4j: 롬북 사용가능
*로그 호출 방법
- log.info()
*로그 레벨 설정
#전체 로그 레벨 설정(기본 info)
logging.level.root = ???
#hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc= = ???
*로그 레벨
- TRACE > DEBUG > INFO > WARN > ERROR
- 요청 매핑
- @Controller는 반환 값이 String이면 뷰 이름으로 인식되어 뷰를 찾고 뷰가 렌더링 된다.
- @RestController는 반환 값으로 뷰가 아닌 HTTP 메시지 바디에 바로 입력한다.
- @RequestMapping("URL 정보") URL 호출이 오면 이 메서드가 실행되도록 매핑하고 대부분의 속성을 배열로 제공하므로 다중 설정이 가능하다.
*HTTP 메서드: GET, HEAD, POST, PUT, PATCH, DELETE
*PathVariable(경로 변수) 사용
- 예시: @GetMapping("/mapping/{userId}") 처럼 사용 가능
- 다중으로 사용 가능
*특정 파라미터 조건 매핑
- @GetMapping(value = "/mapping-param", params = "mode=debug")
*특정 헤더 조건 매핑
- 예시: @GetMapping(value="mapping-header", headers = "mode=debug")
*미디어 타입 조건 매핑
- @PostMapping(value = "/mapping-consume", consumes = "application/json")
- @PostMapping(value = "/mapping-produce", produces = "text/html")
- HTTP 요청 - 기본, 헤더 조회
- HttpMethod : HTTP 메서드를 조회
- Locale : Locale 정보를 조회
- @RequestHeader MultiValueMap headerMap: 모든 HTTP 헤더를 MultiValueMap 형식으로 조회
- @RequestHeader("host") String host: 특정 HTTP 헤더를 조회
- @CookieValue(value = "myCookie", required = false) String cookie: 특정 쿠키를 조회
- HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
- GET - 쿼리 파라미터
- POST - HTML Form
- HTTP message body에 데이터를 직접 담아서 요청
- HTTP 요청 파라미터: @RequestParam
- @RequestParam : 파라미터 이름으로 바인딩
- @RequestParam.required: 파라미터 필수 여부, 기본 값 = true
- defaultValue: 기본 값을 적용 가능
- 파라미터를 Map, MultiValueMap으로 조회 가능
- @ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
- HTTP 요청 파라미터: @ModelAttribute
요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 하는데 스프링은 @ModelAttribute 기능을 통해 자동화를 제공
- HTTP 요청 메시지 - 단순 텍스트
- 요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam , @ModelAttribute 를 사용할 수 없다.
'JAVA' 카테고리의 다른 글
[Java] Open JDK와 Oracle JDK의 차이점과 Java 1.8 (0) | 2023.03.08 |
---|---|
[Java] Spring + JSP 게시판 (Vue 활용하기) (0) | 2023.03.06 |
[Java] JSP + Spring 게시판 만들기 (0) | 2023.02.28 |
[Java] 스프링 입문 - 코딩을 하며 스프링 공부해보기(SpringBoot, 웹 MVC, DB 접근 기술) (0) | 2023.01.07 |
[JAVA] JSP 게시판 따라 만들어보고 서버 배포까지 공부해보기(EC2, Docker) (0) | 2022.12.16 |