티스토리 뷰

API 계층(Presentation layer)은 클라이언트의 요청을 직접적으로 전달받는 계층이다.
당일 학습한 것은 이 API 계층을 Spring MVC 기반의 코드로 구현한 것이다.
Spring MVC
스프링의 모듈 중 서블릿(Servlet) API를 기반으로 클라이언트의 요청을 처리하는 모듈이다.
쉽게 말해서 클라이언트의 요청을 편리하게 처리해주는 프레임워크다.
서블릿(Servlet)?

동적 웹 페이지를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술이며,
웹 요청과 응답의 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 해 준다.
자세한 내용은 이 곳 에서 참고하시면 좋을 듯하다.
Model (spring Mvc)
클라이언트의 요청을 받은 서버는 응답을 다시 돌려주게 되는데,
이때 응답에 담기는 (요청에 따른 처리를 한) 작업의 결과 데이터를 모델이라고 한다.
View (spring mVc)
model 데이터를 이용해 웹브라우저와 같은 클라이언트 애플리케이션의 화면에 보여지는 리소스를 제공하는 역할을 한다.
+ View의 형태
HTML 페이지의 출력
HTML 페이지를 직접 렌더링 해서 클라이언트 측에 전송하는 방식, 프론트엔드 + 백엔드 통합 구조 (SSR)
PDF, Excel 등의 문서 형태로 출력
model 데이터를 가공해서 PDF, Excel 문서로 만들어서 클라이언트 측에 전송하는 방식,
문서 내에서 데이터가 동적으로 변경되어야 하는 경우 사용
XML, JSON 등 특정 형식의 포맷으로 변환
model 데이터를 특정 프로토콜 형태로 변환하여 클라이언트 측에 전송하는 방식,
특정 형식의 데이터만 전송하고 프론트엔드 쪽에서 전송받은 데이터를 기반으로 HTML 페이지를 만든다. (CSR)
☞ 프론트엔드 영역과 백엔드 영역이 명확하게 분리 → 개발 및 유지보수가 상대적으로 용이해진다.
또한 프론트엔드 측에서 비동기 클라이언트 애플리케이션 을 만드는 것이 가능해진다.
JSON?
Spring MVC에서 클라이언트 애플리케이션과 서버 애플리케이션이 주고받는 데이터 형식이다.
{
"속성" : "값"
"속성" : "값"
···
}
형태를 갖고 있다.
Controller (spring mvC)
모델과 뷰의 중간에서 상호작용 역할을 하는, 클라이언트 측의 요청을 직접적으로 전달받는 엔드포인트이다.
클라이언트의 요청을 통해 비즈니스 로직이 model 데이터를 만들어내면, 그것을 view로 전달하는 역할을 한다.
MVC 처리 흐름
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@RestController
@RequestMapping(path = "/v1/coffee")
public class CoffeeController {
private final CoffeeService coffeeService;
CoffeeController(CoffeeService coffeeService) {
this.coffeeService = coffeeService;
@getMapping("/{coffee-id}")
public Coffee getCoffee(@PathVariable("coffee-id") long coffeeId) {
return coffeeService.findCoffee(coffeeId);
}
}
|
cs |
클라이언트가 요청 데이터를 서버 측에 전송한다.
→ controller가 요청 데이터를 수신하여 비즈니스 로직으로 처리, model 데이터를 생성한다.
(CoffeeController가 요청 데이터를 수신, CoffeeService의 객체를 생성자를 통해 받아서
CoffeeService의 findCoffee메서드(비즈니스 로직)를 getCoffee안에서 실행.
findCoffee 메서드를 통해 리턴 받는 값이 model 데이터라고 생각하면 된다.)
→ controller에게 model 데이터가 전달되고 이후에 뷰에게도 전달된다.
(getCoffee를 통해 모델 데이터가 CoffeeController 내부에 전달)
→ view가 응답 데이터를 생성하여 클라이언트 측에 전송
(내부적으로 스프링의 view가 전달받아 JSON 포맷으로 변경됨)
Spring MVC의 동작 방식과 구성 요소

| 진행 순서 | 내용 |
| 1. 요청 | 클라이언트가 DispatcherServlet 클래스에 요청을 전송 |
| 2. Controller 검색 (핸들러 조회) |
DispatcherServlet는 요청을 처리할 controller에 대한 검색을 HandlerMapping 인터페이스에 요청 |
| 3. Controller 정보 리턴 | 요청에 부합하는 핸들러 객체를 다시 DispatcherServlet에게 리턴 핸들러 객체는 핸들러 메서드 정보를 포함한다. (핸들러 메서드 = controller 클래스 안에 구현된 요청 처리 메서드) |
| 4. Controller 호출 위임 | HandlerAdapter에게 핸들러 메서드 호출을 위임 |
| 5. Controller 호출 (반환점) |
전달 받은 controller의 정보를 기반하여 해당 controller의 핸들러 메서드를 호출 |
| 6. Model 데이터 리턴 | controller의 핸들러 메서드가 비즈니스 로직을 처리한 후 리턴 받은 모델 데이터를 HandlerAdapter에게 전달 |
| 7. Model 데이터, View 이름 리턴 | HandlerAdapter는 전달받은 model 데이터와 view 정보를 DispatcherServlet에 전달 |
| 8. View 검색 요청 | DS는 전달 받은 view 정보를 ViewResolver에게 전달해서 뷰 검색을 요청 |
| 9. View 리턴 | view 정보에 해당하는 view를 찾아 리턴 |
| 10. 응답 생성 요청 | DispatcherServlet은 전달 받은 view 객체에게 모델 데이터를 넘겨주면서 클라이언트에게 전달할 응답데이터 생성을 요청 |
| 11. 응답 생성 후 리턴 | view 객체는 응답 데이터를 생성하여 DispatcherServlet에게 전달 |
| 12. 응답 | DispatcherServlet는 전달 받은 응답 데이터를 클라이언트에게 전달 |
DispatcherServlet이 모든 일을 다하고 있는 것 같지만, 실상은 다른 구성요소에게 자신이 해야 할 일들을 위임하고 있는데
이를 FCP(Front Controller Pattern)이라고 한다.
Handler?
스프링 MVC는 웹 요청을 실제로 처리하는 객체를 핸들러(Handler)라고 표현하고 있으며,
@Controller 적용 객체나 controller 인터페이스를 구현한 객체 모두 스프링 MVC입장에서는 핸들러가 된다.
주요 애너테이션
@SpringBootApplication
엔트리 포인트 클래스에 붙는 애너테이션.
자동 구성을 활성화.
@Component가 붙는 클래스를 Spring Bean으로 등록하는 기능을 활성화(Component Scan).
@Configuration이 붙은 클래스를 자동으로 찾아주고 추가적으로 Spring Bean을 등록하는 기능 활성화.
@RestController
특정 클래스에 이 애너테이션을 추가하면
Spring MVC에서 해당 클래스가 REST API의 리소스를 처리하기 위한 API의 엔드포인트로 동작함을 정의.
+ 애플리케이션 로딩 시 Spring Bean으로 등록해줌 (@Controller 내부에 @Component가 붙어있음)
@RequestMapping
클라이언트 요청과 그 요청을 처리하는 핸들러 메서드를 매핑해주는 역할
controller 클래스 레벨에 추가하여 클래스 전체에 사용되는 공통 URL(Base URL)을 설정한다.
@RequsetParam
핸들러 메서드의 파라미터 종류 중 하나.
클라이언트 쪽에서 전송하는 요청 데이터를
쿼리 파라미터(메시지 바디 없이 URL에 데이터 포함) or 폼 데이터 형식(메시지 바디에 쿼리 파라미터 형식으로 전달)으로 전송하면 그 데이터를 서버 쪽에서 전달받을 때 사용하는 애너테이션.
@PathVariable
핸들러 메서드의 파라미터 종류 중 하나.
URI에 변수가 들어가야 할 때 쓴다.
ex) http://localhost:8080/v1/orders/1
맨 끝의 1에 해당하는 값이다.
@PostMapping, @GetMapping, @DeleteMapping ...
HTTP 메서드 타입과 맞춰서 사용하는 애너테이션이다.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.codestates.member;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(value = "/v1/members", produces = {MediaType.APPLICATION_JSON_VALUE})
public class MemberController {
@PostMapping
public String postMember(@RequestParam("email") String email,
@RequestParam("name") String name,
@RequestParam("phone") String phone) {
System.out.println("# email: " + email);
System.out.println("# name: " + name);
System.out.println("# phone: " + phone);
String response =
"{\"" +
"email\":\""+email+"\"," +
"\"name\":\""+name+"\",\"" +
"phone\":\"" + phone+
"\"}";
return response;
}
@GetMapping("/{member-id}")
public String getMember(@PathVariable("member-id")long memberId) {
System.out.println("# memberId: " + memberId);
return null;
}
}
|
cs |
produces = MediaType.APPLICATION_JSON_VALUE
→ produces는 응답 데이터를 어떤 미디어 타입으로 클라이언트에게 전송할지를 결정한다.
지금은 JSON 형식의 데이터를 응답 데이터로 전송하겠다는 의미다.
+@Controller와 @RestController의 차이점
@Controller와 @ResponseBody의 동작을 하나로 결합한 것이 @RestController다.
@Controller의 역할은 model 객체를 만들어 데이터를 담고 view를 찾는 것이지만,
@RestController의 역할은 단순히 JSON 또는 XML 형식의 데이터와 HTTP 응답을 객체에 담아서 반환하는 것이다.
@Controller에 @ResponseBody를 붙이면서 @Controller 또한 동일한 역할을 할 수 있다.
@Controller가 붙은 클래스는 기본적으로 파라미터가 없는 기본 생성자가 있어야 한다.
ResponseEntity
HttpEntity를 상속받는, 결과 데이터와 HTTP 상태 코드를 직접 제어할 수 있는 클래스이다.
사용자의 HttpRequest에 대한 응답 데이터가 포함된다.
ex) new ResponseEntity<>(map, HttpStatus.CREATED)
핸들러 메서드에 리턴 값으로 Map 객체를 리턴하면 Spring MVC 내부적으로 JSON 형식의 데이터를 생성해준다.
ResponseEntity를 사용하지 않는다면 JSON형식의 문자열을 개발자가 직접 수작업으로 작성해야 할 것이다. (위처럼)
ResponseEntity를 사용하여 수정한 코드
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.codestates.order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(@RequestParam("email") String email,
@RequestParam("name") String name,
@RequestParam("phone") String phone) {
Map<String, String> map = new HashMap<>();
map.put("email", email);
map.put("name", name);
map.put("phone", phone);
return new ResponseEntity<>(map, HttpStatus.CREATED);
}
@GetMapping("/{member-id}")
public ResponseEntity getMember(@PathVariable("member-id") long memberId) {
System.out.println("# memberId: " + memberId);
return new ResponseEntity<>(HttpStatus.OK);
}
}
|
cs |
map객체를 리턴하고 있기 때문에 produces가 사라졌고, ResponseEntity를 사용하면서 코드가 훨씬 간결해졌다.
HttpStatus를 사용해서 더 명확하게 응답 상태를 전달할 수 있게 되었다.
DTO(Data Transfer Object)
계층 간 데이터 교환을 하기 위해 사용하는 객체, 로직을 가지지 않는 순수한 데이터 객체이다. (getter, setter만 가짐)
데이터를 전송하기 위한 용도의 객체라고 생각하면 된다.
더 많은 정보들이 회원 정보에 포함되어야 한다면 @RequsetParam의 개수가 계속 늘어나게 될 것이다.
이러한 경우에 요청 데이터를 하나의 객체로 모두 전달받기 위해서 DTO 클래스를 사용하는 것이다.
기존의 postMember
|
1
2
3
4
5
6
7
8
9
10
11
12
|
@PostMapping
public ResponseEntity postMember(@RequestParam("email") String email,
@RequestParam("name") String name,
@RequestParam("phone") String phone) {
Map<String, String> map = new HashMap<>();
map.put("email", email);
map.put("name", name);
map.put("phone", phone);
return new ResponseEntity<Map>(map, HttpStatus.CREATED);
}
|
cs |
DTO를 적용한 postMember
|
1
2
3
4
|
@PostMapping
public ResponseEntity postMember(@RequestBody MemberPostDto memberPostDto) {
return new ResponseEntity<>(memberPostDto, HttpStatus.CREATED);
}
|
cs |
@RequsetBody 또한 @RequestParam과 비슷하게 사용하는 애너테이션이다.
가장 큰 차이는 객체를 생성할 수 있다 없다의 차이다.
URI상에서 데이터를 전달하는 경우는 @RequestParam을,
그 외의 경우는 @RequestBody를 사용한다. ex) 비밀번호 같은 정보가 포함될 경우
+ 직렬화 역직렬화
클라이언트 쪽에서 JSON 형식의 데이터를 서버 쪽으로 전송하면 웹 애플리케이션은 해당 데이터를 DTO 같은
Java의 객체로 변환하는 데 이를 역직렬화(Deserialization)라고 하고 (JSON → Java)
서버 쪽에서 클라이언트에게 응답 데이터를 전송하기 위해 DTO 같은 Java의 객체를 JSON 형식으로 변환하는 것을
직렬화(Serialization)이라고 한다. (Java → JSON)
@RequestBody가 있다면 @ResponseBody 또한 존재하는데, 위에서 사용되지 않은 이유는
Spring MVC에서는 핸들러 메서드의 리턴 값이 ResponseEntity 객체일 경우
내부적으로 HttpMessageConverter가 동작하게 되어 응답 객체를 JSON형식으로 바꿔주기 때문이다.
DTO 클래스
|
1
2
3
4
5
6
7
8
|
import lombok.Getter;
@Getter
public class MemberDto {
private String email;
private String name;
private String phone;
|
cs |
@Getter를 붙이게 되면 모든 변수에 getter가 다 적용된다. (getEmail()과 같은 메서드가 숨어있게 된다.)
☞ DTO를 사용하여 훨씬 효율적이고 간결한 코드를 만들 수 있다.
DTO 유효성 검증
서버 쪽에서는 유효한 데이터를 전달받기 위해 데이터를 검증해야 한다.
이메일과 핸드폰 번호 등에 제대로 이메일 값이 들어오는지, 핸드폰 번호 형태의 숫자가 들어오는 지 등에 대한 검증을
하게 되는 것이다.
이를 유효성 검증이라고 한다.
유효성 검증은 1차적으로 프론트엔드쪽에서도 진행하지만, 그 값은 얼마든지 조작이 가능하기 때문에
서버 쪽에서 한번 더 유효성 검증을 진행해야 한다.
유효성 검증은 핸들러 메서드 내에 직접적으로 포함될 수 있지만 이는 코드의 복잡도를 높이게 된다.
따라서 유효성 검증 로직을 DTO 클래스로 빼내서 핸들러 메서드의 간결함을 유지할 수 있다.
유효성 검증이 적용된 MemberPostDto
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Getter;
@Getter
public class MemberPostDto {
@NotBlank
@Email
private String email;
@NotBlank(message = "이름은 공백이 아니어야 합니다.")
private String name;
@Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
message = "휴대폰 번호는 010으로 시작하는 11자리 숫자와 '-'로 구성되어야 합니다.")
private String phone;
}
|
cs |
@NotBlank
이메일 정보가 비어있지 않은지를 검증한다.
null값이나 공백, 스페이스 같은 값들을 모두 허용하지 않는다.
유효성 검증에 실패하면 에러 메시지가 콘솔에 출력된다.
유효한 이메일 주소인지를 검증한다.
유효성 검증에 실패하면 지정한 메시지가 콘솔에 출력된다.
@Pattern
정규표현식에 매치하는지 확인한다.
유효성 검증에 실패하면 지정한 메시지가 콘솔에 출력된다.
유효성 검증이 적용된 MemberPostDto를 postMember에 적용
|
1
2
3
4
|
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberPostDto) {
return new ResponseEntity<>(memberPostDto, HttpStatus.CREATED);
}
|
cs |
@Valid
유효성 검증을 적용하게 해주는 애너테이션이다.
정규 표현식?
특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어다.

