로깅 간단히 알아보기

로깅 라이브러리

스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리( spring-boot-starter-logging )가 함께 포함된다.

스프링 부트 로깅 라이브러리는 기본으로 다음 로깅 라이브러리를 사용한다.

 

로그 라이브러리는 Logback, Log4J, Log4J2 등등 수 많은 라이브러리가 있는데,

그것을 통합해서 인터페이스로 제공하는 것이 바로 SLF4J 라이브러리다.

쉽게 이야기해서 SLF4J는 인터페이스이고, 그 구현체로 Logback 같은 로그 라이브러리를 선택하면 된다.

실무에서는 스프링 부트가 기본으로 제공하는 Logback을 대부분 사용한다.

 

로그 사용시 장점

  • 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있다.
  • 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그를 상황에 맞게 조절할 수 있다.
  • 시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다.
    특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
  • 성능도 일반 System.out보다 좋다. (내부 버퍼링, 멀티 쓰레드 등등) 그래서 실무에서는 꼭 로그를 사용해야 한다.

 

@RestController

  • @Controller 는 반환 값이 String 이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 랜더링 된다.
  • @RestController 는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다.
  • @ResponseBody 와 @Controller를 합친것과 동일하게 동작한다.

 


HTTP 요청 - 기본, 헤더 조회

애노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다.

@Slf4j
@RestController
public class RequestHeaderController {

	@RequestMapping("/headers")
	public String headers(HttpServletRequest request,
			HttpServletResponse response,
			HttpMethod httpMethod,
			Locale locale,
			@RequestHeader MultiValueMap<String, String> headerMap,
			@RequestHeader("host") String host,
			@CookieValue(value = "myCookie", required = false) String cookie
			) {
            
	log.info("request={}", request);
	log.info("response={}", response);
	log.info("httpMethod={}", httpMethod);
	log.info("locale={}", locale);
	log.info("headerMap={}", headerMap);
	log.info("header host={}", host);
	log.info("myCookie={}", cookie);
    
	return "ok";
	}
}

HttpMethod

HTTP 메서드를 조회한다.

Locale

Locale 정보를 조회한다.

@RequestHeader MultiValueMap<String, String> headerMap

모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.

@RequestHeader("host") String host

특정 HTTP 헤더를 조회한다.

  • 필수 값 여부: required
  • 기본 값 속성: defaultValue

@CookieValue(value = "myCookie", required = false) String cookie

특정 쿠키를 조회한다.

  • 필수 값 여부: required
  • 기본 값: defaultValue

 


HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

[MVC 1편] 섹션 2. 서블릿 에서 학습한 HTTP 요청 데이터에서 배운 내용과 이어진다.

 

1. HTTP 요청 파라미터 - @RequestParam

/**
 * @RequestParam 사용
 * - 파라미터 이름으로 바인딩
 * @ResponseBody 추가
 * - View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
 */
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
		@RequestParam("username") String memberName,
		@RequestParam("age") int memberAge) {
        
	log.info("username={}, age={}", memberName, memberAge);
	return "ok";
}

@RequestParam

파라미터 이름으로 바인딩한다.

여기서 @RequestParam 을 생략해도 동일하게 동작하지만 생략시 코드 가독성이 떨어질 수가 있다.

@RequestParam 이 있으면 명확하게 요청 파라미터에서 데이터를 읽는다는 것을 알 수 있기 때문

 

@ResponseBody 

View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력

 

2. HTTP 요청 파라미터 - @ModelAttribute

실제 개발을 하면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 한다.

스프링은 이 과정을 완전히 자동화해주는 @ModelAttribute 기능을 제공한다

먼저 요청 파라미터를 바인딩 받을 객체를 만들자.

 

HelloData

@Data
public class HelloData {
	private String username;
	private int age;
}

Lombok@Data @Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor

자동으로 적용해준다.

 

/**
 * @ModelAttribute 사용
 * 참고: model.addAttribute(helloData) 코드도 함께 자동 적용됨
 */
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {

	log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
	return "ok";
}

위의 코드를 작동시켜보면 HelloData 객체가 생성되고, 요청 파라미터의 값도 모두 들어가 있다.

 

스프링MVC는 @ModelAttribute 가 있으면 다음을 실행한다.

 

  1. HelloData 객체를 생성한다.
  2. 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다.
  3. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.
    예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.

 

@ModelAttribute 는 생략할 수 있다.

그런데 @RequestParam 도 생략할 수 있으니 혼란이 발생할 수 있으므로 생략 시 주의가 필요하다

 

3. HTTP 요청 메시지 - 단순 텍스트

