개발/Spring

[Spring] 컨트롤러 반환타입

함수형 인간 2025. 3. 27. 17:47

스프링 컨트롤러(Spring Controller) 반환 타입

스프링 MVC 컨트롤러 메소드는 클라이언트의 요청을 처리한 후, 어떤 방식으로 응답할지를 결정하기 위해 다양한 타입의 값을 반환할 수 있다. 반환 타입에 따라 스프링 MVC는 후속 처리(뷰 렌더링, HTTP 응답 메시지 변환 등)를 다르게 수행.

주요 반환 타입들을 정리하면 다음과 같다.

1. String

  • 의미: 뷰(View)의 논리적인 이름(Logical View Name)을 반환.
  • 동작: ViewResolver 설정에 따라 해당 논리적 이름에 매핑되는 실제 뷰 리소스(예: JSP 파일 경로, Thymeleaf 템플릿 경로)를 찾아 렌더링. 모델 데이터는 메소드 파라미터로 받은 Model 객체에 addAttribute() 등으로 추가.
  • 주요 사용처: 전통적인 웹 애플리케이션에서 HTML 페이지를 렌더링할 때 가장 흔하게 사용.
  • 특징:
    • redirect: 접두사: 지정된 URL로 리다이렉트 응답(HTTP 302)을 보낸다. 예: return "redirect:/users/list";
    • forward: 접두사: 현재 요청을 다른 URL로 포워드. (내부적으로 이동) 예: return "forward:/users/detail";
Java
 
@Controller
public class UserController {
    @GetMapping("/users/{id}")
    public String getUserProfile(@PathVariable Long id, Model model) {
        User user = userService.findById(id);
        model.addAttribute("user", user);
        return "user/profile"; // 논리적 뷰 이름 반환 (예: /WEB-INF/views/user/profile.jsp)
    }

    @PostMapping("/users")
    public String createUser(User user) {
        userService.save(user);
        return "redirect:/users/" + user.getId(); // 생성 후 상세 페이지로 리다이렉트
    }
}

 

2. void

  • 의미: 명시적인 반환 값이 없음을 나타냄.
  • 동작:
    • HttpServletResponse 또는 OutputStream/Writer 사용: 메소드 파라미터로 HttpServletResponse나 OutputStream/Writer 등을 받아 직접 응답 데이터를 쓰거나 제어할 때 사용. (예: 파일 다운로드)
    • @ResponseStatus 사용: 메소드에 @ResponseStatus 애노테이션이 있으면 해당 상태 코드로 빈 응답 본문을 보냄.
    • RequestToViewNameTranslator: (설정된 경우) 요청 경로를 기반으로 뷰 이름을 암묵적으로 결정하여 렌더링. (최근에는 잘 사용되지 않음)
  • 주요 사용처: HTTP 응답을 직접 처리해야 하거나, 상태 코드만 반환하고 본문이 없는 경우, 비동기 처리 시작 등.
Java
 
@Controller
public class DownloadController {
    @GetMapping("/download")
    public void downloadFile(HttpServletResponse response) throws IOException {
        // response 헤더 설정 (파일 다운로드)
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=\"data.zip\"");

        try (OutputStream os = response.getOutputStream()) {
            // 파일 데이터를 OutputStream에 쓴다
            fileService.writeFileToStream(os);
        }
    }

    @DeleteMapping("/resources/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT) // 204 No Content 반환
    public void deleteResource(@PathVariable Long id) {
        resourceService.deleteById(id);
        // 본문 없이 상태 코드만 반환
    }
}

3. ModelAndView

  • 의미: 모델 데이터(Model)와 뷰 이름(View Name)을 함께 가지는 객체.
  • 동작: ModelAndView 객체에 포함된 뷰 이름으로 뷰를 찾고, 모델 데이터를 뷰에 전달하여 렌더링.
  • 주요 사용처: 컨트롤러 로직 내에서 동적으로 뷰 이름이나 모델 데이터를 결정해야 할 때 유용. String 반환 타입과 Model 파라미터를 사용하는 방식보다 좀 더 명시적으로 뷰와 모델을 함께 반환.
Java
 
@Controller
public class ProductController {
    @GetMapping("/products/{id}")
    public ModelAndView getProductDetail(@PathVariable Long id) {
        Product product = productService.findById(id);
        ModelAndView mav = new ModelAndView("product/detail"); // 뷰 이름 설정
        mav.addObject("product", product); // 모델 데이터 추가
        return mav;
    }
}

 

4. POJO (Plain Old Java Object) / 컬렉션 (List, Map 등)

  • 의미: DTO(Data Transfer Object), 도메인 객체, List, Map 등 자바 객체를 직접 반환.
  • 동작:
    • @ResponseBody 애노테이션이 메소드에 있거나, 클래스 레벨에 @RestController 애노테이션이 있어야 한다.
    • 반환된 자바 객체는 HttpMessageConverter (주로 Jackson 라이브러리)에 의해 JSON 또는 XML 등의 형식으로 변환되어 HTTP 응답 본문(Body)에 쓰여진다.
  • 주요 사용처: RESTful API에서 클라이언트에게 데이터를 JSON 형태로 제공할 때 주로 사용.
Java
 
@RestController // @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserApiController {

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        // User 객체가 JSON으로 변환되어 응답 본문에 담김
        return userService.findById(id);
    }

    @GetMapping
    public List<User> getAllUsers() {
        // List<User> 객체가 JSON 배열로 변환되어 응답 본문에 담김
        return userService.findAll();
    }
}

