본문 바로가기
개발/Spring

[SpringBoot] Controller의 여러가지 요청과 응답 처리

by solchan98 2022. 2. 10.

스프링 어플리케이션에서 클래스를 정의하면서 @Controller 어노테이션을 추가하면 해당 클래스는 Controller로 등록된다. 클라이언트가 요청을 보내면 DispatcherServlet는 Controller로 등록된 객체 중 해당되는 객체의 메서드를 핸들러가 찾는다.

@Controller // <- 이것이 스프링에 Controller로 등록하는 어노테이션!
public class TestController {

    // 여기에 Controller Mapping Method를 정의
    
    return ...
}

Controller Mapping Method

클라이언트의 요청 매핑은 Controller에 메서드를 어떻게 정의하냐에 따라 정해진다. 이 정의 방법은 클라이언트의 요청 방식에 따라 정의된다.

1. 요청과 응답

클라이언트는 다음과 같은 방식으로 요청을 보낼수 있다.

  • HTTP 요청을 통해 GET, POST, PUT PATCH DELETE
  • URI 쿼리 파라미터
  • json 형태의 바디 데이터로 요청을 보낼 수 있다.

서버는 다음과 같은 방식으로 응답을 할 수 있다.

  • html, jsp를 응답
  • RestAPI방식의 json 응답

2. 클라이언트의 요청 매핑

1. HTTP 요청 받기

@Controller // <- 이것이 스프링에 Controller로 등록하는 어노테이션!
public class TestController {

    // 여기에 Controller Mapping Method를 정의
    @PostMapping("/join")
    public String joinAccount(...) {
        return ...
    }
}

위 처럼 매핑 메서드를 정의 후 @PostMapping 어노테이션을 붙인후 uri를 지정하면 '/join'으로 Post 요청이 왔을 때 이 메서드로 매핑이 된다. GET 등 다른 것 또한 @{}Mapping 형식으로 사용하면 된다.

2. URI 쿼리 파라미터

@RequestParam, @PathVariable

클라이언트의 요청 URI를 다음과 같이 가정해보자.
Get http://localhost:1111/test?name=sol&age=24
위 요청에선 name과 age에 대한 정보를 가져와야 한다.

@Controller // <- 이것이 스프링에 Controller로 등록하는 어노테이션!
public class TestController {

    @GetMapping("/test")
    public String joinAccount(@RequestParam("name" String name, @RequestParam Integer age)) {
        System.out.println("이름 : " + name + " ,나이 : " + age);
        return ...
    }
}

서버는 위와 같은 요청을 받으면 "이름 : sol ,나이 : 24"을 출력할 것이다.

또 다른 방법으로 URI를 동적으로 받을 수 있다.

클라이언트의 요청 URI를 다음과 같이 가정해보자.
Post http://localhost:1234/1/board
위 요청에서 1은 카테고리 번호이다.
해당 부분을 동적으로 받아보자.

@Controller // <- 이것이 스프링에 Controller로 등록하는 어노테이션!
public class TestController {

    @PostMapping("/{kategorieId}/board")
    public String getBoard(@PathVariable Long kategorieId) {
        System.out.println("KategorieId : " + kategorieId);
        return ...
    }
}

서버는 위와 같은 요청을 받으면 "KategorieId : 1"을 출력할 것이다.

3. Form 및 Json

@ModelAttribute, @RequestBody

Form 및 Json을 받기 위해서는 매핑되는 객체(DAO, DTO)가 존재하여야 한다.
예로 회원가입 요청이라고 하면 요청에는 ID(name), PW 등의 정보가 존재할 것이다.

다음과 같은 DTO를 정의하고 예제를 진행하자.

public class Account {
    private String name; // 이름
    private String password; // 비밀번호
    
