Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 타임리프 - 기본기능
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch1
- 스프링 mvc2 - 타임리프
- 스프링 입문(무료)
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch11
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch2
- @Aspect
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch13
- 2024 정보처리기사 수제비 실기
- 스프링 mvc1 - 서블릿
- 자바의 정석 기초편 ch3
- 스프링 db1 - 스프링과 문제 해결
- 게시글 목록 api
- 자바의 정석 기초편 ch8
- 자바의 정석 기초편 ch12
- 자바의 정석 기초편 ch7
- 스프링 고급 - 스프링 aop
- jpa 활용2 - api 개발 고급
- 자바의 정석 기초편 ch14
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch6
- 코드로 시작하는 자바 첫걸음
- jpa - 객체지향 쿼리 언어
- 스프링 mvc1 - 스프링 mvc
Archives
- Today
- Total
나구리의 개발공부기록
스프링 MVC - 기본기능, 프로젝트 생성, 로깅(간단히알아보기), 요청매핑, 요청매핑 - API 예시 본문
인프런 - 스프링 완전정복 코스 로드맵/스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
스프링 MVC - 기본기능, 프로젝트 생성, 로깅(간단히알아보기), 요청매핑, 요청매핑 - API 예시
소소한나구리 2024. 3. 1. 21:10 출처 : 인프런 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 프로젝트 생성
1) 프로젝트 생성
(1) 프로젝트
- Gradle
- Java
- SNAPSHOT, M1 등이 달려있지 않은 버전 중 가장 최신 버전의 스프링 부트
(2) Project Metadata
- Group : hello
- Artifact, Name : springmvc
- Packaging: Jar
- Java : 설치된 버전
(3) Dependencies
- Spring Web
- Thymeleaf
- Lombok
** 주의 - Packaging: Jar
- 스프링 부트를 사용하면 주로 이방식을 사용하게 되는데 Jar는 내장서버를 사용(톰캣 등)하고 webapp 경로도 사용하지 않아 내장 서버 사용에 최적화 되어있음
- War는 내장 서버도 사용가능하지만 주로 외부 서버에 배포(WAS 서버를 별도로 설치하여 빌드된 파일을 넣을 때)하거나 JSP를 사용할 때 사용함
(4) 웰컴페이지 생성
- 강의를 수월하게 진행하기 위한 메인 페이지 작성
- 스프링 부트에 Jar를 사용하면 /resources/static 위치에 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(POSTMAN 필요)</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>
<li><a href="/request-param-v1?username=hello&age=20">요청 파라미터 v1</a></li>
<li><a href="/request-param-v2?username=hello&age=20">요청 파라미터 v2</a></li>
<li><a href="/request-param-v3?username=hello&age=20">요청 파라미터 v3</a></li>
<li><a href="/request-param-v4?username=hello&age=20">요청 파라미터 v4</a></li>
<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">요청 파라미터 MAP</a></li>
<li><a href="/model-attribute-v1?username=hello&age=20">요청 파라미터 @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>
2. 로깅 간단히 알아보기
1) 로그 사용법
- 실무에서는 System.out.println() 같은 시스템 콘솔을 정보를 출력하지 않고 별도의 로깅 라이브러리를 사용해서 로그를 출력
- 로그 관련 라이브러리가 많고 깊게 들어가면끝이 없기 때문에 최소한의 사용 방법만 알아볼 예정
(1) 로깅 라이브러리
- 스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리가 함께 포함되고 기본으로 아래의 로깅 라이브러리를 사용함
- SLF4J, Logback
- 로그 라이브러리는 Logback, Log4J, Log4J2 등등 수많은 라이브러리가 있는데 그것을 통합해서 인터페이스로 제공하는 것이 SLF4J 임
- 즉, SLF4J는 인터페이스이며 그 구현체로 Logback 같은 로그 라이브러리를 선택하면 되며 실무에서는 기본으로 제공하는 Logback을 대부분 사용함
(2) LogTestController - 로그를 사용하기 위한 컨트롤러 생성
- basic패키지를 생성하여 작성
- 로그 선언: 아래의 방식으로 선언하여 로그를 사용할 수 있으나 롬복을 사용하면 로그 선언없이 편리하게 로그를 사용가능 함
private Logger log = LoggerFactory.getLogger(getClass());
private static final Logger log = LoggerFactory.getLogger(Xxx.class);
@Slf4j: 롬복 애노테이션으로 위의 선언 코드 없이 간편하게 로그를 사용할 수 있음 - 로그 호출: log.info("xxx")
package hello.springmvc.basic;
// @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 계산 로직이 먼저 실행되기 때문에 이런 방식으로 사용하면 안됨
log.trace("trace my log=" + name);
return "ok";
}
}
(3) @RestController
- @Controller는 반환값이 String 이면 뷰 이름으로 인식되어 뷰를 찾고 뷰가 렌더링 됨
- @RestController는 반환 값으로 뷰를 찾지 않고 HTTP 메시지 바디에 바로 입력하며 실행 결과로 "ok" 메시지를 바로 반환받을 수 있음
- @ResponseBody와 관련이 있는데 뒤에서 더 자세히 설명함
(4) 로그가 출력되는 포멧
- 시간: 2024-03-01T10:37:04.218+09:00
- 로그레벨: INFO
- 프로세스ID: 58649
- 쓰레드 명: main
- 클래스명: hello.spring.mvc.SpringmvcApplication
- 로그 메세지: Started SpringmvcApplication in 0.635 seconds (process running for 0.87)
2024-03-01T10:37:04.218+09:00 INFO 58649 --- [ main] hello.springmvc.SpringmvcApplication : Started SpringmvcApplication in 0.635 seconds (process running for 0.87)
(5) 로그 레벨 설정
- 로그 LEVEL: TRACE -> DEBUG -> INFO -> WARN -> ERROR
- 개발서버: debug 출력
- 운영서버: info 출력
- application.properties에서 로그 레벨을 설정할 수 있음
# 전체 로그 레벨 설정(기본값: info)
# debug, trace로 하면 엄청난 다량의 정보가 출력됨
logging.level.root = info
#hello.springmvc 패키지와 그 하위 로그 레벨 설정
#trace로 설정 -> 모두 다보임
#debug로 설정 -> trace를 제외하고 다 보임 - 개발 PC에서 설정
#info로 설정 -> 운영 서버(로그 레벨 설정의 기본값)
logging.level.hello.springmvc=debug
(6) 올바른 로그 사용법 - 매우 중요!
- 로그는 불필요한 연산이 발생하지 않도록 무조건 ("data = {}" , data)처럼 코드를 작성해야함
- +로 작성하면 로그레벨이 info로 설정되어 있어도 해당 코드에 있는 더하기 연산이 실행되어 버려 의미없는 cpu, 메모리 등 리소스가 소모됨
log.debug("debug =" + debug) // 로그 출력 레벨을 info로 설정해도 무조건 + 연산이 발생함 -> 리소스 소모
log.debug("debug ={}",debug) // 로그 출력 레벨을 info로 설정하면 아무일도 발생하지 않음
(7) 로그 사용시 장점
- 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고 출력 모양을 조정할 수 있음
- 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하는 등의 상황에 맞게 로그 레벨 조절이 가능함
- 콘솔에만 출력하는 것이 아니라 파일이나 네트워크 전송 등으로 로그를 별도의 위치에 남길 수 있고 심지어 파일로 남길 때에는 일별, 특정 용량에 따라 로그를 분할, 자동 압축 백업 등의 기능을 설정할 수 있음
- 성능도 System.out보다 좋아서(내부 버퍼링, 멀티쓰레드 등등) 실무에서는 꼭 로그를 사용해야함
** 로그 더 공부하기
3. 요청 매핑
1) MappingController
- basic패키지 하위에 requestmapping 패키지를 생성 후 작성
(1) 가장 기본적인 매핑
- /hello-basic URL 호출이 오면 메서드가 실행 되도록 매핑
- 대부분의 속성을 배열[]로 제공하므로 다중 설정도 가능함, {"/hello-basic", "/hello-go"}
/**
* 기본 요청
* HTTP 메서드 모두 허용 GET, HEAD, POST, PUT, PATCH, DELETE
* */
@RequestMapping("/hello-basic")
// @RequestMapping({"/hello-basic","/hello-go"}) // 다중 설정도 가능
public String helloBasic() {
log.info("helloBasic");
return "ok";
}
** 참고 - 스프링 부트 3.0 이전과 이후의 매핑과 URL요청
- 스프링 부트 3.0 이전에는 마지막의 /를 제거하여 아래의 URL의 요청이 같은 요청으로 매핑되었음
- 매핑: /hello-basic -> URL 요청: /hello-basic , /hello-basic/
- 그러나 스프링부트 3.0이후 부터는 서로 다른 URL 요청으로 인식하므로 매핑도 각각 다르게 적용해야함
- 매핑: /hello-basic -> URL 요청 : /hello-basic
- 매핑: /hello-basic/ -> URL 요청 : /hello-basic/
(2-1) HTTP 메서드 매핑
- @RequestMapping에 method속성으로 HTTP 메서드를 지정하지 않으면 HTTP 메서드와 무관하게 모든 요청이 허용 됨
- method 속성을 지정하면 해당 요청만 허용하고 다른 요청이 들어오면 HTTP 405(Method Not Allowed) 상태코드를 반환함
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1() {
log.info("mappingGetV1");
return "ok";
}
(2-1) HTTP 메서드 매핑 축약
- HTTP 메서드를 축약한 애노테이션을 사용하는 것이 더 직관적임
- 각 애노테이션에 들어가서 코드를 보면 @RequestMapping과 method를 지정해서 사용하는 것을 확인할 수 있음
/**
* 편리한 축약 애노테이션 코드
* @GetMapping
* @PostMapping
* @PutMapping
* @DeleteMapping
* @PatchMapping
*/
@GetMapping("/mapping-get-v2")
public String mappingGetV2() {
log.info("mappingGetV2");
return "ok";
}
(3) PathVariable(경로변수) 사용
- 최근HTTP API는 리소스 경로에 식별자를 넣는 스타일을 선호하고 많이 쓰고 있음 (ex: /mapping/userA, /users/1)
- @RequestMapping은 URL경로를 템플릿화 할 수 있는데 @PathVariable 애노테이션을 사용하면 매칭되는 부분을 편리하게 조회가 가능함
- @PathVariable의 이름과 파라미터 이름이 같으면 생략할 수 있음
/**
* PathVariable 사용
* 변수명이 같으면 @PathVariable("UserId") String UserId -> @PathVariable String userId로 변경(생략)가능
*/
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
log.info("mappingPath userId = {}", data);
return "ok";
}
(3-1) 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";
}
(4) 특정 파라미터 조건 매핑
- 특정 파라미터가 있거나 없는 조건을 추가할 수 있지만 잘 사용하지 않음
/**
* 파라미터로 조건을 추가 매핑
* params="mode",
* params="!mode"
* params="mode=debug"
* params="mode!=debug"
* params = {"mode=debug","data=good"}
* http://localhost:8080/mapping-param?mode=debug 처럼 파라미터에 조건이 있어야 가능하도록 세팅
*/
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
log.info("mappingParam");
return "ok";
}
(5) 특정 헤더 조건 매핑
- 파라미터 매핑과 비슷한 방식, HTTP 헤더에 조건을 추가함
- Postman으로 테스트 해야함
/**
* 특정 헤더로 추가 매핑
* 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";
}
(6-1) 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume(해당 타입을 소비함)
- HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입을 매핑함
- 맞지 않으면 HTTP 415(Unsupported Media Type)을 반환
- Postman으로 테스트 해야함
/**
* 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";
}
(6-2) 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce(해당 타입의 요청만 받음)
- HTTP 요청의 Accept 헤더를 기반으로 미디어 타입을 매핑
- 맞지 않으면 HTTP 406(Not Acceptable)을 반환
/**
* 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";
}
** 참고
- 미디어타입 조건 매핑은 타입을 직접 작성하는 것보다 MediaType.타입_VALUE 문법을 사용하는 것을 권장함
- @PostMapping(value = "/mapping-consume", consumes = MediaType.APPLICATION_JSON_VALUE)
- @PostMapping(value = "/mapping-consume", produces = MediaType.TEXT_HTML_VALUE)
4. 요청 매핑 - API 예시
1) HTTP API 매핑
- 회원 관리 HTTP API를 만든다고 가정하고 매핑을 어떻게 하는지 실습
- 실제 데이터가 넘어가는 부분은 생략하고 URL매핑만 실습
(1) 회원관리 API 예시
회원 목록 조회 | GET | /users |
회원 등록 | POST | /users |
회원 조회 | GET | /users/{userId} |
회원 수정 | PATCH | /users/{userId} |
회원 삭제 | DELETE | /users/{userId} |
(2) MappingClassController
- Postman으로 테스트 해보면 정상적으로 작동 되는 것을 확인할 수 있음
- 클래스 레벨에 @RequestMapping으로 매핑 정보를 두어 코드 중목을 제거하고 메서드 레벨에서 해당 정보를 조합해서 사용
- 이런식으로 리소스를 계층으로 식별하는 스타일을 많이 사용하며 실제 개발시에도 사람이 인지하기 좋고 코드가 깔끔해짐
@RestController
@RequestMapping("/mapping/users") // 매핑 중복 제거
public class MappingClassController {
// @GetMapping("/mapping/users")
@GetMapping
public String user() {
return "get users";
}
// @PostMapping("/mapping/users")
@PostMapping
public String addUser() {
return "post user";
}
// @GetMapping("/mapping/users/{userId}")
@GetMapping("/{userId}")
public String findUser(@PathVariable String userId) {
return "get userId=" + userId;
}
// @PatchMapping("/mapping/users/{userId}")
@PatchMapping("{userId}")
public String updateUser(@PathVariable String userId) {
return "update userId=" + userId;
}
// @DeleteMapping("/mapping/users/{userId}")
@DeleteMapping("/{userId}")
public String deleteUser(@PathVariable String userId) {
return "delete userId=" + userId;
}
}