1. 추가 실습 컴포넌트별 핵심 내용 및 코드 요약
① JSON 직렬화 제어 (Jackson 라이브러리 활용)
- 목적: 서버가 자바 객체를 JSON으로 바꿀 때(직렬화), 보안상 민감한 필드를 숨기거나 날짜 포맷을 가독성 있게 제어하기 위함.
- 핵심 동작: 디스패처 서블릿 단계에서
HttpMessageConverter(Jackson)가 객체를 읽을 때 어노테이션 규칙을 적용합니다. - 핵심 코드 (
UserResponse.java):
@JsonIgnore // 클라이언트 응답 JSON에서 이 필드를 완전히 제외(숨김)
private String password;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") // 날짜 객체를 예쁜 문자열 포맷으로 변환
private LocalDateTime createdAt;
② CORS (Cross-Origin Resource Sharing) 설정
- 목적: 프론트엔드(
localhost:3000)와 백엔드(localhost:8080)의 포트 주소가 달라 브라우저가 요청을 차단하는 보안 문제를 해결하기 위함. - 핵심 동작: 디스패처 서블릿이 프론트엔드의 탐색용 요청(Preflight)을 받아 허용 헤더를 주입합니다.
- 핵심 코드 (
WebConfig.java):
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE");
}
③ 파일 업로드 (MultipartResolver)
- 목적: 클라이언트가 보낸 바이너리 파일 데이터(
multipart/form-data)를 서버에서 안전하게 수신하고 저장하기 위함. - 핵심 동작: 디스패처 서블릿이 내부의
MultipartResolver를 가동하여 바이너리 데이터를 자바의MultipartFile객체로 변환해 줍니다. - 핵심 코드 (
UserController.java):
@PostMapping("/api/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
file.transferTo(new File("/Users/oxxultus/develope/upload/" + originalFilename));
return "업로드 완료";
}
2. @RequestParam vs @RequestBody 작동 원리 비교
두 어노테이션 모두 실행 타이밍은 “인터셉터 문이 열린 후, 핸들러 어댑터가 실제 컨트롤러 메서드를 깨우기 바로 직전”으로 완벽히 동일합니다. 하지만 내부에서 사용하는 도구와 성격이 다릅니다.
| 구분 | @RequestParam |
@RequestBody |
|---|---|---|
| 주요 대상 | URL 쿼리 스트링 (?id=oxxultus) 또는 Form 데이터 |
HTTP 바디에 실려오는 JSON 텍스트 |
| 내부 구동 도구 | RequestParamMethodArgumentResolver |
RequestResponseBodyMethodProcessor |
| 메시지 컨버터 사용 여부 | 사용 안 함 ❌ 톰캣의 HttpServletRequest.getParameter()를 활용해 값을 곧바로 추출 |
사용함 ⭕HttpMessageConverter(Jackson)를 거쳐 무거운 역직렬화 연산 수행 |
| 특징 | 필요한 Key 값만 쏙쏙 뽑아오며, 자바의 기본 타입(int, String 등)으로 자동 형변환(Type Conversion)까지 지원 | JSON 전체 구조를 자바의 하나의 거대한 DTO 객체로 통째로 변환 |
톰캣부터 컨트롤러까지의 시퀀스
[ 클라이언트의 HTTP 요청 전송 (텍스트 또는 파일 패킷) ]
│
▼
======================= [ 톰캣 (Tomcat) / 서블릿 컨테이너 구역 ] =======================
0. HttpServletRequest / Response 객체 생성 (바이너리/텍스트 데이터 포함)
│
▼
0-1. [ 서블릿 필터 검문 (Filter) ] -> 거대한 보안/인코딩 인프라 검사
│
▼
======================= [ 스프링 (Spring) / IoC 컨테이너 구역 ] =======================
│
▼ [ 디스패처 서블릿 (DispatcherServlet) 입장 ]
│
1. 핸들러 매핑 조회 -> URL을 처리할 컨트롤러 빈 매칭
2. 핸들러 어댑터 조회 -> 해당 컨트롤러를 실행할 비서(어댑터) 대기
│
▼
3. [ 인터셉터 검문 (Interceptor .preHandle()) ]
"어댑터에게 실행 명령을 내리기 바로 직전 가로챔 false 반환 시 이후 과정 모두 가동 안 함"
│
└─── [ 인터셉터 true 반환 시 (통과) ]
│
▼
4. 핸들러 어댑터에 실행 명령 하달 (.handle() 호출)
"컨트롤러를 깨우기 위해 매개변수(Parameter)에 바칠 선물 상자들을 준비하자!"
│
├─ case A: 메서드에 @RequestBody가 있는 경우
│ - HttpMessageConverter(Jackson)를 깨워 JSON 텍스트를 DTO 객체로 역직렬화
│ - 직후 @Valid 가 있다면 데이터 제약 조건 검증 실행 (실패 시 @RestControllerAdvice로 역류)
│
└─ case B: 메서드에 @RequestParam이 있는 경우
- 메시지 컨버터 없이, 톰캣의 Request 객체에서 getParameter()로 값 추출
- 변수 타입에 맞게 데이터 형변환(Type Conversion) 처리
│
▼
5. [ 실제 컨트롤러 빈 실행 (UserController) ]
"핸들러 어댑터가 완벽하게 준비해 준 매개변수들을 주입받으며 비즈니스 로직 가동"
│
▼
6. 컨트롤러 응답 반환 및 Jackson 직렬화 제어
"컨트롤러가 반환한 응답 DTO를 나가는 HttpMessageConverter가 JSON으로 변환할 때,"
"@JsonIgnore가 붙은 필드는 숨기고, @JsonFormat이 붙은 날짜는 지정된 포맷 문자열로 직렬화하여 채움"
│
▼
======================= [ 톰캣 구역 반환 및 패킷 최종 조립 후 발송 ] =======================
마치며
이로써 요청을 받아들이는 앞단(필터, 인터셉터), 데이터를 가공하고 검증하는 중간 비서(핸들러 어댑터, ArgumentResolver, @Valid), 그리고 나가는 문을 제어하는 뒷단(Jackson 직렬화, 예외 처리기)까지 스프링 웹 구조를 조금 알게되었다.