스터디/Spring

[Spring Boot] Controller 설계

menuhwang 2022. 8. 22. 18:29

Controller


 

 Controller 는 클라이언트의 요청을 비즈니스 로직과 연결시켜주는 다리 역할을 한다.

앞서 작성한 스프링 부트 동작 방식 중 핸들러에 해당한다.

 

 

@Controller & @RestController


@Controller 와 @RestController의 차이점을 알아보자.

 

먼저, @RestController 어노테이션 코드를 보면

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
	// (...)
	@AliasFor(annotation = Controller.class)
	String value() default "";

}

@Controller 어노테이션과 @ResponseBody 어노테이션이 포함되어있는 것을 알 수 있다.

 

@ResponseBody 어노테이션은 응답을 JSON 형식으로 바꿔주는 역할을 한다.

 

즉, @RestController 는 응답으로 객체를 전달할 때 JSON 형식으로 변환하고 헤더의 Content-Type을 application/json으로 설정해 전해준다.

 

 

 

 

 

URI & HTTP 메서드 매핑


 Controller 에 작성한 메서드는 URI 및 HTTP 메서드 매핑이 필요하다.

 

 

@RequestMapping


@RequestMapping 어노테이션을 사용하여 매핑하는 방법이다.

@RestController
public class MyController {
	// http://localhost:8080/api/hello
    @RequestMapping("/api/hello")
    public String hello() {
        return "Hello";
    }
}

@RequestMapping 어노테이션에 URI를 설정해주면 해당 URI로 들어온 요청을 처리해준다.

 

@RequestMapping 어노테이션을 메서드가 아닌 클래스에 붙여줄 수도 있다.

@RestController
@RequestMapping("/api")
public class MyController {
	// http://localhost:8080/api/hello
    @RequestMapping("/hello")
    public String hello() {
        return "Hello";
    }
}

이처럼 클래스에 @ReqeustMapping 어노테이션을 붙이면 해당 클래스 이하 메서드에 공통적으로 적용된다.

 

하지만, 위 코드는 HTTP 메서드에 대한 설정을 해주지 않아 모든 HTTP 메서드를 전부 처리하게 된다.

 

 value 옵션에는 매핑할 URI를, method 옵션에는 매핑할 HTTP 메서드를 설정해주면 된다.

@RestController
@RequestMapping("/api")
public class MyController {
	// http://localhost:8080/api/hello, only GET Method
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String hello() {
        return "Hello";
    }
}

 

그러나, 스프링 4.3 부터는 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping 어노테이션들로 HTTP 메서드 매핑을 한 번에 설정해 줄 수 있다.

@RequestMapping 활용
@GetMapping, @PostMapping, ... 이 있다고 @RequestMapping 이 불필요하진 않다.
만약, HTTP 메서드를 하나가 아닌 여러 개 설정해주고 싶을때 method 옵션에 메서드를 배열로 입력해주면 된다.
+ value 도 String[] 타입으로 입력해줄 수 있다.
@RequestMapping(value = "hello", method = {RequestMethod.GET, RequestMethod.POST})​

 

 

 

매개변수


Get 메서드와 Delete 메서드의 경우 일반적으로 URL에 데이터를 담는다.

URL에 데이터를 담는 방법은 URL 경로 자체에 담는 방법과 쿼리 형식으로 담는 방법이 있다.

 

@PathVariable

 먼저 URL 자체에 담겨온 데이터를 가져오는 방법이다.

 

@RestController
@RequestMapping("/api")
public class MyController {
	// http://localhost:8080/api/hello/menuhwang
    @GetMapping(value = "/hello/{name}")
    public String hello(@PathVariable String name) {
        return "Hello, " + name; // Hello, menuhwang
    }
}

 

 URI를 설정할 때 가져올 값의 위치에 중괄호로 감싼 변수명을 적어주고, 메서드 파라미터에 @PathVariable 어노테이션과 함께 작성해준다.

 

이때 파라미터의 변수명과 URI 안의 변수명이 일치해야 한다.

 

라고 했지만 꼭 일치해야만 동작하는 것은 아니다.

 

@RestController
@RequestMapping("/api")
public class MyController {
	// http://localhost:8080/api/hello/menuhwang
    @GetMapping(value = "/hello/{name}")
    public String hello(@PathVariable("name") String userName) {
        return "Hello, " + userName; // Hello, menuhwang
    }
}

@PathVariable 에 변수명을 입력하면 URI 안에 해당 변수명을 가져와준다.

 

 

@RequestParam

 두 번째 방법으로는 쿼리 형식으로 담겨온 데이터를 가져오는 방법이다.

 

쿼리형식은 URL 과 ? 뒤 Key 와 Value 형태로 데이터가 담겨오는 방식이다.

http://www.sample.com/test?key=value&mode=test

 