요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는

@RequestParam , @ModelAttribute 를 사용할 수 없다

/**
 * HttpEntity: HTTP header, body 정보를 편리하게 조회
 * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 *
 * 응답에서도 HttpEntity 사용 가능
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 */
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {

	String messageBody = httpEntity.getBody();
	log.info("messageBody={}", messageBody);
    
	return new HttpEntity<>("ok");
}

스프링 MVC는 다음 파라미터를 지원한다.

HttpEntity

  • HTTP header, body 정보를 편리하게 조회
  • 메시지 바디 정보를 직접 조회
  • 요청 파라미터를 조회하는 기능과 관계 없음 @RequestParam X, @ModelAttribute X

 

HttpEntity는 응답에도 사용 가능

  • 메시지 바디 정보 직접 반환
  • 헤더 정보 포함 가능
  • view 조회X

 

HttpEntity 를 상속받은 다음 객체들도 같은 기능을 제공한다.

RequestEntity

  • HttpMethod, url 정보 추가
  • 요청에서 사용

 

ResponseEntity

  • HTTP 상태 코드 설정 가능
  • 응답에서 사용
return new ResponseEntity("Hello World", responseHeaders, HttpStatus.CREATED)

스프링MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해주는데,

이때 HTTP 메시지 컨버터( HttpMessageConverter )라는 기능을 사용한다.

 

/**
 * @RequestBody
 * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 *
 * @ResponseBody
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 */
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {

	log.info("messageBody={}", messageBody);
	return "ok";
}

@RequestBody

@RequestBody 를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.

참고로 헤더 정보가 필요하다면 HttpEntity 를 사용하거나 @RequestHeader 를 사용하면 된다.

이렇게 메시지 바디를 직접 조회하는 기능은

요청 파라미터를 조회하는 @RequestParam , @ModelAttribute 와는 전혀 관계가 없다.

 

요청 파라미터 vs HTTP 메시지 바디

요청 파라미터를 조회하는 기능
 - @RequestParam , @ModelAttribute

HTTP 메시지 바디를 직접 조회하는 기능
 - @RequestBody

 

@ResponseBody

@ResponseBody 를 사용하면 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다.

물론 이 경우에도 view를 사용하지 않는다.

 

4. HTTP 요청 메시지 - JSON

/**
 * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
 * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (contenttype: application/json)
 *
 */
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {

	log.info("username={}, age={}", data.getUsername(), data.getAge());
	return "ok";
}

@RequestBody 객체 파라미터

@RequestBody 에 직접 만든 객체를 지정할 수 있다.

 

@RequestBody는 생략 불가능

@ModelAttribute 에서 학습한 내용을 떠올려보자.

 

스프링은 @ModelAttribute , @RequestParam 과 같은 해당 애노테이션을 생략시 다음과 같은 규칙을 적용한다.

  • String , int , Integer 같은 단순 타입 = @RequestParam
  • 나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)

 

따라서 이 경우 HelloData에 @RequestBody 를 생략하면 @ModelAttribute 가 적용되어버린다.

HelloData data -> @ModelAttribute HelloData data

 

따라서 생략하면 HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 된다.

 

물론 앞서 배운 것과 같이 HttpEntity를 사용해도 된다.

@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {

	HelloData data = httpEntity.getBody();
	log.info("username={}, age={}", data.getUsername(), data.getAge());
	return "ok";
}

 

/**
 * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
 * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (contenttype: application/json)
 *
 * @ResponseBody 적용
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용
(Accept: application/json)
 */
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {

	log.info("username={}, age={}", data.getUsername(), data.getAge());
	return data;
}

@ResponseBody

응답의 경우에도 @ResponseBody 를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다.

물론 이 경우에도 HttpEntity 를 사용해도 된다.

@RequestBody 요청

JSON 요청 -> HTTP 메시지 컨버터 -> 객체

 

@ResponseBody 응답

객체 -> HTTP 메시지 컨버터 -> JSON 응답

 


HTTP 응답 - 정적 리소스, 뷰 템플릿

스프링(서버)에서 응답 데이터를 만드는 방법은 크게 3가지이다.

1. 정적 리소스

예) 웹 브라우저에 정적인 HTML, css, js를 제공할 때는, 정적 리소스를 사용한다.

2. 뷰 템플릿 사용

예) 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용한다.

3. HTTP 메시지 사용

HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로,

HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.

 

HTTP 응답 - HTTP API, 메시지 바디에 직접 입력

HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로,

HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.

@Slf4j
@Controller
//@RestController
public class ResponseBodyController {

