
웹 어플리케이션
웹 서버
- 정적 리소스 제공 (HTML, CSS, JS, 이미지, 영상)
웹 어플리케이션 서버 (WAS)
- 쓰레드 풀 - 필요한 쓰레드 보관, 관리 (Tomcat 최대 200개 기본 설정)
- 멀티 쓰레드 지원
- 멀티 쓰레드 환경이기 때문에 싱클톤 객체(Servlet, Bean) 주의해서 사용
서블릿
HTTP 요청 메시지를 편리하게 사용 할 수 있도록 HTTP 요청 메세지 파싱
- 요청마다 객체 재생성 비효율 -> 하나의 객체 생성해 재사용
- 서블릿 객체는 싱글톤으로 관리
- 공유 변수 사용 주의
- 동시 요청을 위한 멀티 쓰레드 처리 지원
서블릿 객체 생성, 초기화, 호출, 종료하는 생명주기 관리 -> 서블릿 컨테이너
멀티 쓰레드
- 요청마다 쓰레드 생성 시 -> 과도한 요청 시 너무 많은 컨텍스트 스위칭으로 인해 cpu, memory임계치 넘어 서버 장애
- 쓰레드 풀에서 쓰레드를 꺼내서 사용 후 반납 -> 미리 생성되어 있어 비용 절약, 응답시간 빠름
- 생성 가능한 쓰레드의 최대치가 있음, 기존 요청 안전하게 처리 가능
- WAS의 주요 튜닝 포인트는 최대 쓰레드(max thread) 수
- 성능 테스트 - 아파치 ab, 제이미터, nGrinder
Servlet
// 임시 저장소 기능
request.setAttribute(name, value);
request.getAttribute(name);
// 세션 관리
request.getSession(create: true);
Request
Get, Post(Form데이터)
조회 메서드
// HttpServletRequest request
String username = request.getParameter("username"); //단일 파라미터 조회
Enumeration<String> parameterNames = request.getParameterNames(); //파라미터 이름들 모두 조회
Map<String, String[]> parameterMap = request.getParameterMap(); //파라미터를 Map 으로 조회
String[] usernames = request.getParameterValues("username"); //복수 파라미터 조회
POST HTML Form 형식은 헤더에 content-type application/x-www-form-urlencoded 이 포함
HTTP body
- InputStream 사용해 Byte 단위로 읽을 수 있음
- json데이터를 읽기위해 jackson사용
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
JsonData data= objectMapper.readValue(messageBody, JsonData.class);
Response
반환 형태를 지정해 응답할 수 있음
// Header 설정
response.setHeader("content-type", "application/json");
// Encoding type 설정
response.setCharacterEncoding("utf-8");
JsonData data = new JsonData();
data.setUsername("kim");
data.setAge(20);
String result = objectMapper.writeValueAsString(data);
response.getWriter().write(result);
MVC
- DB에서 값을 꺼내올 때 동시성 문제로 인해 실무에선 ConcurrentHashMap, AtomicLong 사용을 고려
FrontController Pattern
클라이언트 요청들을 처리하는 서블릿을 두고 요청에 맞는 컨트롤러를 찾아 호출 -> 공통 처리 가능
DispatcherServlet
1. 핸들러
웹 요청을 실제로 처리하는 객체
2. 어댑터
핸들러마다 각각 그 핸들러를 처리하는 객체
3. 핸들러 매핑
요청 URI과 알맞는 핸들러 객체를 Dispatcher Servlet에 리턴
4. 핸들러 어댑터
핸들러 객체를 가지고 어댑터를 찾는 과정
2개 이상의 인터페이스에 스펙이 맞지 않을 때, 중간에 이 스펙을 맞도록 변환해주는 역할을 하는 객체
핸들러와 어댑터가 필요한 이유
- 핸들러 어댑터는 다양한 요청에 맞춰 ModelAndView객체로 응답
ex) 리턴 타입 변경, 매개변수 변경 - 다양한 형태의 핸들러 처리를 위해 ex) Controller Interface, @Controller 처리를 위한 핸들러 어댑터 필요
- 비즈니스 개발 로직에만 집중하여 편하게 원하는 형태로 컨트롤러를 정의 가능
RequestMapping
우선순위가 가장 높은 핸들러 매핑, 어댑터
-> RequestMappingHandlerMapping, RequestMappingAdaptorMapping
@Controller // Spring bean에 등록
@RequestMapping // 요청 정보 매핑
- @RequestMapping은 클래스 레벨, 메서드 데벨로 나눠서 분리, 조합 가능
@RequestMapping("/map/"); // 클래스 레벨
@RequestMapping("kr"); // 메서드 레벨 -> /map/kr
-> @GetMapping, @PostMapping 으로 활용 가능!
Log
사용 방법
private Logger log = LoggerFactory.getLogger(getClass());
@Slf4j // Lombok 사용 가능
log.trace("{}", data); // 로그 출력 레벨에 따라 실행 x
Spring MVC HTTP 요청, 응답 처리
HTTP 메서드 매핑
@RestController // HTTP Message Body에 바로 입력
@Controller // 반환 값이 'String'이면 뷰이름으로 인식, 뷰를 찾고 렌더링
@RequestMapping // method 속성으로 HTTP 메서드를 지정하지 않으면 HTTP 메서드와 무관하게 호출
@GetMapping({path}) // 축약 가능
@GetMapping(value = {path})
@PostMapping({path})
@PutMapping({path})
@DeleteMapping({path})
@PatchMapping({path})
PathVariable(경로 변수)
@GetMapping("/mapping/users/{id}/name/{name}")
public String mappingPath(@PathVariable String id, @PathVariable String name) {
return "ok";
}
리소스 경로 내 변수 사용 가능
조건 매핑
HTTP 메서드를 매핑해주는 어노테이션에 파라미터 추가해 조건 매핑 가능
@PostMapping(value = "/api", produces = "text/html")
조건 | 내용 | 형식 |
---|---|---|
params | 파마미터 | params = “mode=debug” |
headers | 특정 헤더 | headers = “mode=debug” |
produces | Acept 헤더 | produces = MediaType.APPLICATION_JSON_VALUE |
consumes | Content-Type 헤더 | consumes = MediaType.TEXT_HTML_VALUE |
@RequestParam
url이 매핑 된 함수의 인자값으로 @RequestParam 추가
@RequestMapping("/users")
public String requestParam(
@RequestParam("username") String username,
@RequestParam String phone,
int age,
@requestParam(required = true, defaultValue = "aaa@aaa.com") String email
// @RequestParam Map<String, Object> paramMap
){
...
}
- 파라미터 이름이 변수 이름과 같으면 () 생략 가능
- String, int 등 단순 타입이면 @RequestParam 생략 가능
- 생략할 경우 required=false적용
- Map, MultiValueMap으로 parameter를 한번에 받을 수 있음
@ModelAttribute
요청 파라미터를 받아 객체 생성 (Model 객체에 자동으로 들어간다)
- 생략 가능 (기본 단순 타입 제외한 값이 입력값인 경우 자동 적용)
- 객체 프로퍼티 찾음 -> setter호출 해 입력값 바인딩
- BindException 처리 필요
Text 데이터 처리
@PostMapping("/request")
public HttpEntity<String> requestBody(HttpEntity<String> httpEntity) {
// public HttpEntity<String> requestBody(@RequestBody String httpbody) {
...
}
HttpEntity
- HTTP header, body 정보 조회
- 요청 파라미터 조회 기능 관계 x (@RequestParam, @ModelAttribute)
RequestEntity
HttpMethod, url 정보 추가
ResponseEntity
HTTP 상태코드 설정 가능(조건문에 따라 분기처리)
- @RequestBody, @ResponseBody 어노테이션으로도 제공
- @RequestBody 파라미터에, @ResponseBody는 Mapping 되는 곳 or 클래스에 어노테이션 달아줌
- @RestController or @ResponsesBody를 클래스 레벨 적용시 해당 컨트롤러의 response에 모두 적용
- HttpEntity -> 요청 값 받는 Json에는 사용 가능 (Form 사용 불가능)
Form, HTTP Message Body 데이터를 읽을 때
- 요청 파라미터 조회 : @RequestParam, @ModelAttribute
- HTTP 메시지 바디 조회 : @RequestBody
Json 데이터 처리
처리 방법
- Json 데이터는 Jackson 라이브러리 objectMapper로 객체를 변환해 사용
- HttpEntity, @RequestBody 사용
@PostMapping("/request")
// public HelloData requestBodyJson(HttpEntity<HelloData> httpEntity) {
public HelloData requestBodyJson(@RequestBody HelloData data) {
...
}
@RequestBody생략 불가 -> @ModelAttribute이 적용 됨
Http Body가 아닌 요청 파라미터 처리(Form처리)
Response응답
응답시에는 ResponseEntity(HttpEntity), @ResponseBody 사용
@GetMapping("/ResponseEntity")
public ResponseEntity<String> ResponseEntityApi() {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/ModelAttribute")
public Data ModelAttributeApi() {
...
}
조건문에 따라 상태값을 다르게 리턴하려면 ResponseEntity사용
Http Message Converter
메세지 컨버터가 클래스 타입, 미디어 타입을 읽고 처리 방법 선택
요청 : @RequestBody, HttpEntity(RequestEntity) 응답 : @ResponseBody, HttpEntity(ResponseEntity)
Mapping Handler Adaptor
-
RequestMapping하는 어노테이션에 구현되어 있음
-
Argument Resolver는 요청 시 보낸 데이터 처리
-
@RequestBody, HttpEntity에 한에서 HTTP 메세지 컨버터로 Body데이터 처리
Redirect
RedirectAttributes 객체 사용 (매개변수로 받는다)
@PostMapping("/api")
public String addItem(Item item, RedirectAttributes redirectAttributes) {
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
// pathVarible해주고 나머진 쿼리 파라미터 처리
return "redirect:/basic/items/{itemId}";
}
기능 : URL 인코딩, pathVarible, 쿼리 파라미터 처리
Thymeleaf
순수 HTML을 그대로 유지하면서 뷰 템플릿 사용 가능 (네츄럴 템플릿)
사용 선언
<html xmlns:th="http://www.thymeleaf.org"></html>
@{/…} 형식을 사용하면 Thymeleaf가 context path 추가 ex) WAS안에 여러 애플리케이션 배포 시
@{/basic/items}
Tip!
요청 데이터 종류 : 1) Get쿼리 파라미터, 2) Post - HTML Form, 3) HTTP message body
응답 데이터 종류 : 1) 정적 리소스, 2) 뷰 템플릿, HTTP Message Body
application.properties - logging.level.org.apache.coyote.http11=debug
-> http 요청 메세지 확인 가능 (부하가 있어 개발 할 때만)
redirect vs forward
-> 리다이렉트는 실제 클라이언트(웹 브라우저)에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 요청
(클라이언트가 인지할 수 있고, URL 경로도 실제로 변경)
-> 포워드는 서버 내부에서 일어나는 호출이기 때문에 클라이언트가 전혀 인지x
MultiValueMap
하나의 키에 여러 값을 받을 수 있다. (HTTP header, 쿼리 파라미터 같이 하나의 키에 여러 값을 받을 때 사용)
MultiValueMap<String, String> map = new LinkedMultiValueMap();
map.add("key", "value1");
map.add("key", "value2");
//[value1,value2]
List<String> values = map.get("key");
- 코드 짜면서 중복 vs 명확성 중 고민이면 명확성을 따르는게 좋다
Reference
- [인프런]김영한 스프링 MVC 1 강의