@RestController
@RequestMapping("/api")
public class MyController {
	// http://localhost:8080/api/hello?name=menuhwang&age=5
    @GetMapping(value = "/hello")
    public String hello(@RequestParam("name") String name, @RequestParam("age") int age) {
        return "Hello, " + name + ", " + age; // Hello, menuhwang, 5
    }
}

@PathVariable 과 마찬가지로 변수명과 쿼리 키값이 같다면 @RequestParam 에 변수명을 지정해 주지 않아도 된다.

 

 

Map 활용

 혹시 어떤 쿼리 값으로 요청받을지 알 수 없다면 Map 자료형을 사용하면 된다.

@RestController
@RequestMapping("/api")
public class MyController {
	// http://localhost:8080/api/hello?name=menuhwang&weight=60
    @GetMapping(value = "/hello")
    public  String hello(@RequestParam Map<String, Object> param) {
        StringBuilder sb = new StringBuilder();
        param.entrySet().forEach(map -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });
        return sb.toString(); // name : menuhwang weight : 60
    }
}

 

URL? URI?

 

URL URI 차이 [결론 못냄]

URL과 URI의 차이 URL URL (Uniform Resource Locator) 네트워크 상에서 자원이 어디 있는지를 알려주기 위한 규약 *출처 : 위키백과 URI URI (Uniform Resource Identifier) 인터넷에 있는 자원을 나타내는 유일..

menuhwang.tistory.com

 

 

 

DTO


대체로 API 설계 시 정해진 규칙대로 요청을 받도록 설계한다. 정해진 입력값을 가져올 때 DTO를 활용할 수 있다.

 

 DTO (Data Transfer Object) 는 다른 레이어 간의 데이터 교환에 활용된다. 따라서 DTO에는 별도의 로직을 포함시키지 않는다.

 

public class MyDTO {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "MyDTO{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
Lombok 의 Getter, Setter 어노테이션으로 getter, setter 메서드 생략 가능

 

DTO vs VO

 

DTO, VO 비교

DTO 와 VO 비교 DTO : Data Transfer Object VO : Value Object DTO Data Transfer Object Transfer : 옮기다, 이송하다 DTO는 레이어 간 데이터를 이송하기 위한 객체이다. 따라서 다른 로직이 포함되지 않고 데..

menuhwang.tistory.com

 

 

 

@RequestBody


 Post 메서드와 Put 메서드는 Get과 Delete 메서드와 달리 URL에 값을 담아 보내지 않고 HTTP Body에 담아 보낸다.

HTTP Body에 담긴 값을 사용하기 위해 @RequestBody 어노테이션을 사용한다.

 

@RestController
@RequestMapping("/api")
public class MyController {
	@PostMapping(value = "/user")
    public MyDTO postUser(@RequestBody MyDTO myDTO) {
        return myDTO;
    }
}

 

 


앞서 @ResponseBody 어노테이션이 Content-Type 을 json으로 설정하고 보내준다고 설명했다.

 

하지만 메서드의 리턴 타입이 String 인 경우 Content-Type을 확인해 보면 text/plain 으로 응답하는 것을 볼 수 있다.

 

boolean, int, 객체 (DTO처럼 직접 생성한 객체를 포함한) 등은 json으로 잘 응답하지만 문자열의 경우 text/plain 으로 응답한다. (옵션을 설정해주면 String도 Content-Type이 json으로 응답할 수 있다.)

 

 추가적으로 DTO와 같이 객체를 리턴해주는 경우 getter가 없다면 값에 접근하지 못한다.

* HttpMediaTypeNotAcceptableException 발생

 

 

 

ResponseEntity


 스프링에는 헤더와 Body로 구성된 HTTP 요청과 응답을 구성하는 HttpEntity 라는 클래스가 있다.

 

ResponseEntity는 이 HttpEntity를 상속받아 구현됐다.

 

 ResponseEntity를 활용하면 HttpStatus 코드와 헤더, Body를 쉽게 구성하여 응답할 수 있다.

 

@RestController
@RequestMapping("/api")
public class MyController {
    @PostMapping(value = "/user")
    public ResponseEntity<MyDTO> postUser(@RequestBody MyDTO myDTO) {
        return ResponseEntity
                .status(HttpStatus.ACCEPTED)
                .body(myDTO);
    }
}

ResponseEnity.status() 는 BodyBuilder를 리턴.

 

BodyBuilder는 HeaderBuilder를 상속받음.

 

HeaderBuilder에 header() 메서드가 있음.

 

body() 메서드는 BodyBuilder에 있음.

 

 

 

HTTP Status Code

 

HTTP Status Code 정리

HTTP Status Code HTTP 응답 코드 1XX 100번 대 응답 코드는 잘 사용하지 않아 따로 정리하지 않음. 2XX 성공적으로 요청이 처리되었음을 의미. 200 [OK] : 처리 완료. 201 [Created] : 생성 완료. 204 [No Conten..

menuhwang.tistory.com