본문 바로가기
Framework/Spring

[Spring Boot] Controller

by 코딩하는 랄로 2024. 1. 8.
728x90

@Controller

Controller는 MVC 구조에서 비즈니스 로직을 담당하는 Model과 클라이언트에 화면을 보여주는 View를 연결해주는 역할을 담당한다. 이 때, client의 요청에 따라 적절한 model을 호출하고 그 결과와 함께 view를 반환하여 준다.

 

Spring MVC에선, 클래스에 @Controller annotation을 통해 controller를 생성할 수 있다. 해당 annotation을 붙이게 되면 Spring에선 해당 클래스를 Controller 역할을 하는 Bean 객체로 생성한다.

 

 

 

@RequestMapping

이제 Controller 생성하였으니 해당 클래스 내에서 관련 요청(request)를 처리하는 메소드들을 정의해주어야 하는데 이러한 메소드를 handler라고 한다. handler를 통해 어떤 request가 오면 적절한 비즈니스 로직을 처리하고 그 결과로 어떤 response를 할지를 정의하는 것이다.

 

Spring 에는 handler를 지정해주기 위한 여러 annotation을 제공해주고 있는데 @RequestMapping은 요청 방식에 상관없이 정해진 경로로 오는 모든 요청을 받아서 처리한다.

/*
@RequestMapping에서 자주 사용하는 variables
value : 요청 경로 지정, 여러개 지정 가능
method : 요청 방식 지정. 여러개 지정 가능 ex. RequestMethod.GET, POST, PUT, ...
*/
@RequestMapping("/")  // == value = "/"
public String list() {
    return "list";
}

 

위의 handler의 경우, "/" 경로로 오는 요청이 들어오면 list 라는 이름을 가진 view를 응답해준다. 만약 void 타입으로 선언하게 되면 view 이름은 request url 로 지정해준 경로 이름으로 지정해준다. handler끼리 요청 url이 중복이되면 IllegalStateException 에러가 발생하기 때문에 url이 중복되지 않도록 조심해주어야 한다.

@RequestMapping("/list") 
public void list() {
    // 자동으로 url 경로인 list를 view 이름으로 지정해줌
}

 

view 이름은 handler가 리턴하는 이름 또는 경로로 정해지기 때문에 handler의 이름은 그닥 중요하지 않지만, 가급적 request url과 handler 이름, view 이름은 같게 하거나 동일한 맥락으로 통일해주는 것이 가독성 측면에서 좋다.

 

 

 

@GetMapping, @PostMapping, ...

Spring 4.3 이후부터는 RequestMapping에서 method를 지정해주는 것이 아닌 method 마다 개별적인 annotation을 사용할 수 있게 되었다. @GetMapping annotation은 RequestMapping(method=RequestMethod.GET) 과 같은 동작을 하는 annotation인 것이다.

 

또한, request의 method가 다른 요청은 url이 같더라도 다르게 취급을 한다.

@GetMapping("/list") 
public void listForGet() {
    return "list"
}

@PostMapping("list")
public void listForPost() {
    return "list"
}

 

그래도 handler의 이름은 같으면 안되기 때문에 코드의 가독성을 고려하여 큰 맥락을 해치지 않는 선에서 이름을 변경해주면 된다.

 

 

 

@RequestParam

HTTP Request 파라미터를 처리하기 위한 annotation 이다. 해당 Annotation을 매개변수 앞에 붙일 경우, Http request에서 해당 매개변수명에 맞는 key 값을 매핑하여 준다. 

@GetMapping("/list") // 다른 방식의 요청도 가능 ( ex. POST )
public void list(@RequestParam int age) {
    Sytem.out.println(age);   // "/list/age=10" => 10
    return "list"
}

 

해당 annotation을 사용할 때, 주의할 점은 annotation이 붙은 변수명을 값는 Key 값이 무조건 Http request에 담겨있어야 한다는 것이다. 만약, 해당 정보를 넣지 않고 요청하게 되면,

 

다음과 같은 에러를 마주하게 된다. 만약, 해당 Handler가 age 정보가 있든 없든 list 경로에 대한 요청을 처리하게 하려면 어떻게 해야 할까? 이럴 때에는 @RequestParam annotation을 생략하고 age가 null 값도 받을 수 있도록 Integer 타입으로 변경해주면 된다.

@GetMapping("/list") 
public void list(Integer age) {
    Sytem.out.println(age);   // "/list" => null
    return "list"
}

 

그럼 age값을 보낼 때는 해당 값이, 아닐 때는 null 이 출력 되는 것을 확인할 수 있다. Wrapper 클래스가 아닌 primitive로 선언하게 되면 null 값이 담기지 않아 에러를 띄우므로 파라미터 타입을 신경써주어야 한다!!

 

만약, 파라미터 명과 다른 key 값을 받고 싶다면 어떻게 해야 할까? 개발을 하다보면 본의 아니게 원하는 변수명을 사용할 수 없는 경우가 있기 때무에 이런 경우에는 @RequestParam에 name 속성을 지정을 해 주면 된다.

@GetMapping("/list") 
public void list(@RequestParam(name= "old") Integer age) {
    Sytem.out.println(age);   // "/list/old=10" => 10
    return "list"
}

 

 

 

@PathVariable

해당 annotation은 url의 경로에 있는 변수들을 파라미터에 mapping 해주는 annotation이다.

@GetMapping("/list/{old}/{name}") 
public void list(
    @PathVariable Integer age,
    @PathVariable String name
) {
    Sytem.out.println("age >> " + age + " name >>" + name);   
    // "/list/10/kim" => age >> 10 name >> kim
    return "list"
}

 

