스프링 MVC 기본 기능
- 김영한님의 스프링 MVC 1편 강의를 통해 스프링 MVC의 기본 기능들인 로깅, 요청 매핑, 요청 데이터 처리, 응답 데이터 생성, HTTP 메시지 컨버터의 동작 원리를 정리함
프로젝트 기본 설정
프로젝트 생성
- 빌드 도구
- Gradle
- 언어
- Java 11
- Spring Boot
- 2.4.x
- 패키징
- Jar (내장 톰캣 사용)
- Dependencies
- Spring Web
- Thymeleaf
- Lombok
Jar와 War 비교
- Jar (Java Archive)
- 정의
- 애플리케이션과 내장 WAS(톰캣)를 함께 패키징
- 실행 방식
java -jar application.jar- 별도의 WAS 설치 불필요
- 특징
- 독립 실행 가능한 애플리케이션
- 내장 톰캣이 포함되어 있음
- 개발과 운영 환경 차이 최소화
- 정의
- War (Web Application Archive)
- 정의
- 웹 애플리케이션만 패키징 (WAS 미포함)
- 실행 방식
- 외부 톰캣/제티 등의 WAS에 배포 필요
- 특징
- WAS에 종속적
- 전통적인 자바 웹 애플리케이션 방식
WEB-INF,web.xml등 표준 디렉토리 구조
- 정의
로깅
로깅 라이브러리
- 스프링 부트는 기본으로 다음 로깅 라이브러리를 사용
- SLF4J (인터페이스)
- Logback (구현체)
로그 레벨
1
TRACE > DEBUG > INFO > WARN > ERROR
- 개발 서버
DEBUG레벨 출력
- 운영 서버
INFO레벨 출력
로그 설정
1
2
3
4
5
# 전체 로그 레벨 설정 (기본: INFO)
logging.level.root=info
# 패키지별 로그 레벨 설정
logging.level.hello.springmvc=debug
로그 사용법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Slf4j
@RestController
public class LogTestController {
@RequestMapping("/log-test")
public String logTest() {
String name = "Spring";
// 로그 레벨별 출력
log.trace("trace log={}", name);
log.debug("debug log={}", name);
log.info("info log={}", name);
log.warn("warn log={}", name);
log.error("error log={}", name);
return "ok";
}
}
올바른 로그 사용법
-
올바른 방법
1
log.info("message={}", data);
- 파라미터 방식 사용
- 로그 레벨에 따라 해당 로그가 출력되지 않으면 문자열 연산이 발생하지 않음
-
잘못된 방법
1
log.info("message=" + data);
- 문자열 연결 연산이 항상 발생함
- 로그가 출력되지 않아도 불필요한 연산 수행
- 성능 저하
로깅의 장점
- 쓰레드 정보, 클래스 이름과 같은 부가 정보 제공
- 출력 형식 통일
- 로그 레벨에 따라 출력 조절 가능
- 개발/운영 서버별 차등 적용
- 콘솔뿐만 아니라 파일, 네트워크 등 위치를 지정할 수 있음
- 일별로 파일 분할 가능
- 성능이
System.out보다 우수- 내부 버퍼링, 멀티 쓰레드 등 최적화
요청 매핑
기본 매핑
1
2
3
4
5
@RestController
public class MappingController {
@RequestMapping("/hello-basic") // 모든 HTTP 메서드 허용
@GetMapping("/mapping-get") // GET 메서드만 허용
}
@RestController@Controller+@ResponseBody- 반환 값을 HTTP 메시지 바디에 직접 입력
- 기본 매핑 전체 코드
HTTP 메서드 매핑
1
2
3
4
5
@GetMapping // GET
@PostMapping // POST
@PutMapping // PUT
@DeleteMapping // DELETE
@PatchMapping // PATCH
- 메서드가 일치하지 않으면 405 상태코드 반환
PathVariable (경로 변수)
1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {
return "ok";
}
// 다중 PathVariable 사용
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(
@PathVariable String userId,
@PathVariable Long orderId) {
return "ok";
}
- 경로 변수
- URL 경로에 값을 포함시켜 전달
- RESTful API 설계에 적합
- 변수명이 같으면
@PathVariable("userId")생략 가능
조건부 매핑
-
특정 파라미터 조건
1 2 3 4
@GetMapping(value = "/mapping-param", params = "mode=debug") public String mappingParam() { return "ok"; }
-
특정 헤더 조건
1 2 3 4
@GetMapping(value = "/mapping-header", headers = "mode=debug") public String mappingHeader() { return "ok"; }
-
Content-Type 조건
1 2 3 4
@PostMapping(value = "/mapping-consume", consumes = "application/json") public String mappingConsumes() { return "ok"; }
-
Accept 조건
1 2 3 4
@PostMapping(value = "/mapping-produce", produces = "text/html") public String mappingProduces() { return "ok"; }
RESTful API 예시
1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
@GetMapping // 회원 목록 조회
@PostMapping // 회원 등록
@GetMapping("/{userId}") // 회원 조회
@PatchMapping("/{userId}") // 회원 수정
@DeleteMapping("/{userId}") // 회원 삭제
}
HTTP 요청 데이터 처리
요청 데이터 전달 방식