    // 생성자
    public void Account(String name, String password) {
        this.name = name;
        this.password = password
    }
    // 이하 Setter, Getter
    pbulic String getName() {
        return this.name;
    |
    pbulic String getPassword() {
        return this.password;
    |
    public void setName(String name) {
        this.name = name;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

다음 Controller

회원가입을 위해 아이디와 비밀번호를 Body로 받는다고 가정.
Form과 Json 두 방식으로 받는다.

  1. Form(@ModelAttribute)
    Form 형식으로 데이터가 넘어오면 application/form-data으로 넘어온다.
@Controller // <- 이것이 스프링에 Controller로 등록하는 어노테이션!
public class TestController {

    @PostMapping("/join")
    public String setJoin(@ModelAttribute Account account) {
        System.out.println("이름 : " + account.getName());
        System.out.println("비밀번호 : " + account.getPassword());
        return ...
    }
}

메서드 파라미터 부분에 @ModelAttribute를 추가하고 뒤애 받을 데이터의 객체 타입으로 받는다.
객체에 데이터를 1대1로 직접 바인딩하기 때문에 객체에는 Setter를 작성해야한다. 작성하지 않을 경우 해당 부분은 null로 받아진다.

참고로 @ModelAttribute는 생략이 가능하다.

  1. Json(@RequestBody)
    Json 형식으로 데이터가 넘어오면 application/json으로 넘어온다.
{
  "name": "sol",
  "password" : "123123123" 
}
@Controller // <- 이것이 스프링에 Controller로 등록하는 어노테이션!
public class TestController {

    @PostMapping("/join")
    public String setJoin(@RequestBody Account account) {
        System.out.println("이름 : " + account.getName());
        System.out.println("비밀번호 : " + account.getPassword());
        return ...
    }
}

메서드 파라미터 부분에 @RequestBody를 추가하고 받으려는 객체 타입으로 받으면 된다.

참고로 application/form-data를 @RequestBody로 받으려고 하면 null이 들어온다.
이유는 당연히 매핑되는 타입이 아니기 때문이다.

1, 2 방식에서 모두 공통사항인 다음의 참고사항이 있다.
지금은 간략한 이해를 목적으로 하기 때문에 메서드의 파라미터에서 Account 즉, Entity를 직접 받고있다. 하지만 실제 개발시에는 직접 Entity로 매핑하지 말고 DTO를 생성하여 사용하는 것이 좋다. 이는 서버의 응답에서도 마찬가지이다.

2. 서버의 응답.

서버의 응답은 서버의 목적에 따라 크게 다음 두 가지로 나눌 수 있다.

  1. 정적 View타입 응답.
  2. RestAPI(JSON 응답)

1. 정적 View 응답

정적 View 응답의 경우는 jsp, HTML 등의 정적 view를 응답하는 경우이다.
예로 사용자가 메인 페이지를 Get 요청하여 응답해주는 상황을 가정해보자.

응답 타입이 경로이기 때문에 String이어야한다.

@Controller // <- 이것이 스프링에 Controller로 등록하는 어노테이션!
public class TestController {

    @GetMapping("/")
    public String getIndex() {
        return "index";
    }
}

위 코드는 "src/main/resources/templates/"경로에 index.html이 저장되어 있고 이 경로를 응답하여 ViewResolver가 view를 찾고 최종적으로 클라이언트에게 index.html을 랜더하여 응답해준다.

지금은 간단하게 단순히 index페이지의 경로를 응답해주는 일만 하지만 service로직 처리 후 응답해주는 데이터가 있다면, 데이터를 추가하여 응답해줄 수 있다.

@Controller // <- 이것이 스프링에 Controller로 등록하는 어노테이션!
public class TestController {

    @GetMapping("/")
    public String getIndex(Model model) {
        model.addAttribute("message", "안녕하세요");
        return "index";
    }
}

다음은 message라는 키 값으로 "안녕하세요"라는 데이터를 추가하여 응답해준다.
Model를 파라미터로 받아서 위 처럼 사용할 수 있는 것이다.

2. json 응답

json응답의 경우는 응답 json에 맞는 DTO를 생성하여 응답해주는 방식이다.
예로 사용자가 ID(name), PW를 통해 가입을 하고(Post), 가입된 ID(name)를 응답해주는 상황을 가정해보자.
Account는 위에서 만들었던 Account를 그대로 사용한다고 가정한다.
요청 ID(name) : sol, PW : 1234

응답 타입이 DTO(객체)이기 때문에 DTO를 만들어야 한다.

// AccountDTO
// ID만 응답하기 때문에 ID만 갖는 간단한 객체를 생성한다.
public class AccountDTO {
    private String name;
    
    public String getID() {
        return this.name;
    }
    public void getID(String id) {
        this.name = name;
    }
}

이제 응답하기
반환 타입이 반환되는 DTO로 바뀌어야 한다.

@RestController // <- 이것이 스프링에 Controller로 등록하는 어노테이션!
public class TestController {

    @PostMapping("/")
    public AccountDTO setJoin(@RequestBody Account account) {
        AccountDTO  accountDTO = new AccountDTO(); // DTO 생성
        accountDTO.setID(account.get)
        return accountDTO;
    }
}

JSON 응답, 즉 DTO 응답의 경우 @Controller -> @RestController로 변경하면 된다. 혹은 @ResponseBody를 사용하면 된다. 그러면 반환 값을 HTTP Message converter를 통해 HTTP response body에 바인딩한다.

위에 대한 응답을 예시로 보면 다음과 같다.

{
  "name" : "sol"
}

이 처럼 AccountDTO가 json으로 변환되어 응답된다.

이해완료 테스트

이제 요청과 응답의 방식을 이해하였다면, 다음 상황을 구현해보자.

  • Post요청으로 이름과 나이를 받는다.
  • 받은 이름과 나이로 DTO를 구성하여 구성한 DTO를 응답해보자.

지식 더하기

1. 매핑 URI 줄이기

예로 게시판 등록, 조회, 삭제는 URI의 공통적인 부분이 존재할 것이다.
등록 : Post /board
조회 : Get /board/{postId}
삭제 : Delete(Post) /board

위 3개의 공통은 "/board"이다.
따라서 다음과 같이 사용할 수 있다.

@RequestMapping("/board")
@Controller
public class boardController {

    @GetMapping("/{postId}")
    ...
}

@RequestMapping("/board")를 사용하면, 위 @GetMapping는 "/board/{postId}"이렇게 매핑된다.

2. HttpServletRequest, HttpServletResponse

HttpServletRequest, HttpServletResponst를 통해 요청, 응답의 HTTP에 접근할 수 있다.

@RequestMapping("/board")
@Controller
public class boardController {

    @GetMapping("/{postId}")
    public String getBoard(HttpServletRequest request, HttpServletResponse response) {
        request.getHeader("헤더의 키"); // 키로 값 가져오기
        response.setHeader("헤더의 키", "데이터") // 키와 데이터로 헤더에 넣기
        response.setStatus(200) // 상태코드 200으로 응답
        ...
        return ...
    }
}

위 처럼 HTTP의 request와 response의 접근하여 데이터를 가져오거나 넣고 확인할 수 있다.
지금은 헤더와 상태코드에 대해서만 다루었지만 실제로 확인해보면 제공하는 메서드가 많이 존재한다.


해당 게시글은 공부를 하며 기록해 나가는 글입니다.
혹여나 정리 내용에서 잘못된 부분이 있다면 언제든 피드백 환영입니다!