	@GetMapping("/response-body-string-v1")
	public void responseBodyV1(HttpServletResponse response) throws IOException{
		response.getWriter().write("ok");
	}
    
	/**
	* HttpEntity, ResponseEntity(Http Status 추가)
	* @return
	*/
	@GetMapping("/response-body-string-v2")
	public ResponseEntity<String> responseBodyV2() {
		return new ResponseEntity<>("ok", HttpStatus.OK);
	}
    
	@ResponseBody
	@GetMapping("/response-body-string-v3")
	public String responseBodyV3() {
		return "ok";
	}
    
	@GetMapping("/response-body-json-v1")
	public ResponseEntity<HelloData> responseBodyJsonV1() {
		HelloData helloData = new HelloData();
		helloData.setUsername("userA");
		helloData.setAge(20);
        
		return new ResponseEntity<>(helloData, HttpStatus.OK);
	}
    
	@ResponseStatus(HttpStatus.OK)
	@ResponseBody
	@GetMapping("/response-body-json-v2")
	public HelloData responseBodyJsonV2() {
		HelloData helloData = new HelloData();
		helloData.setUsername("userA");
		helloData.setAge(20);
        
		return helloData;
	}
}

responseBodyV1

서블릿을 직접 다룰 때 처럼 HttpServletResponse 객체를 통해서

HTTP 메시지 바디에 직접 ok 응답 메시지를 전달한다.

 

responseBodyV2

ResponseEntity 는 HttpEntity 를 상속 받았는데, HttpEntity는 HTTP 메시지의 헤더, 바디 정보를 가지고 있다.

ResponseEntity 는 여기에 더해서 HTTP 응답 코드를 설정할 수 있다.

HttpStatus.CREATED 로 변경하면 201 응답이 나가는 것을 확인할 수 있다.

 

responseBodyV3

@ResponseBody 를 사용하면 view를 사용하지 않고,

HTTP 메시지 컨버터를 통해서 HTTP 메시지를 직접 입력할 수 있다.

ResponseEntity 도 동일한 방식으로 동작한다.

 

responseBodyJsonV1

ResponseEntity 를 반환한다.

HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환된다.

 

responseBodyJsonV2

ResponseEntity 는 HTTP 응답 코드를 설정할 수 있는데,

@ResponseBody 를 사용하면 이런 것을 설정하기 까다롭다.

@ResponseStatus(HttpStatus.OK) 애노테이션을 사용하면 응답 코드도 설정할 수 있다.

물론 애노테이션이기 때문에 응답 코드를 동적으로 변경할 수는 없다.

프로그램 조건에 따라서 동적으로 변경하려면 ResponseEntity 를 사용하면 된다.

 

@RestController

@Controller 대신에 @RestController 애노테이션을 사용하면,

해당 컨트롤러에 모두 @ResponseBody 가 적용되는 효과가 있다.

따라서 뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 데이터를 입력한다.

이름 그대로 Rest API(HTTP API)를 만들 때 사용하는 컨트롤러이다.

참고로 @ResponseBody 는 클래스 레벨에 두면 전체 메서드에 적용되는데,

@RestController 에노테이션 안에 @ResponseBody 가 적용되어 있다.

 


HTTP 메시지 컨버터

뷰 템플릿으로 HTML을 생성해서 응답하는 것이 아니라,

HTTP API처럼 JSON 데이터를 HTTP 메시지 바디에서 직접 읽거나 쓰는 경우

HTTP 메시지 컨버터를 사용하면 편리하다.

 

@ResponseBody 사용 원리

 

@ResponseBody 를 사용

  • HTTP의 BODY에 문자 내용을 직접 반환
  • viewResolver 대신에 HttpMessageConverter 가 동작
  • 기본 문자처리: StringHttpMessageConverter
  • 기본 객체처리: MappingJackson2HttpMessageConverter
  • byte 처리 등등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있음

 

스프링 MVC는 다음의 경우에 HTTP 메시지 컨버터를 적용한다.

  • HTTP 요청: @RequestBody , HttpEntity(RequestEntity) 
  • HTTP 응답: @ResponseBody , HttpEntity(ResponseEntity)

 

