| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- Routing Key
- kafka
- CORS
- Spring
- JPQL
- Web
- 쿠버네티스
- JWT
- Spring Container
- DLQ
- DI
- JdbcTemplate
- AWS
- @ComponentScan
- 스프링 부트
- dockerhub
- Dead Letter Queue
- JPA
- docker
- redis
- 페이징
- 서블릿 컨테이너
- mybatis
- @Transactional
- 지연 로딩
- securitycontextholderfilter
- MSA
- 컨테이너
- Spring Data JPA
- docker compose
- Today
- Total
look-forest
요약 본문
Servlet과 멀티스레딩
서블릿이란?
웹 서버에서 실행되는 자바 프로그램. request 받아 response 응답.
클라이언트의 요청에 대해 동적으로 작동하는 웹 어플리케이션 컴포넌트.
Servlet은 Http 스펙을 편리하게 사용하게 해준다. 개발자는 비즈니스 로직만 구현하면 된다
서블릿 컨테이너
WAS 안의 서블릿 컨테이너가 서블릿을 생성하고 호출해준다.
싱글톤으로 관리 -> 동시 요청을 위한 멀티 쓰레드 처리를 지원한다
멀티 스레드에 대한 부분은 WAS가 처리해주기 때문에, 개발자가 멀티 스레드 관련 코드를 신경쓰지 않아도 된다!
스레드가 서블릿 객체를 호출해준다 ( 스레드: 프로세스 안에서 여러 갈래로 나뉘는 것 )
필요한 스레드를 미리 만들어 스레드 풀에 보관하고 관리

MVC 패턴 구현
프론트 컨트롤러와 어댑터를 통해 컨트롤러를 단순하고 유연하게 구현하는 과정
version 1. 프론트 컨트롤러 도입
수문장이 되는 서블릿에서 공통 처리가 가능해지고, url에 따라 각 컨트롤러를 호출한다.
version 2. View 중복 로직 처리
각 컨트롤러에서 중복되는 view 포워딩 로직을 제거하고 프론트 컨트롤러에서 처리한다.
version 3. Model 추가
컨트롤러에서 불필요한 서블릿 종속성 제거
+ 뷰 풀네임 내의 중복 → 변경의 지점을 하나로 모은다 (프론트 컨트롤러)
version 4. 단순하고 실용적인 컨트롤러
개발자가 구현하기 쉽도록 컨트롤러를 [비즈니스 로직 → 모델에 데이터 추가 → 뷰 이름 반환]의 구조로 단순화
version 5. 유연한 컨트롤러
다양한 컨트롤러 인터페이스를 프론트 컨트롤러에서 일관되게 처리할 수 있도록 '어댑터 패턴' 도입
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
//private Map<String, ControllerV4> controllerMap = new HashMap<>(); 이전 버전
private final Map<String, Object> handlerMappingMap = new HashMap<>(); //V3든 V4든 수용할 수 있도록 Object로 받는다.
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
//생성자는 핸들러 매핑과 어댑터를 초기화(등록)한다.
public FrontControllerServletV5() {
initHandlerMappingMap();
initHandlerAdapters();
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object handler = getHandler(request);
if (handler == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
//어댑터를 끼워 핸들러(컨트롤러) 실행
MyHandlerAdapter adapter = getHandlerAdapter(handler);
ModelView modelView = adapter.handle(request, response, handler);
MyView view = viewResolver(modelView.getViewName());
view.render(modelView.getModel(), request, response);
}
/* 새로운 컨트롤러와 그에 맞는 어댑터가 추가되더라도 이 부분만 수정하면 된다! 뼈대를 흔들지 않는다! */
//url에 매핍되는 핸들러를 반환
private void initHandlerMappingMap() {
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
}
// 각 핸들러마다의 어댑터를 담아둔다.
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
}
Spring MVC 구조 이해

