기본 기능
'스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 수업을 듣고 정리한 내용입니다.
📚 1. 프로젝트 생성
스프링 부트 스타터 사이트로 이동해서 스프링 프로젝트 생성
https://start.spring.io
프로젝트 선택
- Project: Gradle Project
- Language: Java
- Spring Boot: 2.4.x
Project Metadata
- Group: hello
- Artifact: springmvc
- Name: springmvc
- Package name: hello.springmvc
- Packaging: Jar (주의!)
- Java: 11
의존성 관리
Spring Web
Thymeleaf
Lombok
스프링 부트 스타터 사이트로 이동해서 스프링 프로젝트 생성
https://start.spring.io
프로젝트 선택
- Project: Gradle Project
- Language: Java
- Spring Boot: 2.4.x
Project Metadata
- Group: hello
- Artifact: springmvc
- Name: springmvc
- Package name: hello.springmvc
- Packaging: Jar (주의!)
- Java: 11
의존성 관리
Spring Web
Thymeleaf
Lombok
주의!
Packing
는War
가 아니라Jar
를 선택해주어야 한다.
JSP
를 사용하지 않기 때문에Jar
를 사용하는 것이 좋다.
앞으로 스프링 부트를 사용하면 이 방식을 주로 사용하게 된다.
Jar
를 사용하면 항상 내장 서버(톰캣등)을 사용하고,webapp
경로도 사용하지 않는다.
내장 서버 사용에 최적화 되어 있는 기능이다.
최근에는 주로 이 방식을 사용한다.
War
를 사용하면 내장 서버도 사용가능 하지만, 주로 외부 서버에 배포하는 목적으로 사용한다.
(1) Welcome 페이지 만들기
스프링 부트에 Jar
를 사용하면 /resources/static/index.html
위치에 index.html
파일을 두면 Welcome
페이지로 처리해준다.
(스프링 부트가 지원하는 정적 컨텐츠 위치에 /index.html
)이 있으면 된다.)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body> <ul>
<li>로그 출력 <ul>
<li><a href="/log-test">로그 테스트</a></li> </ul>
</li>
<!-- --> <li>요청 매핑
<ul>
<li><a href="/hello-basic">hello-basic</a></li>
<li><a href="/mapping-get-v1">HTTP 메서드 매핑</a></li>
<li><a href="/mapping-get-v2">HTTP 메서드 매핑 축약</a></li>
<li><a href="/mapping/userA">경로 변수</a></li>
<li><a href="/mapping/users/userA/orders/100">경로 변수 다중</a></li> <li><a href="/mapping-param?mode=debug">특정 파라미터 조건 매핑</a></li> <li><a href="/mapping-header">특정 헤더 조건 매핑(POST MAN 필요)</a></
li>
<li><a href="/mapping-consume">미디어 타입 조건 매핑 Content-Type(POST MAN 필요)</a></li>
<li><a href="/mapping-produce">미디어 타입 조건 매핑 Accept(POST MAN 필요)</a></li>
</ul> </li>
<li>요청 매핑 - API 예시 <ul>
<li>POST MAN 필요</li> </ul>
</li>
<li>HTTP 요청 기본
<ul>
<li><a href="/headers">기본, 헤더 조회</a></li>
</ul> </li>
<li>HTTP 요청 파라미터 <ul>
v1</a></li>
<li><a href="/request-param-v1?username=hello&age=20">요청 파라미터 <li><a href="/request-param-v2?username=hello&age=20">요청 파라미터
v2</a></li>
v3</a></li>
v4</a></li>
<li><a href="/request-param-v3?username=hello&age=20">요청 파라미터
<li><a href="/request-param-v4?username=hello&age=20">요청 파라미터
<li><a href="/request-param-required?username=hello&age=20">요청 파라미터 필수</a></li>
<li><a href="/request-param-default?username=hello&age=20">요청 파라미터 기본 값</a></li>
<li><a href="/request-param-map?username=hello&age=20">요청 파라미터 <li><a href="/model-attribute-v1?username=hello&age=20">요청 파라미터
MAP</a></li>
@ModelAttribute v1</a></li>
<li><a href="/model-attribute-v2?username=hello&age=20">요청 파라미터 @ModelAttribute v2</a></li>
</ul> </li>
<li>HTTP 요청 메시지 <ul>
<li>POST MAN</li>
</ul>
</li>
<li>HTTP 응답 - 정적 리소스, 뷰 템플릿
<ul>
<li><a href="/basic/hello-form.html">정적 리소스</a></li> <li><a href="/response-view-v1">뷰 템플릿 v1</a></li> <li><a href="/response-view-v2">뷰 템플릿 v2</a></li>
</ul> </li>
<li>HTTP 응답 - HTTP API, 메시지 바디에 직접 입력 <ul>
<li><a href="/response-body-string-v1">HTTP API String v1</a></li>
<li><a href="/response-body-string-v2">HTTP API String v2</a></li>
<li><a href="/response-body-string-v3">HTTP API String v3</a></li>
<li><a href="/response-body-json-v1">HTTP API Json v1</a></li>
<li><a href="/response-body-json-v2">HTTP API Json v2</a></li>
</ul> </li>
</ul>
</body>
</html>
💡 참고
- 스프링 부트 Welcome 페이지 지원
https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot- features.html#boot-features-spring-mvc-welcome-page
📚 2. 로깅 간단히 알아보기
- 지금까지 공부하며 콘솔창에 실행결과나 기대값을
System.out.println()
을 통해 출력하였다.
- 실제 운영을 할 때는 시스템 콘솔이 아닌 별도의 로깅 라이브러리를 사용해 출력을 한다.
- 많은 로깅 라이브러리가 있지만, 그 중
SLF4J
, Logback
을 알아보자!
- 지금까지 공부하며 콘솔창에 실행결과나 기대값을
System.out.println()
을 통해 출력하였다. - 실제 운영을 할 때는 시스템 콘솔이 아닌 별도의 로깅 라이브러리를 사용해 출력을 한다.
- 많은 로깅 라이브러리가 있지만, 그 중
SLF4J
,Logback
을 알아보자!
📖 A. 로깅 라이브러리
스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리 (
spring-boot-starter-logging
)가 함께 포함된다.
스프링 부트 로깅 라이브러리는 기본으로 다음 로깅 라이브러리를 사용한다.
SLF4J
=http://www.slf4j.org
Logback
:http://logback.qos.ch
- 로그 라이브러리는
Logback
,Log4J
,Log4J2
등등 수많은 라이브러기가 있는데, 그것을 통합해서 인터페이스로 제공하는 것이SLF4J
라이브러리이다. SLF4J
는 인터페이스이고, 그 구현체는Logback
과 같은 로그 라이브러리를 선택하면 된다.- 실무에서는 스프링 부트가 기본으로 제공하는
Logback
을 대부분 사용한다.
📖 B. 사용법
로깅을 사용하기위해 먼저 로깅 객체를 생성해야 한다.
(1) 클래스 참조변수 선언
// getClass()메서드를 통해 사용되는 클래스 타입 변환하여 삽입
private final Logger log = LoggerFactory.getLogger(getClass());
// 직접적으로 해당 클래스타입을 입력해주어도 된다.
private static final logger log = LoggerFactory.getLogger(Xxx.class);
(2) 롬북(Lombok) @Slf4j
@Slf4j
public class LogTestController {
...
}
@Slf4j
를 넣어두면
private final Logger log = LoggerFactory.getLogger(getClass());
와 같이 log 생성자를 호출할 필요가 없다.
(자동 등록)
(3) 로그 호출
log.info("hello")
➡️ 시스템 콘솔로 직접 출력하는 것보다 로그를 사용하면 다음과 같은 장점이 있다. (실무에서는 항상 로그를 사용해야 한다.)
📖 C. 소스코드
main.java.springmvc.basic.LogTestController
package hello.springmvc.basic;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class LogTestController {
// private final Logger log = LoggerFactory.getLogger(getClass());
@RequestMapping("/log-test")
public String logTest() {
String name = "Spring";
log.trace("trace log={}", name);
log.debug("debug log={}", name);
log.info(" info log={}", name);
log.warn(" warn log={}", name);
log.error("error log={}", name);
// 로그를 사용하지 않아도 a+b 계산 로직이 먼저 실행됨, 이런 방식으로 사용하면 X log.debug("String concat log=" + name);
return "ok";
// RestController
// 문자 반환시 String이 반환된다.
}
}
-
@RestController
➡️@Controller
는 반환 값이 String이면 뷰 이름으로 인식하기에 뷰를 찾고 뷰가 렌더링된다.
➡️@RestController
는 반환 값으로 뷰를 찾는게 아니라HTTP 메세지 바디
에 바로 입력한다.
(클래스 레벨이 아닌 메서드 레벨에서@ResponseBody
를 사용하면@Controller
를 사용하더라도 바로HTTP 메세지 바디
에 입력해서 반환을 해준다.) -
로그 출력 포맷
➡️ 시간, 로그 레벨, 프로세스 ID(PID), 쓰레드 명, 클래스 명, 로그 메세지
📖 D. 로그 레벨
5개의 로그를 출력시도 하였지만, 실행결과에서는 3개가 나왔다.
이유
- 로그에는 레벨이 있다. 로그레벨을 설정하면 그 로그 보다 우선순위가 높은 것만 출력된다.
- 스프링 부트에서 기본으로 설정되어 있는 로그레벨은
info
이다.- 그렇기에 info보다 우선순위가 낮은
debug
,trace
는 출력되지 않는다.info
를 줄시info
,warn
,error
가 출력된다.
(1) 로그 레벨 설정
원하는대로 로그 레벨을 설정할 수 있다.
application.properties
에서 레벨을 설정할 수 있다.
# 전체 로그 레벨 설정(기본 info)
logging.level.root=info
# hello.springmvc 패키지와 그 하위 로그 레벨 설정
#logging.level.hello.springmvc=[변경을 원하는 로그 레벨]
logging.level.hello.springmvc=debug
(2) 로그 레벨 우선순위
- LEVEL :
TRACE
>DEBUG
>INFO
>WARN
>ERROR
- 개발 서버는
debug
출력- 운영 서버는
info
출력
(3) 올바른 로그 사용법
log.debug("data"+data)
: 올바르지 않은 로그 사용
- 로그 출력 레벨을
info
로 설정해도 해당 코드에 있는"data="+data
가 실제 실행이 되어 버린다.- 문자 더하기 연산이 발생한다.
- 리소스 낭비이다.
log.debug("data={}", data)
: 올바른 로그 사용
- 이와 같이 작성시 로그 출력 레벨을
info
로 설정하면 아무 일도 발생하지 않는다. (DEBUG > INFO
)- 앞과 같은 의미없는 연산이 발생하지 않는다.
📖 E. 로그 사용시 장점
- 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있다.
- 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그를 상황에 맞게 조절할 수 있다.
- 시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다. 특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
- 성능도 일반
System.out
보다 좋다. (내부 버퍼링, 멀티 쓰레드 등등) 그래서 실무에서는 꼭 로그를 사용해야 한다.
💡 참고
스프링 부트가 제공하는 로그 기능은 다음을 참고하자 :https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot- features.html#boot-features-logging
📚 3. 요청 매핑
요청 매핑 : 요청이 왔을 때 어떤 컨트롤러에서 매핑을 할지 조사해서 매핑을 진행하는 것
요청 매핑 : 요청이 왔을 때 어떤 컨트롤러에서 매핑을 할지 조사해서 매핑을 진행하는 것
MappingController
(1) 기본 매핑(RequestMapping)
package hello.springmvc.basic.requestmapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
@RestController
public class MappingController {
private Logger log = LoggerFactory.getLogger(getClass());
/**
* 기본 요청
* 둘다 허용 /hello-basic, /hello-basic/
* HTTP 메서드 모두 허용 GET, HEAD, POST, PUT, PATCH, DELETE */
// @RequestMapping({"/hello-basic", "/hello-go"})
@RequestMapping("/hello-basic")
public String helloBasic() {
log.info("helloBasic");
return "ok";
}
...
}
-
@RestController
➡️@Controller
는 반환 값이String
이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 렌더링 된다.
➡️@RestController
는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다. (따라서 실행 결과로 ok 메세지를 받을 수 있다.@ResponseBody
)와 관련이 있다. -
@RequestMapping("/hello-basic")
➡️/hello-basic
URL 호출이 오면 이 메서드가 실행되도록 매핑한다.
➡️ 대부분의 속성을배열[]
로 제공하므로 다중 설정이 가능하다. (ex.{"/hello-basic", "/hello-go"}
)
둘다 허용
다음 두가지 요청은 다른 URL이지만, 스프링은 다음 URL 요청들을 같은 요청으로 매핑한다.
- 매핑 :
/hello-basic
- URL 요청 :
/hello-basic
,/hello-basic/
HTTP 메서드
@RequestMapping
에 method 속성으로 HTTP 메서드를 지정하지 않으면 HTTP 메서드와 무관하게 호출된다.- 모두 허용 :
GET
,HEAD
,POST
,PUT
,PATCH
,DELETE
(2) HTTP 메서드 매핑
/**
* method 특정 HTTP 메서드 요청만 허용
* GET, HEAD, POST, PUT, PATCH, DELETE
*/
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1() {
log.info("mappingGetV1");
return "ok";
}
- 여기에
POST
요청을 하면 스프링 MVC는HTTP 405 상태코드(Method Not Allowed)
를 반환한다.
(3) HTTP 메서드 매핑 축약
/**
* 편리한 축약 애노테이션 (코드보기)
* @GetMapping
* @PostMapping
* @PutMapping
* @DeleteMapping
* @PatchMapping
*/
@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
log.info("mapping-get-v2");
return "ok";
}
- 매번 method 속성을 설정해서 HTTP 메서드를 지정해주는게 번거롭고 가독성도 떨어지기에 축약한 애노테이션을 사용하는 것이 더 직관적이다.
GetMapping
,PostMapping
,PatchMapping
,DeleteMapping
➡️ 코드를 보면 애노테이션 내부에서@RequestMapping
과method
를 지정해서 사용하는 것을 확인할 수 있다.
(4) PathVariable(경로 변수) 사용
/**
* PathValuer
* PathVariable 사용
* 변수명이 같으면 생략 가능
*
* @PathVariable("userId") String userId -> @PathVariable userId
* /mapping/userA
*/
@GetMapping
public String mappingPath(@PathVariable("userId") String data) {
log.info("mappingPath userId={}", data);
return "ok";
}
실행
http://localhost:8080/mapping/userA
- 최근 HTTP API는 다음과 같이 리소스 경로에 식별자를 넣는 스타일을 선호한다.
➡️/mapping/userA
➡️/users/1
@RequestMapping
은 URL 경로를 템플릿화 할 수 있는데,@PathVariable
을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.@PathVariable
의 이름과 파라미터 이름이 같으면 생략할 수 있다.
➡️@PathVariable("data") String data
→@PathVariable String data
(5) PathVariable 사용 - 다중
/**
* PathVariable 사용 다중
*/
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long
orderId) {
log.info("mappingPath userId={}, orderId={}", userId, orderId);
return "ok";
}
실행
http://localhost:8080/mapping/users/userA/orders/100
- 하나 이상의
PathVariable
도 사용이 가능하다.
(6) 특정 파라미터 조건 매핑
/**
* 파라미터로 추가 매핑
* params="mode",
* params="!mode"
* params="mode=debug"
* params="mode!=debug" (! = )
* params = {"mode=debug","data=good"}
*/
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
log.info("mappingParam");
return "ok";
}
실행
http://localhost:8080/mapping-param?mode=debug
- 특정 파라미터를 조건식으로 매핑해서 매핑여부를 결정할 수 있다.
- 현재는 잘 사용하지 않는다.
(7) 특정 헤더 조건 매핑
/**
* 특정 헤더로 추가 매핑
* headers="mode",
* headers="!mode"
* headers="mode=debug"
* headers="mode!=debug" (! = )
*/
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
log.info("mappingHeader");
return "ok";
}
- 특정 파라미터 매핑과 동일하게 헤더 역시 조건 매핑이 가능하다.
- HTTP 헤더를 사용한다.
(8) 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume
/**
* Content-Type 헤더 기반 추가 매핑 Media Type
* consumes="application/json"
* consumes="!application/json"
* consumes="application/*"
* consumes="*\/*"
* MediaType.APPLICATION_JSON_VALUE
*/
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
log.info("mappingConsumes");
return "ok";
}
- HTTP 요청의
Content-Type
헤더를 기반으로 미디어 타입으로 매핑한다. - 일치하지 않을 경우 HTTP 415 상태코드(Unsupported Media Type)을 반환한다.
- 조건을 배열로 설정할 수도 있고 상수로 제공하는 매직넘버를 사용해도 된다.
ex)
consumes = "text/plain"
consumes = {"text/plain", "application/*"}
consumes = MediaType.TEXT_PLAIN_VALUE
(9) 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce
/*
* Accept 헤더 기반 Media Type
* produces = "text/html"
* produces = "!text/html"
* produces = "text/*"
* produces = "*\/*"
*/
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
log.info("mappingProduces");
return "ok";
}
- HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑한다.
- 만약 맞지 않으면
HTTP 406 상태코드(Not Acceptable)
을 반환한다.
ex)
produces = "text/plain"
produces = {"text/plain", "application/*"}
produces = MediaType.TEXT_PLAIN_VALUE
produces = "text/plain;charset=UTF-8"
📚 4. 요청 매핑 - API 예시
회원관리를 HTTP API로 만든다 생각하고 매핑을 어떻게 하는지 알아보자!
HTTP API 구조만 만들자!
(실제 데이터가 넘어가는 부분은 생략하고 URL 매핑만)
회원관리를 HTTP API로 만든다 생각하고 매핑을 어떻게 하는지 알아보자!
HTTP API 구조만 만들자!
(실제 데이터가 넘어가는 부분은 생략하고 URL 매핑만)
회원 관리 API
- 회원 목록 조회 :
GET
/users
- 회원 등록 :
POST
/users
- 회원 조회 :
GET
/users/{userId}
- 회원 수정 :
PATCH
/users/{userId}
- 회원 삭제 :
DELETE
/users/{userId}
MappingClassController
package hello.springmvc.basic.requestmapping;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
/**
* GET /mapping/users
*/
@GetMapping
public String users() {
return "get users";
}
/**
* POST /mapping/users
*/
@PostMapping
public String addUser() {
return "post user";
}
/**
* GET /mapping/users/{userId}
*/
@GetMapping("/{userId}")
public String findUser(@PathVariable String userId) {
return "get userId=" + userId;
}
/**
* PATCH /mapping/users/{userId}
*/
@PatchMapping("/{userId}")
public String updateUser(@PathVariable String userId) {
return "update userId=" + userId;
}
/**
* DELETE /mapping/users/{userId}
*/
@DeleteMapping("/{userId}")
public String deleteUser(@PathVariable String userId) {
return "delete userId=" + userId;
}
}
/mapping
: 강의의 다른 예제들과 구분하기 위해 사용@RequestMapping("/mapping/users")
: 클래스 레벨에 매핑 정보를 두면 메서드 레벨에서 해당 정보를 조합해서 사용한다.
📖 A. Postman으로 테스트
- 회원 목록 조회 :
GET
/users
- 회원 등록 :
POST
/users
- 회원 조회 :
GET
/users/userA
- 회원 수정 :
PATCH
/users/userA
- 회원 삭제 :
DELETE
/users/userA
매핑 방법을 이해했으니, 이제부터 HTTP 요청이 보내는 데이터들을 스프링 MVC로 어떻게 조회하는지 알아보자!
참고
Author And Source
이 문제에 관하여(기본 기능), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@chang626/스프링-MVC-기본-기능저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)