HTTP 요청 데이터 읽기

  • HTTP 요청이 오고, 컨트롤러에서 @RequestBody , HttpEntity 파라미터를 사용한다.
  • 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 를 호출한다.
    • 대상 클래스 타입을 지원하는가.
      예) @RequestBody 의 대상 클래스 ( byte[] , String , HelloData )
    • HTTP 요청의 Content-Type 미디어 타입을 지원하는가.
      예) text/plain , application/json , */*
  • canRead() 조건을 만족하면 read() 를 호출해서 객체 생성하고, 반환한다.

 

HTTP 응답 데이터 생성

  • 컨트롤러에서 @ResponseBody , HttpEntity 로 값이 반환된다.
  • 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 를 호출한다.
    • 대상 클래스 타입을 지원하는가.
      예) return의 대상 클래스 ( byte[] , String , HelloData )
    • HTTP 요청의 Accept 미디어 타입을 지원하는가.(정확히는 @RequestMapping 의 produces )
      예) text/plain , application/json , */*
  • canWrite() 조건을 만족하면 write() 를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.

 

요청 매핑 헨들러 어뎁터 구조

RequestMappingHandlerAdapter 동작 방식

 

ArgumentResolver

애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있었다.

HttpServletRequest , Model 은 물론이고, @RequestParam , @ModelAttribute 같은 애노테이션

그리고 @RequestBody , HttpEntity 같은 HTTP 메시지를 처리하는 부분까지 매우 큰 유연함을 보여주었다.

이렇게 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver 덕분이다.

애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter

ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다.

그리고 이렇게 파리미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.

그렇다면 HTTP 메시지 컨버터는 어디쯤 있을까?

 

HTTP 메시지 컨버터 위치

HTTP 메시지 컨버터를 사용하는 @RequestBody 도 컨트롤러가 필요로 하는 파라미터의 값에 사용된다. @ResponseBody 의 경우도 컨트롤러의 반환 값을 이용한다.

 

요청의 경우

@RequestBody 와 HttpEntity 를 처리하는 ArgumentResolver 가 있다.

이 ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성한다.

 

응답의 경우

@ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 가 있다.

그리고 여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.

 

스프링 MVC는 @RequestBody@ResponseBody 가 있으면

RequestResponseBodyMethodProcessor (ArgumentResolver)를 사용하고

 

HttpEntity 가 있으면 HttpEntityMethodProcessor (ArgumentResolver)를 사용한다.

스프링 MVC 전체 구조

직접 만든 MVC 프레임워크 구조

 

Spring MVC 구조

 

사실상 명칭만 조금씩 다를 뿐 같은 구조이다

 

스프링 MVC의 핵심 - DispatcherServlet

DispatcherServlet 의 요청 흐름과 동작 순서

 

스프링 MVC - 시작하기

스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작해서, 매우 유연하고 실용적이다.

과거에는 자바 언어에 애노테이션이 없기도 했고, 스프링도 처음부터 이런 유연한 컨트롤러를 제공한 것은 아니다.

 

@RequestMapping

스프링은 애노테이션을 활용한 매우 유연하고, 실용적인 컨트롤러를 만들었는데

이것이 바로 @RequestMapping 애노테이션을 사용하는 컨트롤러이다.

@Controller
public class SpringMemberFormControllerV1 {
	@RequestMapping("/springmvc/v1/members/new-form")
	public ModelAndView process() {
		return new ModelAndView("new-form");
	}
}

 

스프링 MVC - 컨트롤러 통합

@RequestMapping 은 클래스 단위 뿐 아니라 메서드 단위로도 적용할 수 있다.

따라서 컨트롤러 클래스를 유연하게 하나로 통합할 수 있다.

/**
 * 클래스 단위 -> 메서드 단위
 * @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("/springmvc/v2/members")

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

 

스프링 MVC - 실용적인 방식

스프링 MVC는 개발자가 편리하게 개발할 수 있도록 수 많은 편의 기능을 제공한다.

실무에서는 지금부터 설명하는 방식을 주로 사용한다.

/**
 * v3
 * Model 도입
 * ViewName 직접 반환
 * @RequestParam 사용
 * @RequestMapping -> @GetMapping, @PostMapping
 */
