만쥬의 개발일기

ResponseEntity란?

ResponseEntity를 설명하기 전에, 먼저 HttpEntity를 알아야한다.

HttpEntity란 HTTP 요청(Request) 또는 응답(Response)에 해당하는 HttpHeader HttpBody를 포함하는 클래스이다. 

 

그리고 HttpEntity 클래스를 상속받아 구현한 클래스가 RequestEntity, ResponseEntity 클래스이다. ResponseEntity는 사용자의 HttpRequest에 대한 응답 데이터를 포함하는 클래스이다. 따라서 HttpStatus, HttpHeaders, HttpBody를 포함한다. 

 

ResponseEntity의 생성자를 보면 this( )를 통해서 매개변수가 3개인 생성자를 호출해 결국엔 아래 보이는 매개변수가 3개인 생성자로 가게된다. 

 

인자를 한개만 넘겨주면 status, 두개를 넘겨주면 body와 status, 세개를 넘겨주면 header와 body와 status가 된다.

 

public ResponseEntity(HttpStatus status) {
	this(null, null, status);
}
public ResponseEntity(@Nullable T body, HttpStatus status) {
	this(body, null, status);
}
	public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {
		super(body, headers);
		Assert.notNull(status, "HttpStatus must not be null");
		this.status = status;
	}

 

사용 예제

ResponEntity를 이용해서 클라이언트에게 응답을 보내는 예제를 정리해보자.

 

import lombok.Data;

@Data
public class Message {

    private StatusEnum status;
    private String message;
    private Object data;

    public Message() {
        this.status = StatusEnum.BAD_REQUEST;
        this.data = null;
        this.message = null;
    }
}
public enum StatusEnum {

    OK(200, "OK"),
    BAD_REQUEST(400, "BAD_REQUEST"),
    NOT_FOUND(404, "NOT_FOUND"),
    INTERNAL_SERER_ERROR(500, "INTERNAL_SERVER_ERROR");

    int statusCode;
    String code;

    StatusEnum(int statusCode, String code) {
        this.statusCode = statusCode;
        this.code = code;
    }
}

위와 같이 상태코드와 메세지, 데이터를 담은 Message 클래스와 상태코드의 몇가지 예시를 포함한 enum클래스를 생성해보자.

 

@RestController
public class UserController {
    private UserDaoService userDaoService;

    public UserController(UserDaoService userDaoService) {
        this.userDaoService = userDaoService;
    }

    @GetMapping(value = "/user/{id}")
    public ResponseEntity<Message> findById(@PathVariable int id) {
        User user = userDaoService.findOne(id);
        Message message = new Message();
        HttpHeaders headers= new HttpHeaders();
        headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));

        message.setStatus(StatusEnum.OK);
        message.setMessage("성공 코드");
        message.setData(user);

        return new ResponseEntity<>(message, headers, HttpStatus.OK);
    }


}

 

그리고 general 타입이기 때문에 위와 같이 body에 들어갈 타입을 직접 정하고, 바디와 헤더와 status를 지정한뒤 반환할 수 있다.

 

다양한 활용 방법

Return은 생성자보다는 빌더 패턴 사용

생성자 패턴

return new ResponseEntity(body, headers, HttpStatus.valueOf(200));

빌더 패턴

return ResponseEntity.ok()
	.headers(headers)
	.body(body);

 

ResponseEntity.ok()는 status로 성공을 의미하는 OK(200 code)를 포함하고, 정적 팩토리 메서드이기때문에 빌더 패턴을 사용하는 것이 더 직관적이고 유지보수에 좋다.

 

ResponseEntity의 Body 타입을 명시하라

public ResponseEntity getUser() {

위와 같이 메소드를 작성하면 ResponseEntity의 타입을 작성하지 않은 것이라 Object(모든 자바의 부모 클래스)를 Return으로 받게된다.

 

public ResponseEntity<Object> getUser() {

따라서 위 코드와 기능적으로는 동일하다.

ResponseEntity의 타입은 명시하지 않으면 Object 타입을 Return 해주기 때문이다.

하지만 대부분의 상황에서는 유지보수를 위해 타입을 명시해 주는 것이 직관적이므로 좋다.

 

타입을 여러 개 받고 싶다면 Return 타입은 Object 대신 와일드카드 사용

보통 타입을 여러개 받고 싶은 경우 아래와 같이 Object나 와일드카드를 사용할 수 있다. 이러한 방법은 API로 제공되는 라이브러리가 아니라 개발 구성원들끼리만 공유될 경우 개발 생산성을 높일 수 있기 때문에 좋은 선택이 될 수 있다고 한다.

반환 타입이 명확하지 않아도 Return 된다

public ResponseEntity<Object> getUsers() {
    List<User> users = userService.getUsers();
    return ResponseEntity.ok(users);
}

 

public ResponseEntity<?> getUsers() {
    List<User> users = userService.getUsers();
    return ResponseEntity.ok(users);
}

Return 할 때 객체의 타입이 명확하지 않을 때는 Object를 사용하는 것보다 와일드카드(?)를 사용하는 것이 좋다고 한다.

정해지지 않은 타입을 반환한다는 점에서는 Object나 와일드카드나 같은 기능을 하지만 와일드카드를 사용하면 반환할 수 있는 객체의 타입이 명확하지 않아도 사용이 가능하다.

 

즉, 위 코드를 아래와 같이 쓸 수 있다.

@GetMapping("/users")
public ResponseEntity<?> getUsers() {
    List<?> users = userService.getUsers();
    return ResponseEntity.ok(users);
}

List를 구성하는 객체의 타입이 바뀌어도 코드를 그대로 사용 가능하다는 측면에서 유지보수에 큰 강점이 있다.

 

와일드카드, Object vs 사용자 객체

와일드카드나 Object를 이용해서 API를 만든다면 해당 API를 사용하는 사용자는 불필요한 형변환 작업을 해줘야 하는 단점이 존재한다.

 

아래는 (List<User>)로 불필요한 형변환을 하는 예제이다.

public ResponseEntity<Object> getUsers() {
    List<User> users = userService.getUsers();
    return ResponseEntity.ok(users);
}
ResponseEntity<Objects> response = restTemplate.getForEntity("/users", Objects.class);
List<User> users = (List<User>) response.getBody();

그래서 와일드카드가 아닌 generic타입 파라미터를 사용한다면 컴파일 타임에 자동 형변환이 되므로 개발자는 따로 형변환을 해줄 필요가 없다.

@GetMapping("/")
public ResponseEntity<T> getUser() {
    T user = userService.getUser();
    return ResponseEntity.ok(user);
}

 

 

하지만 API 설계 측면에서는 타입 파라미터를 이용하는 것보다 명시적으로 사용자 객체를 지정해 주는 것이 더 좋으므로, 결론적으로는 상황에 맞는 설계와 개발을 하자.

@GetMapping("/users")
public ResponseEntity<User> getUser() {
    User user = userService.getUsers();
    return ResponseEntity.ok(user);
}

 

reference

profile

만쥬의 개발일기

@KangManJoo

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!