- GET - 쿼리 파라미터
/url?username=hello&age=20- 메시지 바디 없이 URL의 쿼리 파라미터에 데이터 포함
- 검색, 필터, 페이징 등에서 사용
- POST - HTML Form
Content-Type: application/x-www-form-urlencoded- 메시지 바디에 쿼리 파라미터 형식으로 전달
- 회원 가입, 상품 주문 등에서 사용
- HTTP message body
- HTTP API에서 주로 사용
- JSON, XML, TEXT
- POST, PUT, PATCH
@RequestParam
-
기본 사용
1 2 3 4 5 6 7 8
// 파라미터 이름 지정 @RequestMapping("/request-param-v1") public String requestParamV1( @RequestParam("username") String memberName, @RequestParam("age") int memberAge) { log.info("username={}, age={}", memberName, memberAge); return "ok"; }
-
파라미터 이름 생략
1 2 3 4 5 6 7
// 변수명과 파라미터명이 같으면 생략 가능 @RequestMapping("/request-param-v2") public String requestParamV2( @RequestParam String username, @RequestParam int age) { return "ok"; }
-
애노테이션 생략
1 2 3 4 5
// 단순 타입은 @RequestParam 생략 가능 @RequestMapping("/request-param-v3") public String requestParamV3(String username, int age) { return "ok"; }
- 단순 타입인 경우 생략 가능
- 너무 생략하면 명확하지 않으므로
@RequestParam사용 권장
-
필수 파라미터 설정
1 2 3 4 5 6 7
// required 옵션으로 필수 여부 설정 @RequestMapping("/request-param-required") public String requestParamRequired( @RequestParam(required = true) String username, @RequestParam(required = false) Integer age) { return "ok"; }
required = true- 기본값, 파라미터 필수
required = false- 파라미터 선택
- 주의
int age는null을 받을 수 없으므로Integer age사용
-
기본값 설정
1 2 3 4 5 6 7
// defaultValue로 기본값 지정 @RequestMapping("/request-param-default") public String requestParamDefault( @RequestParam(defaultValue = "guest") String username, @RequestParam(defaultValue = "-1") int age) { return "ok"; }
defaultValue는 빈 문자열(““)에도 적용됨
-
Map으로 조회
1 2 3 4 5 6 7
// 모든 파라미터를 Map으로 받기 @RequestMapping("/request-param-map") public String requestParamMap(@RequestParam Map<String, Object> paramMap) { log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age")); return "ok"; }
@ModelAttribute
-
객체 자동 바인딩
1 2 3 4 5 6 7 8 9 10 11 12
@Data public class HelloData { private String username; private int age; } // 요청 파라미터를 객체로 자동 바인딩 @RequestMapping("/model-attribute-v1") public String modelAttributeV1(@ModelAttribute HelloData helloData) { log.info("username={}, age={}", helloData.getUsername(), helloData.getAge()); return "ok"; }
- HelloData 클래스 코드
- 동작 과정
HelloData객체 생성- 요청 파라미터 이름으로
HelloData객체의 프로퍼티 찾기 - 해당 프로퍼티의 setter 호출하여 값 바인딩
-
애노테이션 생략
1 2 3 4 5
// 단순 타입 외의 객체는 @ModelAttribute 생략 가능 @RequestMapping("/model-attribute-v2") public String modelAttributeV2(HelloData helloData) { return "ok"; }
- 단순 타입 외의 객체는
@ModelAttribute생략 가능
- 단순 타입 외의 객체는
- 바인딩 규칙
String,int,Integer등 단순 타입@RequestParam적용
- 나머지
@ModelAttribute적용
HTTP 메시지 바디 - 단순 텍스트
-
@RequestBody(권장)1 2 3 4 5 6
// @RequestBody로 HTTP 메시지 바디 직접 조회 @PostMapping("/request-body-string-v3") public HttpEntity<String> requestBodyStringV3(@RequestBody String messageBody) { log.info("messageBody={}", messageBody); return new HttpEntity<>("ok"); }
-
HTTP 메시지 바디 정보를 편리하게 조회
HTTP 메시지 바디 - JSON
-
자동 변환 (권장)
1 2 3 4 5 6 7
// HTTP 메시지 컨버터가 JSON을 객체로 자동 변환 @ResponseBody @PostMapping("/request-body-json-v2") public String requestBodyJsonV2(@RequestBody HelloData data) { log.info("username={}, age={}", data.getUsername(), data.getAge()); return "ok"; }
- 동작 방식
@RequestBody에 직접 객체 지정- HTTP 메시지 컨버터가 JSON을 객체로 변환
Content-Type: application/json필수
- 장점
- 수동 변환 불필요 (
ObjectMapper사용 X) - 간결한 코드
- HTTP API 개발에 필수
- 수동 변환 불필요 (
- JSON 처리 전체 코드
주의
@RequestBody는 생략 불가능- 생략하면
@ModelAttribute가 적용되어 요청 파라미터를 처리하게 됨
HTTP 응답 데이터 생성
응답 데이터 생성 방식
- 정적 리소스
- 웹 브라우저에 정적인 HTML, CSS, JS 제공
- 뷰 템플릿
- 동적인 HTML 제공
- HTTP 메시지
- HTTP 메시지 바디에 JSON 같은 데이터 직접 전달
정적 리소스
- 경로
src/main/resources/staticsrc/main/resources/publicsrc/main/resources/resourcessrc/main/resources/META-INF/resources
- URL 매핑
src/main/resources/static/basic/hello.html→http://localhost:8080/basic/hello.html
- 사용
- HTML, CSS, JavaScript 파일
- 이미지, 폰트 등의 리소스
- 변하지 않는 고정된 콘텐츠
뷰 템플릿
- 경로
src/main/resources/templates
-
ModelAndView 반환
1 2 3 4 5 6
// ModelAndView 객체로 모델과 뷰 이름 전달 @RequestMapping("/response-view-v1") public ModelAndView responseViewV1() { ModelAndView mav = new ModelAndView("response/hello").addObject("data", "hello!"); return mav; }
-
String 반환 (논리 뷰 이름)
1 2 3 4 5 6
// 뷰 논리 이름을 String으로 반환 @RequestMapping("/response-view-v2") public String responseViewV2(Model model) { model.addAttribute("data", "hello!"); return "response/hello"; }
- 사용
- 동적으로 변하는 HTML 페이지
- 사용자별 맞춤 콘텐츠
- 서버 사이드 렌더링 (SSR)
HTTP API 응답
-
@ResponseBody(권장)1 2 3 4 5 6 7 8 9 10
// @ResponseBody로 HTTP 메시지 바디에 직접 JSON 데이터 반환 @ResponseStatus(HttpStatus.OK) @ResponseBody @GetMapping("/response-body-json-v2") public HelloData responseBodyJsonV2() { HelloData helloData = new HelloData(); helloData.setUsername("userA"); helloData.setAge(20); return helloData; }
- 동작 방식
@ResponseBody는 HTTP 메시지 바디에 직접 입력@ResponseStatus로 응답 코드 설정- 동적으로 상태 코드를 변경해야 하면
ResponseEntity사용
- 사용
- REST API 개발
- 단일 페이지 애플리케이션 (SPA)
- 모바일 앱 백엔드
- 마이크로서비스 간 통신
- HTTP 응답 처리 전체 코드
HTTP 메시지 컨버터
동작 원리
1
2
3
4
5
6
7
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
T read(Class<? extends T> clazz, HttpInputMessage inputMessage);
void write(T t, MediaType contentType, HttpOutputMessage outputMessage);
}
canRead(),canWrite()- 메시지 컨버터가 해당 클래스와 미디어 타입을 지원하는지 체크
read(),write()- 메시지 컨버터를 통해 메시지를 읽고 쓰는 기능
스프링 부트 기본 메시지 컨버터
1
2
3
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
ByteArrayHttpMessageConverter- 클래스 타입
byte[]
- 미디어 타입
*/*
- 클래스 타입
StringHttpMessageConverter- 클래스 타입
String
- 미디어 타입
*/*
- 클래스 타입
MappingJackson2HttpMessageConverter- 클래스 타입
- 객체 또는
HashMap
- 객체 또는
- 미디어 타입
application/json
- 클래스 타입
적용 시점
- 요청
@RequestBodyHttpEntity
- 응답
@ResponseBodyResponseEntity
요청 데이터 읽기
flowchart TD
A[HTTP 요청] --> B[컨트롤러에서 @RequestBody 사용]
B --> C{canRead 호출}
C -->|클래스 타입 확인| D[대상 클래스 체크]
C -->|미디어 타입 확인| E[Content-Type 체크]
D --> F{조건 만족?}
E --> F
F -->|예| G[read 호출]
F -->|아니오| H[다음 컨버터 시도]
G --> I[객체 생성 및 반환]
style A fill:#E3F2FD,stroke:#1976D2
style C fill:#FFF3E0,stroke:#F57C00
style G fill:#E8F5E9,stroke:#388E3C
style I fill:#E8F5E9,stroke:#388E3C
- HTTP 요청이 오고, 컨트롤러에서
@RequestBody,HttpEntity파라미터 사용 - 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해
canRead()호출- 대상 클래스 타입 확인
- HTTP 요청의 Content-Type 미디어 타입 확인
canRead()조건을 만족하면read()호출하여 객체 생성 후 반환
응답 데이터 생성
flowchart TD
A[컨트롤러 @ResponseBody 반환] --> B{canWrite 호출}
B -->|클래스 타입 확인| C[대상 클래스 체크]
B -->|미디어 타입 확인| D[Accept 헤더 체크]
C --> E{조건 만족?}
D --> E
E -->|예| F[write 호출]
E -->|아니오| G[다음 컨버터 시도]
F --> H[HTTP 응답 바디 생성]
style A fill:#E3F2FD,stroke:#1976D2
style B fill:#FFF3E0,stroke:#F57C00
style F fill:#E8F5E9,stroke:#388E3C
style H fill:#E8F5E9,stroke:#388E3C
- 컨트롤러에서
@ResponseBody,HttpEntity로 값 반환 - 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해
canWrite()호출- 대상 클래스 타입 확인
- HTTP 요청의 Accept 미디어 타입 확인
canWrite()조건을 만족하면write()호출하여 HTTP 응답 메시지 바디에 데이터 생성
요청 매핑 핸들러 어댑터 구조
전체 흐름