@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2 {

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

 

Model 파라미터

save() , members() 를 보면 Model을 파라미터로 받는 것을 확인할 수 있다.

스프링 MVC도 이런 편의 기능을 제공한다.

ViewName 직접 반환

뷰의 논리 이름을 반환할 수 있다.

@RequestParam 사용

스프링은 HTTP 요청 파라미터를 @RequestParam 으로 받을 수 있다.

@RequestParam("username") 은 request.getParameter("username") 와 거의 같은 코드라 생각하면 된다.

물론 GET 쿼리 파라미터, POST Form 방식을 모두 지원한다.

@RequestMapping -> @GetMapping, @PostMapping

@RequestMapping 은 URL만 매칭하는 것이 아니라, HTTP Method도 함께 구분할 수 있다

프론트 컨트롤러 패턴 소개

프론트 컨트롤러 도입 전

 

프론트 컨트롤러 도입 후

 

FrontController 패턴 특징

  • 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
  • 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
  • 입구를 하나로!
  • 공통 처리 가능
  • 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨

 

스프링 웹 MVC와 프론트 컨트롤러

스프링 웹 MVC의 핵심도 바로 FrontController !

스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있음

 

프론트 컨트롤러 도입 과정

v1

프론트 컨트롤러를 도입

v2

View 분류 단순 반복 되는 뷰 로직 분리

v3

Model 추가, 서블릿 종속성 제거, 뷰 이름 중복 제거

 

v4

단순하고 실용적인 컨트롤러 v3와 거의 비슷

구현 입장에서 ModelView를 직접 생성해서 반환하지 않도록 편리한 인터페이스 제공

 

v5

유연한 컨트롤러 어댑터 도입 어댑터를 추가해서 프레임워크를 유연하고 확장성 있게 설계

 

HTTP 요청 데이터는 주로 다음과 같은 3가지 방법을 사용한다

1. GET - 쿼리 파라미터

다음과 같이 URL에서 쿼리 스트링 형식으로 전달한다.

http://localhost:8080/request-param?username=hello&age=20
  • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
  • 예) 검색, 필터, 페이징등에서 많이 사용하는 방식

 

2. POST - HTML Form

HTML Form 형식으로 요청 헤더를 보면 아래와 같은 형식을 확인할 수 있다.

content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파라미터 형식으로 전달 
  • message body:
    username=hello&age=20
  • 예) 회원 가입, 상품 주문, HTML Form 사용
  • 이 방식으로 데이터 전달 시 HTTP 메시지 바디에 해당 데이터를 포함해서 보내기 때문에
    바디에 포함된 데이터가 어떤 형식인지 content-type을 꼭 지정해야 한다.

 

 

3. HTTP message body

JSON 방식으로 보내면 아래와 같은 형식을 확인할 수 있다.

content-type: application/json
  • 말 그대로 메시지 바디에 데이터를 담아 보낸다.
  • message body:
    {
        "username": "hello",
        "age": 20
    }
  • HTTP API에서 주로 사용하는 방식으로 JSON, XML, TEXT이 있다.
  • POST, PUT, PATCH

 

이러한 JSON 데이터를 파싱, 처리해주는 라이브러리 (스프링의 경우 Jackson) 을 사용해서

역직렬화 하여 자바 객체로 변환할 수도 있다.

 

요약

  1. 쿼리 파라미터 : 메시지 바디x, URL 에 쿼리 파라미터 형식
  2. HTML Form : 메시지 바디에 쿼리 파라미터 형식
    key1=value1&key2=value2&... 형식
  3. HTTP message body : 메시지 바디에 데이터를 담음, 주로 JSON

'네트워크' 카테고리의 다른 글

[네트워크] JWT 란  (2) 2023.10.04
x-www-form-urlencoded와 json  (0) 2023.09.21
웹 서버(WS)와 WAS 및 분리 이유  (0) 2023.04.03
[네트워크] REST API 란?  (0) 2023.03.27
[네트워크] API 란?  (0) 2023.03.27

자바 애플리케이션에서 쓰레드란?

애플리케이션 코드를 하나하나 순차적으로 실행하는 것은 쓰레드이다.

자바 메인 메서드를 처음 실행하면 main이라는 이름의 쓰레드가 실행된다.

쓰레드가 없다면 자바 애플리케이션 실행이 불가능하며 쓰레드는 한번에 하나의 코드 라인만 수행한다.

동시 처리가 필요하면 쓰레드를 추가로 생성하여 처리해야한다.

 

요청마다 쓰레드 생성

 

장점

  • 동시 요청을 처리할 수 있다.
  • 리소스(CPU, 메모리)가 허용할 때 까지 처리가능
  • 하나의 쓰레드가 지연돼도 나머지 쓰레드는 정상 동작한다.

단점

  • 쓰레드는 생성 비용이 매우 비싸다.
    -> 고객의 요청이 올 때 마다 쓰레드를 생성하면 응답 속도가 늦어진다.

  • 쓰레드는 컨텍스트 스위칭 비용이 발생한다.
  • 쓰레드 생성에 제한이 없다.
    -> 고객 요청이 너무 많이 오면 CPU, 메모리 임계점을 넘어서 서버가 죽을 수 있다.

 

쓰레드 풀

 

특징

필요한 쓰레드를 쓰레드 풀에 보관하고 관리한다.

쓰레드 풀에 생성 가능한 쓰레드의 최대치를 관리한다.(톰캣은 최대 200개 기본 설정)