위 사진을 예로 들 수 있는데, 이해하기 위해서는 많은 연습이 필요할 것 같다. (나도 뚜렷하게 이해하진 못했다.)
Jakarta Bean Validation
유효성 검증을 위한 표준 스펙에서 지원하는 내장 애너테이션들로,
지금까지 DTO 클래스의 유효성 검증을 위해 사용한 애너테이션도 여기에 포함된다.
Java Bean 스펙을 준수하는 Java 클래스라면 Jakarta Bean Validation의 애너테이션을 사용해 유효성 검증을 할 수 있다.
DTO 클래스에 유효성 검증을 적용하다 보면 목적에 맞는 애너테이션이 존재하지 않을 수 있는데,
이러한 경우는 원하는 목적에 맞는 Custom Anntation을 정의해서 Custom Validator를 구현하여 사용할 수도 있다.
'SEB_BE_41' 카테고리의 다른 글
| 10.25 ~ 26 (예외 처리) (0) | 2022.11.02 |
|---|---|
| 10.24 (서비스 계층) (0) | 2022.10.24 |
| 10.19 (섹션2 회고) (0) | 2022.10.22 |
| 10.17 ~ 18 (AOP) (0) | 2022.10.20 |
| 10.13 ~ 14 (DI) part 2 (0) | 2022.10.19 |