동작 순서
프론트 컨트롤러가 핸들러를 찾아 어댑터로 실행하고, 반환 결과로 뷰를 렌더링한다.
- 핸들러 조회 : 핸들러 매핑을 통해 URL에 매핑된 핸들러(컨트롤러) 조회
- 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터 조회
- 어댑터 실행 : 프론트 컨트롤러(Dispatcher Servlet)에서 인터페이스(어탭터)를 통해
- 핸들러 실행 : 핸들러를 실행하고, 어댑터는 반환 정보를 프론트 컨트롤러에서 일괄 처리할 수 있는
- ModelAndView 반환 : ModelAndView로 변환해서 반환한다.
- viewResolver 호출 : 뷰 리졸버(IF)는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체 반환
- View 반환 : ※ JSP의 경우 InternalResourceView(JstlView)를 반환하는데, 내부에 forward() 로직이 있다
- 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링한다
뷰 리졸버
핸들러 어댑터가 반환한 ModelAndView를 기반으로 ViewResolver가 View를 반환하고, View가 뷰를 렌더링한다.
스프링 부트가 빈으로 자동 등록하는 뷰 리졸버들
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다
application.properties 에 등록한 spring.mvc.view.prefix , spring.mvc.view.suffix 설정 정보를 사용해서 등록
n = ThymeleafViewResolver Thymeleaf 뷰 템플릿을 사용. 라이브러리만 추가하면 스프링 부트가 자동화
스프링 MVC 기본 기능 - 요청
//URL 경로를 템플릿화 할 수 있는데, @PathVariable 을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
log.info("mappingPath userId={}, orderId={}", userId, orderId);
return "ok";
}
@PostMapping(value = "/mapping-consume", consumes = MediaType.APPLICATION_JSON_VALUE)
public String mappingConsumes() {
return "ok";
}
@PostMapping(value = "/mapping-produce", produces = MediaType.TEXT_PLAIN_VALUE)
public String mappingProduces() {
return "ok";
}
@RequestMapping("/headers")
public String headers(HttpServletRequest request,
HttpServletResponse response,
HttpMethod httpMethod,
Locale locale,
//MultiValueMap : 하나의 키에 여러 값을 받을 수 있다. HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용
@RequestHeader MultiValueMap<String, String> headerMap,
@RequestHeader("host") String host,
@CookieValue(value = "cookieName", required = false) String cookie) {
요청 파라미터 vs HTTP 메시지 바디 요청
- 파라미터를 조회하는 기능: @RequestParam , @ModelAttribute
- HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody
@ModelAttribute
1. 요청 파라미터를 받아서 필요한 객체를 만들어 준다.
객체를 파라미터로 받고, 이를 처리하는 어떠한 애노테이션도 없다면 생략 가능하다.
Spring MVC에서 우선적으로 ModelAttributeMethodProcessor를 통해서 바인딩 할 수 있는지 체크하고,
@Primary가 적용된 생성자 > 파라미터를 많이 받는 생성자 순으로 생성자를 리턴해 바인딩할 객체를 생성한다.
그 후 resolveArgument()를 호출해서 파라미터를 객체에 바인딩하고, 그 외 바인딩되지 않은 값은 setter로 바인딩한다.
즉, setter가 없어도 파라미터를 받는 생성자가 있다면 바인딩 받을 수 있다.
2. 모델(Model)에 @ModelAttribute 로 지정한 객체를 자동으로 넣어준다
- key 값은 @ModelAttribute 에 지정한 name(value) 속성, 없으면 클래스명 첫자 소문자
[HTTP 메세지 바디 조회] - @RequestBody, HttpEntity
1. HTTP BODY의 단순 텍스트 조회 - @RequestBody

2. HTTP BODY의 JSON 조회 - @RequestBody
JSON 데이터는 주로 객체로 변환해서 쓴다.
@RequestBody로 조회한 JSON 데이터를 ObjectMapper 라이브러리를 이용해 객체로 변환하는 과정이 필요하다.

Spring은 이 귀찮은 과정을 지원해준다.
@RequestBody에 직접 만든 객체를 지정할 수 있다. by 메세지컨버터

HttpEntity, @RequestBody를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 문자나 객체 등으로 변환
참고로 @RequestBody는 생략 불가하다. 생략 시 @ModelAttribute로 동작하기 때문이다.
-> body에 setter로 넣을 파라미터가 없어 기본값으로 객체 생성됨.
응답 시에는 @ResponseBody를 사용하면 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다. by 메세지컨버터

3. 헤더의 정보도 필요할 경우 - HttpEntity
- HttpEntity(개체, 객체): HTTP header, body 정보를 편리하게 조회
- 스프링MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해주는데, 이때 HTTP 메시지 컨버터( HttpMessageConverter )라는 기능을 사용
- 응답에서도 HttpEntity 사용 가능 - 메시지 바디 정보 직접 반환
- HttpEntity 를 상속받은 다음 객체들도 같은 기능을 제공 : RequestEntity, ResponseEntity

스프링 MVC 기본 기능 - 응답
스프링에서 응답 데이터를 만드는 방법
- 정적 리소스
- 뷰 템플릿
- HTTP 메시지 바디에 문자, JSON 등 직접 입력 - @ResponseBody, HttpEntity

문자열 입력 
JSON 입력
HTTP 메시지 컨버터
HTTP API처럼 JSON, String 등을 body에 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터가 동작한다.
HTTP 메시지 body에 JSON을 직접 반환하는 경우, ViewResolver 대신에 HttpMessageConverter가 동작한다.

HTTP 메시지 컨버터는 전체 Spring 구조에서 어디를 차지하는지 알아보자.
@RequestMapping 기반 컨트롤러를 사용할 때, 놀랍도록 다양한 파라미터 타입과 리턴 타입을 지원한다.
컨트롤러가 호출될 때, 파라미터 값은 다음 과정에서 처리된다.

어댑터는 Argument Resolver와 ReturnValueHandler의 지원으로 이를 가능케 한다.
- RequestMappingHandlerAdapter는 ArgumentResolver를 호출해 핸들러 메소드가 필요로 하는 파라미터 값(객체) 생성
파라미터 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.
- ReturnValueHandler는 응답 값을 변환하고 처리한다
컨트롤러에서 String으로 뷰 이름을 반환해도, 동작하는 이유가 바로 ReturnValueHandler 덕분이다.

HTTP 메시지 컨버터
ArgumentResolver와 ReturnValueHandler에서 HTTP 메시지 body에 직접 읽고, 쓰기가 필요한 경우
HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성한다.
- 요청 : @RequestBody 를 처리하는 ArgumentResolver 가 있고, HttpEntity 를 처리하는 ArgumentResolver 가 있다
- 응답 : @ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 가 있다

기능 확장은 WebMvcConfigurer 를 상속 받아서 스프링 빈으로 등록하면 된다
Redirect 관련
PRG : Post/Redirect/Get 패턴 활용
RedirectAttributes : URL 인코딩도 해주고, pathVarible , 쿼리 파라미터까지 처리해준다

참고 자료 & 이미지 출처
스프링 MVC 1편(김영한 님)
'Spring > Spring MVC - 웹 개발 핵심 기술' 카테고리의 다른 글
| Redirect 관련 (0) | 2023.08.26 |
|---|---|
| HTTP 메시지 컨버터 (0) | 2023.08.22 |
| 스프링 MVC 기본 기능 - 응답 (0) | 2023.08.22 |
| 스프링 MVC 기본 기능 - 요청 (0) | 2023.05.07 |
| Spring MVC 구조 이해 (0) | 2023.05.01 |