사용

  1. 쓰레드가 필요하면, 이미 생성되어 있는 쓰레드를 쓰레드 풀에서 꺼내서 사용한다
  2. 사용을 종료하면 쓰레드 풀에 해당 쓰레드를 반납한다
  3. 최대 쓰레드가 모두 사용중이어서 쓰레드 풀에 쓰레드가 없으면?
    -> 기다리는 요청을 거절하거나 특정 숫자만큼만 대기하도록 설정할 수 있다

 

장점

  • 쓰레드가 미리 생성되어 있으므로 쓰레드를 생성하고 종료하는 비용(CPU)이 절약되고 응답 시간이 빠르다.
  • 생성 가능한 쓰레드의 최대치가 있으므로 너무 많은 요청이 들어와도 기존 요청은 안전하게 처리할 수 있다.

 

WAS의 주요 튜닝 포인트는 최대 쓰레드(max thread) 수이다.

이 값을 너무 낮게 설정하면?

동시 요청이 많으면, 서버 리소스는 여유롭지만, 클라이언트는 금방 응답 지연

이 값을 너무 높게 설정하면?

동시 요청이 많으면, CPU, 메모리 리소스 임계점 초과로 서버 다운

장애 발생시?

클라우드면 일단 서버부터 늘려서 급한불부터 끄고 이후에 튜닝하고, 클라우드가 아니면 즉시 열심히 튜닝해야한다.

 

결론

멀티 쓰레드에 대한 부분은 WAS에서 처리하므로 개발자는 멀티 쓰레드 관련 코드를 신경쓰지 않아도 된다.

따라서 개발자는 마치 싱글 쓰레드 프로그래밍을 하듯이 편리하게 소스 코드를 개발할 수 있다.

단, 멀티 쓰레드 환경이므로 싱글톤 객체(서블릿, 스프링 빈)는 주의해서 사용해야한다.

'백엔드 > Spring' 카테고리의 다른 글

BeanValidation 써보기  (0) 2023.09.22
@ModelAttribute 와 @RequestBody 써보기  (0) 2023.09.21
DispatcherServlet  (0) 2023.08.01
Servlet  (0) 2023.07.25
의존성 주입: Spring을 사용할 때 의존성 주입 방법  (0) 2023.07.18

서블릿

서블릿(Servlet)이란 동적 웹 페이지를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술이다.

 

 

위의 그림에서 개발자가 실제로 만드는 의미있는 비즈니스 로직은 초록색 부분에 불과하다.

하지만 웹 애플리케이션 동작에는 초록색 이외의 모든 과정 또한 필요한데

이 과정들을 대신해서 처리하여 개발자가 비즈니스 로직에 집중할 수 있게 해주는 것이 서블릿이다.

즉, 서블릿은 웹 요청과 응답의 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 해주는 기술이다.

 


서블릿 컨테이너

클라이언트로부터 요청을 받을 때 서블릿을 실행시켜주는 톰캣과 같은 WAS 를 서블릿 컨테이너라고 한다.

 

서블릿 컨테이너는 서블릿 객체를 생성, 초기화, 호출, 종료하는 생명주기를 관리한다.

JSP도 서블릿으로 변환 되어서 사용되며 동시 요청을 위한 멀티 쓰레드 처리를 지원한다.

 

HTTP 요청, 응답 흐름

  1. HTTP 요청 시 WAS는 Request, Response 객체를 새로 만들어서 서블릿 객체 호출
  2. 개발자는 Request 객체에서 HTTP 요청 정보를 편리하게 꺼내서 사용
  3. 개발자는 Response 객체에 HTTP 응답 정보를 편리하게 입력
  4. WAS는 Response 객체에 담겨있는 내용으로 HTTP 응답 정보를 생성

 

서블릿 객체는 싱글톤으로 관리한다.

고객의 요청이 올 때 마다 계속 객체를 생성하는 것은 비효율적이다.

따라서 서블릿 객체는 최초 로딩 시점에 싱글톤으로 미리 만들어두고 재활용한다.

이후 서블릿 컨테이너 종료시 함께 종료된다.

  • 모든 고객 요청은 동일한 서블릿 객체 인스턴스에 접근
  • 싱글톤이므로 공유 변수 사용에 주의 !

 


DispatcherServlet 

스프링을 사용해봤다면 DispatcherServlet 을 들어봤을것이다. 그렇다면 DispatcherServlet 은 뭘까?

이 앞에서 클라이언트가 요청 시 서블릿 컨테이너가 요청을 받는다고 했다.
스프링은 이 요청들을 가장 앞단에서 받아 처리하는 FrontController 라는 개념을 두었다.