ArgumentResolver
- 정식 명칭
HandlerMethodArgumentResolver
1 2 3 4 5
public interface HandlerMethodArgumentResolver { boolean supportsParameter(MethodParameter parameter); Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory); }
- 역할
- 애노테이션 기반 컨트롤러의 다양한 파라미터를 유연하게 처리
- 동작 과정
supportsParameter()로 해당 파라미터를 지원하는지 체크- 지원하면
resolveArgument()호출하여 실제 객체 생성 - 생성된 객체가 컨트롤러 호출 시 넘어감
- 지원 파라미터
HttpServletRequestModel@RequestParam@ModelAttribute@RequestBodyHttpEntity- 30개 이상
ReturnValueHandler
- 정식 명칭
HandlerMethodReturnValueHandler
- 역할
- 응답 값을 변환하고 처리
- 동작 과정
- 컨트롤러에서 반환된 값을 HTTP 응답으로 변환
- 지원 반환 타입
ModelAndView@ResponseBodyHttpEntityString- 10개 이상
HTTP 메시지 컨버터 위치
- 요청
@RequestBody,HttpEntity를 처리하는ArgumentResolver가 메시지 컨버터를 사용- 요청 데이터를 읽음
- 응답
@ResponseBody,HttpEntity를 처리하는ReturnValueHandler가 메시지 컨버터를 사용- 응답 데이터를 생성
확장
- 스프링은 다음을 모두 인터페이스로 제공
HandlerMethodArgumentResolverHandlerMethodReturnValueHandlerHttpMessageConverter
-
필요하면 언제든지 기능을 확장할 수 있음
1 2 3 4 5 6 7 8 9 10 11 12 13 14
@Bean public WebMvcConfigurer webMvcConfigurer() { return new WebMvcConfigurer() { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { // ArgumentResolver 추가 } @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { // 메시지 컨버터 추가 } }; }
연습 문제
-
실제 서비스 개발에서
System.out.println대신 로깅 라이브러리를 사용하는 주된 이유가 무엇일까요?a. 로그 레벨 설정으로 출력 레벨을 조절할 수 있음
- 로깅 라이브러리는 레벨 설정으로 필요한 로그만 출력할 수 있고, 실제 운영 환경에서 시스템 자원을 효율적으로 사용하여 문제를 추적할 수 있게 도와줌
-
애플리케이션의 로깅 레벨을 INFO로 설정했다면, 어떤 레벨의 로그 메시지가 출력될까요?
a. INFO, WARN, ERROR
- 로깅 레벨은 성정된 레벨 이상(심각한)의 로그만 출력하도록 동작함
- 따라서 INFO 레벨로 설정하면 INFO, WARN, ERROR 로그가 보이게 됨
-
Spring MVC에서
@RequestMapping어노테이션으로 URL 경로 외에 어떤 정보를 함께 조건으로 사용하여 요청을 특정 컨트롤러 메서드에 매핑할 수 있을까요?a. HTTP 메서드 (GET, POST 등)
@RequestMapping은 URL 뿐만 아니라 HTTP 메서드(GET, POST 등), 파라미터 조건, 헤더 조건 등 다양한 조건으로 조합하여 매핑 정의가 가능함- 이를 통해 더 유연하고 구체적인 매핑이 가능함
-
/users/{userId}와 같은 형식으로 URL 경로 자체에 값을 포함시켜 요청을 처리하는 방식을 무엇이라고 부르나요?a. 경로 변수 (Path Variable)
- URL 경로를 변수로 넣는 방식을 경로 변수(Path Variable)라고 부름
- REST API 설계에서 특정한 리소스를 식별할 때 자주 사용되는 방법임
-
HTTP 요청 시 데이터를 전달하는 방식 중,
request.getParameter()로 편리하게 조회할 수 있는 방식은 무엇인가요?a. URL 쿼리 파라미터와 HTML Form 데이터
- 둘다 요청 파라미터 형식이기 때문에 URL에 붙은 쿼리 파라미터나 HTML
<form>태그로 전송하는 데이터는request.getParameter()등을 통해 동일하게 조회 가능
- 둘다 요청 파라미터 형식이기 때문에 URL에 붙은 쿼리 파라미터나 HTML
-
Spring MVC의
@ModelAttribute는 주로 어떤 목적으로 사용될까요?a. HTTP 요청 파라미터를 모아 자바 객체에 바인딩할 때
@ModelAttribute는 요청 파라미터(쿼리 스트링, 폼 데이터)의 이름과 객체의 필드 이름이 같을 때, 해당 객체의 세터(setter)를 호출하여 자동으로 값을 바인딩해주는 역할을 함
-
Spring MVC에서
@RequestBody나@ResponseBody어노테이션이 제대로 동작하기 위해 핵심적으로 필요한 내부 컴포넌트는 무엇일까요?a. HTTP Message Converter
- HTTP 메시지 컨버터는 HTTP 요청 본문의 데이터를 원하는 자바 객체로 변환하거나, 자바 객체를 HTTP 응답 본문으로 변환하는 역할을 함
@RequestBody와@ResponseBody는 이 컨버터를 사용함
-
클라이언트에서 HTTP 요청 본문에 JSON 데이터를 담아 보낼 때, Spring이 이를 적절한 자바 객체로 자동 변환하기 위해 주로 확인하는 HTTP 헤더는 무엇일까요?
a. Content-Type
- 클라이언트가 보내는 Content-Type 헤더는 요청 본문에 담긴 데이터의 타입을 알려줌
- Spring은 이 정보를 보고 어떤 HTTP 메시지 컨버터(예: JSON용 Jackson)를 사용할지 결정함
-
HTML 템플릿을 사용해 동적인 웹 페이지를 출력하는
@Controller와 달리, API 서버처럼 데이터를 HTTP 응답 본문에 직접 담아 보내는 컨트롤러에 주로 사용되는 어노테이션은 무엇일까요?a.
@RestController@RestController는@Controller와@ResponseBody를 합친 어노테이션으로, 뷰 템플릿 대신에 데이터(주로 JSON)을 응답 본문에 직접 담아 보내는 API 컨트롤러에 적합함
-
컨트롤러에서 HTTP 응답 본문 데이터와 함께 HTTP 상태 코드를 동적으로 변경하거나, 헤더 정보까지 세밀하게 제어하고 싶을 때 주로 사용하는 Spring의 반환 타입은 무엇일까요?
a.
ResponseEntityResponseEntity는 HTTP 응답 전체를 객체로 표현함- 응답 본문 데이터 외에 HTTP 상태 코드, 헤더 등을 프로그래밍적으로 설정하여 동적으로 제어할 수 있는 강력한 반환 타입
요약 정리
- 프로젝트 설정
- Spring Boot를 사용하여 간편하게 프로젝트 설정
- Jar 방식으로 패키징하여 내장 톰캣 사용
- 로깅
- SLF4J + Logback 사용
- 로그 레벨로 출력 조절
- 운영 환경에서 필수
System.out.println대신 로그 사용
- 요청 매핑
@RequestMapping및 HTTP 메서드별 애노테이션- PathVariable로 경로 변수 처리
- 다양한 조건부 매핑 지원
- 요청 데이터 처리
@RequestParam- 쿼리 파라미터, Form 데이터
@ModelAttribute- 요청 파라미터를 객체로 바인딩
@RequestBody- HTTP 메시지 바디 (JSON 등)
- 응답 데이터 생성
- 정적 리소스
- 뷰 템플릿 (Thymeleaf 등)
- HTTP API
@ResponseBodyResponseEntity@RestController
- HTTP 메시지 컨버터
- 요청과 응답에서 HTTP 메시지를 객체로 변환
@RequestBody,@ResponseBody에서 사용- JSON 처리
MappingJackson2HttpMessageConverter
- 핸들러 어댑터
- ArgumentResolver
- 컨트롤러 파라미터 처리
- ReturnValueHandler
- 컨트롤러 응답 처리
- HTTP 메시지 컨버터 연동
- ArgumentResolver