5. ResponseEntity<T>

  • 의미: HTTP 응답 전체를 표현하는 객체. 응답 본문(Body) 데이터 T, HTTP 상태 코드(Status Code), 응답 헤더(Headers)를 포함.
  • 동작: @ResponseBody 애노테이션을 사용한 것과 유사하게 본문 데이터 T는 HttpMessageConverter를 통해 변환된다. 추가적으로 상태 코드와 헤더를 직접 설정하여 응답을 제어할 수 있다.
  • 주요 사용처: REST API에서 응답 상태 코드나 헤더를 동적으로 설정해야 할 때 매우 유용. (예: 생성(201), 실패(4xx, 5xx), 특정 헤더 추가 등)
Java
 
@RestController
@RequestMapping("/api/items")
public class ItemApiController {

    @GetMapping("/{id}")
    public ResponseEntity<Item> getItem(@PathVariable Long id) {
        Item item = itemService.findById(id);
        if (item == null) {
            // return new ResponseEntity<>(HttpStatus.NOT_FOUND); // 404 Not Found
            return ResponseEntity.notFound().build();
        }
        // return new ResponseEntity<>(item, HttpStatus.OK); // 200 OK 와 함께 Item 데이터 반환
        return ResponseEntity.ok(item);
    }

    @PostMapping
    public ResponseEntity<Item> createItem(@RequestBody Item item) {
        Item savedItem = itemService.save(item);
        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(URI.create("/api/items/" + savedItem.getId()));
        // 201 Created 와 함께 Location 헤더, 생성된 Item 데이터 반환
        return new ResponseEntity<>(savedItem, headers, HttpStatus.CREATED);
    }
}

6. HttpEntity<T>, RequestEntity<T>

  • ResponseEntity와 유사하게 HTTP 요청/응답의 헤더와 본문을 다루는 객체입니다. ResponseEntity는 HttpEntity를 상속받아 응답에 특화된 기능을 제공.
  • 컨트롤러 반환 타입으로는 주로 ResponseEntity가 더 명확하고 많이 사용된다.

7. HttpHeaders

  • 의미: HTTP 응답 헤더 정보만 반환.
  • 동작: 응답 본문은 비어있게 되며, 지정된 헤더 정보만 응답에 포함됨. 상태 코드는 기본적으로 200 OK이지만 @ResponseStatus 등으로 변경 가능.
  • 주요 사용처: HEAD 요청 처리, 특정 헤더 정보만 전달해야 하는 경우 등.
Java
 
@Controller
@RequestMapping("/headers")
public class HeaderController {
    @GetMapping("/custom")
    public HttpHeaders getCustomHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Custom-Header", "MyValue");
        headers.setCacheControl("no-cache");
        return headers; // 본문 없이 헤더만 반환
    }
}

8. 비동기 처리 관련 (Callable<T>, DeferredResult<T>, Mono<T>, Flux<T> 등)

  • 의미: 요청 처리를 비동기적으로 수행하고자 할 때 사용.
  • 동작: 컨트롤러 메소드는 즉시 반환되어 서블릿 스레드를 반납하고, 실제 작업은 별도의 스레드에서 수행됨. 작업 완료 시 결과가 응답으로 전송됨.
  • Callable<T>: 비교적 간단한 비동기 작업에 사용. 반환 타입 T는 위에서 설명한 동기 반환 타입 중 하나가 될 수 있다.
  • DeferredResult<T>: 외부 이벤트(메시지 큐, 스케줄러 등)에 의해 비동기 작업 결과가 결정될 때 유용.
  • Mono<T>, Flux<T>: 스프링 WebFlux (Reactive Stack) 환경에서 사용되는 반응형 타입.
  • 주요 사용처: 시간이 오래 걸리는 I/O 작업, 외부 API 호출 등을 처리하여 서블릿 스레드 고갈을 막고 시스템 처리량을 높이고자 할 때 사용.
Java
 
@RestController
public class AsyncController {
    @GetMapping("/async-callable")
    public Callable<String> processLongTaskCallable() {
        return () -> {
            Thread.sleep(3000); // 시간이 오래 걸리는 작업 시뮬레이션
            return "Callable result after 3 seconds";
        }; // 작업 완료 후 String 결과가 JSON 응답으로 변환됨
    }

    @GetMapping("/async-deferred")
    public DeferredResult<ResponseEntity<String>> processLongTaskDeferred() {
        DeferredResult<ResponseEntity<String>> deferredResult = new DeferredResult<>(5000L); // 타임아웃 5초

        // 별도 스레드에서 작업 수행 후 결과 설정
        new Thread(() -> {
            try {
                Thread.sleep(2000);
                deferredResult.setResult(ResponseEntity.ok("Deferred result after 2 seconds"));
            } catch (InterruptedException e) {
                deferredResult.setErrorResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error occurred"));
            }
        }).start();

        deferredResult.onTimeout(() -> deferredResult.setErrorResult(ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body("Request timeout")));
        return deferredResult;
    }
}

선택 기준 요약

  • HTML 페이지 렌더링: String (주로 사용), ModelAndView
  • REST API (JSON/XML 데이터 반환): POJO/Collection (with @RestController), ResponseEntity<T> (상태/헤더 제어 필요시)
  • 파일 다운로드 등 직접 응답 제어: void (with HttpServletResponse)
  • 상태 코드/헤더만 중요: ResponseEntity<?> (body 없음), void (with @ResponseStatus), HttpHeaders
  • 비동기 처리: Callable<T>, DeferredResult<T>, Mono<T>, Flux<T>