Spring Boot로 API를 개발하다 보면, 모든 컨트롤러의 응답을 일관된 JSON 구조로 반환해야 할 때가 있다.
예를 들어, 다음과 같은 응답 형식을 유지하고 싶다고 가정해 보면,
{
"result": {
"resultCode": "Success",
"resultMsg": "조회 성공"
},
"payload": {
"data": [
{ "boardNo": "101", "boardTitle": "공지사항 1" },
{ "boardNo": "102", "boardTitle": "공지사항 2" }
],
"totalCount": 2
}
}
하지만 일반적인 컨트롤러는 보통 Map <String, Object>나 DTO 객체를 반환하므로, 위처럼 자동으로 감싸지 지는 않는다.
일반적인 컨트롤러에서는 이렇게 데이터가 출력된다.
{
"data": [
{ "boardNo" "101", "boardTitle": "공지사항 1" },
{ "boardNo": "102", "boardTitle": "공지사항 2" }
],
"totalCount": 2
}
이를 해결하기 위해 ResponseBodyAdvice를 활용하면 모든 컨트롤러의 응답을 일관된 구조로 감싸줄 수 있다.
1. ResponseBodyAdvice란?
Spring은 컨트롤러의 응답 데이터를 **HttpMessageConverter**를 사용하여 JSON, XML 등의 형식으로 변환한다.
이 과정에서 ResponseBodyAdvice 인터페이스를 구현하면, 변환 직전에 데이터를 가공할 수 있다.
즉, 모든 컨트롤러의 응답을 자동으로 감싸는 기능을 구현할 수 있다!!
2. ResponseBodyAdvice 동작 방식
💡 ResponseBodyAdvice의 동작 흐름
- 사용자가 API를 호출하면 컨트롤러가 데이터를 반환
- Spring이 HttpMessageConverter를 통해 JSON으로 변환하기 직전 ResponseBodyAdvice가 개입
- 응답 데이터를 우리가 원하는 JSON 형식으로 감싸도록 변환
- 최종적으로 클라이언트에게 변환된 JSON 응답이 전달
3. ResponseBodyAdvice 구현
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
public class AppResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
org.springframework.http.server.ServerHttpRequest request,
org.springframework.http.server.ServerHttpResponse response) {
if ("handleException".equals(returnType.getMethod().getName()) ||
"userHandleException".equals(returnType.getMethod().getName())) {
return body;
}
// 응답 데이터가 Map이 아닌 경우, 기본적으로 오류 응답 구조로 감싸기
if (!(body instanceof Map)) {
return createResponse("Error", "에러 메세지", null);
}
return createResponse("Success", "조회 성공", body);
}
/**
* 응답 데이터를 감싸는 공통 메서드
*/
private Map<String, Object> createResponse(String code, String message, Object data) {
Map<String, Object> response = new HashMap<>();
Map<String, String> result = new HashMap<>();
result.put("resultCode", code);
result.put("resultMsg", message);
response.put("result", result);
response.put("payload", data);
return response;
}
}
💡 주요 기능
- supports() : 모든 컨트롤러 응답에 적용되도록 true 반환
- beforeBodyWrite() : 응답 데이터를 감싸서 변환하는 로직 구현
4. ResponseBodyAdvice 적용 결과
✔ Controller에서 Map 반환
@RestController
@RequestMapping("/api/board")
public class BoardController {
@GetMapping("/list")
public Map<String, Object> getBoardList() {
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> boardList = new ArrayList<>();
Map<String, Object> board1 = new HashMap<>();
board1.put("boardNo", "101");
board1.put("boardTitle", "공지사항 1");
Map<String, Object> board2 = new HashMap<>();
board2.put("boardNo", "102");
board2.put("boardTitle", "공지사항 2");
boardList.add(board1);
boardList.add(board2);
result.put("data", boardList);
result.put("totalCount", boardList.size());
return result;
}
}
하면 맨 위에 나와있는 것처럼 데이터가 가공되어서 출력된다.
5. ResponseBodyAdvice를 활용하면 좋은 점
✅ 모든 API 응답 형식을 일괄적으로 적용할 수 있음
✅ 컨트롤러에서 불필요한 응답 가공 코드를 제거하여 유지보수성 향상
✅ 예외 응답과 정상 응답을 구분하여 처리 가능
🎯 즉, API 응답 형식이 일관되게 유지되면서도 컨트롤러의 코드가 간결해짐
'Frameworks > Spring(개인)' 카테고리의 다른 글
| [Spring 개인] Spring_Servlet / HTTP Request (0) | 2024.06.10 |
|---|---|
| [Spring 개인] Web Server, Web Application Sever (1) | 2024.06.08 |
| [Spring 개인] 스프링 컨테이너 / 스프링 빈 조회 (0) | 2024.06.07 |
| [Spring 개인] Spring Security JWT (Access/Refresh Token) (1) | 2024.05.30 |
| [Spring 개인] Spring Security OAuth2 + JWT (0) | 2024.05.29 |