@PathVariable annotation은 생략하게 되면 자동으로 mapping을 해 주지 않는다...값을 넘겨주어도!! 그렇기 때문에 위와 같이 url로 넘겨주게 되면 꼭 @PathVariable을 붙여주어야 한다!! 추가로 @PathVariable 도 name 속성을 통해 원하는 매개변수에 값을 매핑 시켜 줄 수 있다.

@GetMapping("/list/{old}/{name}") 
public void list(
    @PathVariable(name ="old") Integer age,
    @PathVariable String name
) {
    Sytem.out.println("age >> " + age + " name >>" + name);   
    // "/list/10/kim" => age >> 10 name >> kim
    return "list"
}

 

 

 

@RequestBody

@RequestBody annotation이 파라미터에 붙게 되면 해당 http 요청의 body가 그대로 전달이 된다. 보통 Post 요청의 경우 body에 xml 또는 json 기반의 메세지를 사용하여 요청을 보내는 경우가 있는데 이 때 사용하면 유용한 annotation이다.

 

Postman을 사용하여 해당 json을 담고 있는 post 요청을 보내보자.

 

이 때, 중요한 점은 Body로 담겨져 오는 data를 활용하기 위해서는 Json 객체의 프로퍼티 값을 매핑할 수 있는 자바 객체가 있어야 한다!! 또한 body로 보내는 객체의 프로퍼티명과 자바 객체의 인스턴스 변수명이 동일해야 매핑이 된다.

@PostMapping ("/list")
public String list(@RequestBody Human human) {
    System.out.println(human);  // BoardController.Human(name=kim, age=10)
    return "list";
}


@Data
@AllArgsConstructor
public static class Human {
    private String name;
    private Integer age;
}

 

추가로, @RequestBody에 require 속성을 통해 해당 객체에 매핑되는 정보가 request body에 필수로 담겨있어야 동작하도록 하는 것도 가능하다.

 

 

 

@ResponseBody

@ResponseBody annotation을 handler에 붙여주게 되면 해당 handler가 반환하는 값을 바로 view가 아닌 응답으로 바로 보내게 된다. 예를 들어 아래의 list handler에

@GetMapping("/list") 
@ResponseBody
public void list() {
    return "list"
}

 

@ResponseBody를 붙여주게 되면,

위와 같이 웹 브라우저에 handler가 반환했던 list 문자열이 그대로 출력되는 것을 볼 수 있다. 관리자 도구를 열어서 해당 요청에 대한 응답을 보아도 아래와 같이 list가 담겨있는 것을 볼 수 있다.

 

@ResponseBody를 사용할 때, 주의 깊게 보아야 할 점은 위와 같이 String 또는 다른 Integer, int 등 Wrapper 클래스나 primitive 타입을 리턴할 때가 아닌 자바의 객체를 리턴을 하는 경우이다.

 

아래와 같이 임으로 inner Class를 선언하고 해당 클래스로 객체를 생성한 후 ResponseBody의 반환값으로 넘겨보자.

@GetMapping ("/list")
@ResponseBody
public Human list(Model model) {
    Human human = new Human("kim", 10);
    return human;
}

@Data  // Getter & Setter
@AllArgsConstructor  // 모든 field에 해당하는 값을 파라미터로 받는 생성자
public static class Human {
    private String name;
    private Integer age;
}

 

그 결과를 보면 아래와 같다.

 

@ResponseBody는 자바 객체를 반환할 경우 아래와 같이 변수명과 값을 key-value 쌍으로 가지는 Json을 반환하는 것을 확인할 수 있다!! 관리자 도구를 열어 response를 확인해도 Json을 받은 것을 볼 수 있다.

 

그렇기 때문에, @ResponseBody annotation을 이용하여 Ajax 요청이 올 때 Json 형태로 data를 보내주게 되어 Ajax를 요청한 쪽도 편하게 데이터를 다룰 수 있게 되는 것이다.

 

 

 

@RestController

만약, 해당 Controller의 모든 Handler가 @ResponseBody annotation이 필요하다면 어떻게 해야 할까? 물론 아래와 같이 class 단에서 annotation을 붙여주어도 된다.

@Controller
@ResponseBody
public class BoardController {

    @GetMapping ("/list")
    public Human list(Model model) {
        Human human = new Human("kim", 10);
        return human;
    }

    @Data
    @AllArgsConstructor
    public static class Human {
        private String name;
        private Integer age;
    }

}

 

이렇게 사용하여도 되지만,  @ResponseBody를 매번 붙이는 번거로움을 없애기 위해서 Controller + ResponseBody를 합친 형태인 @RestController annotation 또한 제공해주고 있다.

@RestController
public class BoardController {

    @GetMapping ("/list")
    public Human list(Model model) {
        Human human = new Human("kim", 10);
        return human;
    }

    @Data
    @AllArgsConstructor
    public static class Human {
        private String name;
        private Integer age;
    }

}

 

동작 방식은 @ResponseBody를 붙인것과 똑같다!!

728x90

'Framework > Spring' 카테고리의 다른 글

[Spring Boot] Cookie  (0) 2024.01.19
[Spring Boot] Lombok  (0) 2024.01.11
[Spring Boot] 게시판 목록 페이지  (0) 2024.01.08
[Spring Boot] MVC pattern  (0) 2024.01.08
[토비의 스프링] Singleton Registry에 대한 이해  (1) 2024.01.02