FrontController 가 바로 DispatcherServlet 이다.

따라서 DispatcherServlet 을 통해 처리되는 요청은 일련의 스프링 MVC 의 처리 과정을 거친다는의미이다.

 

DispatcherServlet 의 동작 방식은 여기를 참고하자.

'백엔드 > Spring' 카테고리의 다른 글

BeanValidation 써보기  (0) 2023.09.22
@ModelAttribute 와 @RequestBody 써보기  (0) 2023.09.21
DispatcherServlet  (0) 2023.08.01
Thread Pool  (0) 2023.07.25
의존성 주입: Spring을 사용할 때 의존성 주입 방법  (0) 2023.07.18

서블릿으로 회원 관리 웹 애플리케이션 만들기

서블릿으로 회원 정보를 입력할 수 있는 HTML Form을 만들면 아래와 같다.

@WebServlet(name = "memberFormServlet", urlPatterns = "/servlet/members/newform")
public class MemberFormServlet extends HttpServlet {

	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) 
    		throws ServletException, IOException {
            
		response.setContentType("text/html");
		response.setCharacterEncoding("utf-8");
		PrintWriter w = response.getWriter();
		w.write("<!DOCTYPE html>\n" +
			"<html>\n" +
			"<head>\n" +
			" <meta charset=\"UTF-8\">\n" +
			" <title>Title</title>\n" +
			"</head>\n" +
			"<body>\n" +
			"<form action=\"/servlet/members/save\" method=\"post\">\n" +
			" username: <input type=\"text\" name=\"username\" />\n" +
			" age: <input type=\"text\" name=\"age\" />\n" +
			" <button type=\"submit\">전송</button>\n" +
			"</form>\n" +
			"</body>\n" +
			"</html>\n");
	}
}

이처럼 자바 코드로 HTML을 만들어야하므로 쉽지 않은 작업이다.

 

플릿 엔진으로

위와 같이 서블릿과 자바 코드만으로 HTML을 만들어보았다.

서블릿 덕분에 동적으로 원하는 HTML을 마음껏 만들 수 있지만 코드에서 보듯 이는 매우 복잡하고 비효율 적이다.

자바 코드로 HTML을 만들어 내는 것 보다 차라리 HTML 문서에 동적으로 변경해야 하는 부분만

자바 코드를 넣을 수 있다면 더 편리할 것이다. 이것이 바로 템플릿 엔진이 나온 이유이다.

템플릿 엔진을 사용하면 HTML 문서에서 필요한 곳만 코드를 적용해서 동적으로 변경할 수 있다.

템플릿 엔진에는 JSP, Thymeleaf, Freemarker, Velocity등이 있다.

 

회원 등록 폼 JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
	<title>Title</title>
</head>
<body>

<form action="/jsp/members/save.jsp" method="post">
	username: <input type="text" name="username" />
	age: <input type="text" name="age" />
	<button type="submit">전송</button>
</form>

</body>
</html>

회원 등록 폼 JSP를 보면 첫 줄을 제외하고는 완전히 HTML와 똑같다.

JSP는 서버 내부에서 서블릿으로 변환되는데, 위에서 만들었던 MemberFormServlet과

거의 비슷한 모습으로 변환된다.

 

서블릿과 JSP의 한계

서블릿으로 개발할 때는 뷰(View)화면을 위한 HTML을 만드는 작업이 자바 코드에 섞여서 지저분하고 복잡했다.

JSP를 사용하면 뷰를 생성하는 HTML 작업을 깔끔하게 가져가고,

중간중간 동적으로 변경이 필요한 부분에만 자바 코드를 적용했다.

 

그런데 이렇게 해도 해결되지 않는 몇가지 고민이 남는다.

코드를 보면, JAVA 코드, 데이터를 조회하는 리포지토리 등등 다양한 코드가 모두 JSP에 노출되어 있다.

JSP가 너무 많은 역할을 한다. 이렇게 작은 프로젝트도 벌써 머리가 아파오는데,

수백 수천줄이 넘어가는 JSP를 떠올려보면 정말 지옥과 같을 것이다. (유지보수 지옥 썰)

 


 

MVC 패턴 - 개요

너무 많은 역할

하나의 서블릿이나 JSP만으로 비즈니스 로직과 뷰 렌더링까지 모두 처리하게 되면,

너무 많은 역할을 하게되고, 결과적으로 유지보수가 어려워진다.

비즈니스 로직을 호출하는 부분에 변경이 발생해도 해당 코드를 손대야 하고,

UI를 변경할 일이 있어도 비즈니스 로직이 함께 있는 해당 파일을 수정해야 한다.

 

변경의 라이프 사이클

사실 이게 정말 중요한데, 진짜 문제는 둘 사이에 변경의 라이프 사이클이 다르다는 점이다.

UI 를 일부 수정하는 일과 비즈니스 로직을 수정하는 일은 각각 다르게 발생할 가능성이 매우 높고 대부분 서로에게 영향을 주지 않는다.

이렇게 변경의 라이프 사이클이 다른 부분을 하나의 코드로 관리하는 것은 유지보수하기 좋지 않다.

 

기능 특화

JSP 같은 뷰 템플릿은 화면을 렌더링 하는데 최적화 되어 있기 때문에 이 업무만 담당하는 것이 가장 효과적이다.

 

Model View Controller

MVC 패턴은 지금까지 학습한 것 처럼 하나의 서블릿이나, JSP로 처리하던 것을

컨트롤러(Controller)와 뷰(View)라는 영역으로 서로 역할을 나눈 것을 말한다

 

컨트롤러

HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다.

그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다.

모델

뷰에 출력할 데이터를 담아둔다. 뷰가 필요한 데이터를 모두 모델에 담아서 전달해주는 덕분에

뷰는 비즈니스 로직이나 데이터 접근을 몰라도 되고, 화면을 렌더링 하는 일에 집중할 수 있다.

모델에 담겨있는 데이터를 사용해서 화면을 그리는 일에 집중한다. 여기서는 HTML을 생성하는 부분을 말한다.

 

MVC 패턴 적용 전

 

MVC 패턴 적용 후

 

MVC 패턴 - 한계

MVC 패턴을 적용한 덕분에 컨트롤러의 역할과 뷰를 렌더링 하는 역할을 명확하게 구분할 수 있다.

특히 뷰는 화면을 그리는 역할에 충실한 덕분에, 코드가 깔끔하고 직관적이다.

단순하게 모델에서 필요한 데이터를 꺼내고, 화면을 만들면 된다.

그런데 컨트롤러는 중복이 많고, 필요하지 않는 코드들도 있을 수 있다.

 

MVC 패턴 모형

 

정리하면 공통 처리가 어렵다는 문제가 있다

이 문제를 해결하려면 컨트롤러 호출 전에 먼저 공통 기능을 처리하는  소위 수문장 역할을 하는 기능이 필요하다.

프론트 컨트롤러(Front Controller) 패턴을 도입하면 이런 문제를 깔끔하게 해결할 수 있다. (입구를 하나로!)

스프링 MVC의 핵심도 바로 이 프론트 컨트롤러에 있다.

서블릿이란

서블릿과 서블릿 컨테이너

 

ㄱ. HttpServletRequest 역할

HTTP 요청 메시지를 개발자가 직접 파싱해서 사용해도 되지만, 매우 불편하다.

서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신 HTTP 요청 메시지를 파싱한다.

그리고 그 결과를 HttpServletRequest 객체에 담아서 제공한다.

 

HttpServletRequest를 사용하면 다음과 같은 HTTP 요청 메시지를 편리하게 조회할 수 있다.

 

HttpServletRequest 객체에서 얻을 수 있는 정보

1. START LINE 정보

  • HTTP 메소드
  • URL
  • 쿼리 스트링
  • 스키마, 프로토콜

2. 헤더 정보

  • 헤더 조회

3. 바디 정보

  • form 파라미터 형식 조회
  • message body 데이터 직접 조회

+ 세션 관리 기능 등의 부가기능

 

HTTP 요청 데이터

주로 다음 3가지 방법을 사용한다

1. GET - 쿼리 파라미터

2. POST - HTML Form

3. HTTP message body

 

ㄴ. HttpServletResponse 역할

서블릿은 HTTP 응답 메시지를 작성하는 메서드를 지원한다

제공하는 기능은 아래와 같다

  • HTTP 응답코드 지정
  • 헤더 생성 바디 생성
  • 편의 기능 제공 : Content-Type, 쿠키, Redirect

 

HTTP 응답 데이터

1.  단순 텍스트, HTML

HTTP 응답으로 HTML을 반환할 때는 content-type을 text/html 로 지정해야한다

content-type: text/html 

2.  API JSON

HTTP 응답으로 JSON을 반환할 때는 content-type을 application/json 으로 지정해야한다

content-type: application/json

응답으로 반환할 객체 정보를 라이브러리 (스프링의 경우 Jackson) 을 사용해서

직렬화 하여 JSON 데이터로 변환할 수 있다

+ Recent posts