해당 게시글은 스프링부트 핵심 가이드 교재를 공부하며 작성한 글입니다.
03. 개발 환경 구성
자바 JDK 설치
자바 JDK는 Azul에서 제공하는 Open JDK를 사용합니다. (이 외에도 다른 방법도 가능)
윈도우에서 정상적으로 JDK를 사용하기 위해서는 환경변수를 추가해야 합니다.
인텔리제이 IDEA 설치
제트브레인의 인텔리제이 IDEA를 사용합니다.
04. 스프링 부트 애플리케이션 개발하기
프로젝트 생성
인텔리제이 IDEA에서 프로젝트 생성하기
인텔리제이 IDEA 얼티밋 버전을 사용한다면 내장된 Spring Initializr를 사용하여 스프링 프로젝트를 생성할 수 있다.
스프링 공식 사이트에서 프로젝트 생성하기
스프링 공식 사이트에는 스프링 부트 프로젝트를 자동으로 만들어주는 서비스가 있다.
https://start.spring.io
pom.xml(Project Object Model) 살펴보기
- pom.xml 파일은 메이븐의 기능을 사용하기 위해 작성하는 파일
- 프로젝트, 의존성 라이브러리, 빌드 등의 정보 및 해당 프로젝트를 관리하는 데 필요한 내용이 기술
빌드 관리 도구
개발 규모가 커질수록 관리할 라이브러리가 많아지고 라이브러리 간 버전 호환성을 체크해야 하는 어려움이 발생하는데, 빌드 관리 도구를 이용하면 이 같은 문제를 해결할 수 있음
메이븐
대표기능
- 프로젝트 관리 : 프로젝트 버전과 아티팩트를 관리한다.
- 빌드 및 패키징 : 의존성을 관리하고 설정된 패키지 형식으로 빌드를 수행한다.
- 테스트 : 빌드를 수행하기 전에 단위 테스트를 통해 작성된 애플리케이션 코드의 정상 동작 여부를 확인한다.
- 배포 : 빌드가 완료된 패키지를 원격 저장소에 배포한다.
메이븐의 생명주기
기본 생명주기(Default Lifecycle), 클린 생명주기(Clean Lifecycle), 사이트 생명주기(Site Lifecycle)로 구분
클린 생명주기
- clean : 이전 빌드가 생성한 모든 파일을 제거
기본 생명주기
- validate : 프로젝트를 빌드하는 데 필요한 모든 정보를 사용할 수 있는지 검토
- compile : 프로젝트의 소스코드를 컴파일
- test : 단위 테스트 프레임워크를 사용해 테스트를 실행
- package : 컴파일한 코드를 가져와서 JAR 등의 형식으로 패키징을 수행
- verify : 패키지가 유효하며 일정 기준을 충족하는지 확인
- install : 프로젝트를 사용하는 데 필요한 패키지를 로컬 저장소에 설치
- deploy : 프로젝트를 통합 또는 릴리스 환경에서 다른 곳에 공유하기 위해 원격 저장소에 패키지를 복사
사이트 생명주기
- site : 메이븐의 설정 파일 정보를 기반으로 프로젝트의 문서 사이트를 생성
- site-deploy : 생성된 사이트 문서를 웹 서버에 배포
웹 브라우저를 통한 동작 테스트
스프링 부트에서는 기본적으로 8080번 포트를 통해 웹 애플리케이션이 실행
'http://localhost:8080/~~'에 접속하면 작성한 코드가 반환하는 것을 확인할 수 있음
Talend API Tester를 통한 동작 테스트
크롬 확장 프로그램인 Talend API Tester를 사용
05. API를 작성하는 다양한 방법
GET API 만들기
컨트롤러에 @RestController와 @RequestMapping을 붙여 내부에 선언되는 메서드에서 사용할 공통 URL을 설정합니다.
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
}
@RequestMapping으로 구현하기
- @RequestMapping 어노테이션을 별다른 설정 없이 선언하면 HTTP의 모든 요청을 받는다.
- method 요소의 값을 RequestMethod.GET으로 설정하면 요청 형식을 GET으로만 설정한다.
- 스프링 4.3 버전 이후로는 @RequestMapping 어노테이션은 더 이상 사용되지 않는다. 아래 어노테이션 사용
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
매개변수가 없는 GET 메서드 구현
// http://localhost:8080/api/v1/get-api/name
@GetMapping(value = "/name")
public String getName() {
return "Flature";
}
@PathVariable을 활용한 GET 메서드 구현
URL 자체에 값을 담아 요청하는 것
// http://localhost:8080/api/v1/get-api/variable1/{String 값}
@GetMapping(value = "/variable1/{variable}")
public String getVariable1(@PathVariable String variable) {
return variable;
}@PathVariaㅠle을 명시, @GetMapping 어노테이션과 @PathVariable에 지정된 변수의 이름을 동일하게 맞춰야함
동일하게 맞추지 못할 경우
// http://localhost:8080/api/v1/get-api/variable2/{String 값}
@GetMapping(value = "/variable2/{variable}")
public String getVariable2(@PathVariable("variable") String var) {
return var;
}
@RequestParam을 활용한 GET 메서드 구현
- 쿼리 형식으로 값을 전달할 수 있다.
- URI에서 '?'를 기준으로 우측에 '{키}={값}' 형태로 구성
- @RequestParam 어노테이션을 명시해 쿼리 값과 매핑하면 됩니다.
// http://localhost:8080/api/v1/get-api/request1?name=value1&email=value2&organization=value3
@GetMapping(value="request1")
public String getRequestParam1(
@RequestParam String name,
@RequestParam String email,
@RequestParam String organization) {
return name + " " + email + " " + organization;
}마찬가지로 value 요소를 사용하여 매핑할 수 있음
어떤 값이 들어올 지 모른다면 Map 객체를 활용한다.
// http://localhost:8080/api/v1/get-api/request2?key1=value1&key2=value2
@GetMapping(value = "/request2")
public String getRequestParam2(@RequestParam Map<String, String> param) {
StringBuilder sb = new StringBuilder();
param.entrySet().forEach(map -> {
sb.append(map.getKey() + " : " + map.getValue() + "\n");
});
return sb.toString();
}*entrySet() : Map에서 모든 Entry(key-value 쌍)를 가져와 Set 객체로 반환
*forEach : 그냥 순회. for문과 동일
URI와 URL의 차이
URI는 특정 리소스를 식별할 수 있는 식별자를 의미
URL은 우리가 흔히 말하는 웹 주소
DTO 객체를 활용한 GET 메서드 구현
DTO란?
- Data Transfer Object의 약자
- 데이터 교환에 활용
- 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체
- 데이터 교환 용도로만 사용되기에 별도의 로직이 포함되지 않음
// http://localhost:8080/api/v1/get-api/request3?name=value1&email=value2&organization=value3
@GetMapping(value = "/request3")
public String getRequestParam3(MemberDto memberDTO) {
//return memberDto.getName() + " " + memberDto.getEmail() + " " + memberDto.getOrganization();
return memberDTO.toString();
}
POST API 만들기
저장하고자 하는 리소스나 값을 HTTP 바디(body)에 담아 서버에 전달
@RequestBody를 활용한 POST 메서드 구현
- 클라이언트가 서버에 리소스를 저장하는 데 사용
- 클라이언트의 요청 트래픽에 값이 포함
- HTTP Body에 값을 넣어 전송
- 일반적으로 JSON 형식으로 전송
요청을 통해 어떤 값이 들어오게 될지 특정하기 어려울 땐 Map 객체를 사용, 들어갈 값이 정해져 있다면 DTO 객체를 매개변수로 삼아 작성할 수 있다.
// http://localhost:8080/api/v1/post-api/member2
@PostMapping(value = "/member2")
public String postMemberDto(@RequestBody MemberDto memberDTO) {
return memberDTO.toString();
}
PUT API 만들기
- 데이터베이스 같은 저장소에 존재하는 리소스의 값을 업데이트
- POST API와 비교하면 요청을 받아 실제 데이터베이스에 반영하는 과정에서 차이가 있지만, 구현하는 방법은 거의 동일
- HTTP Body를 활용
@RequestBody를 활용한 PUT 메서드 구현
// 예제 5.16
// http://localhost:8080/api/v1/put-api/member1
@PutMapping(value = "/member1")
public String postMemberDto1(@RequestBody MemberDto memberDto) {
return memberDto.toString();
}
// http://localhost:8080/api/v1/put-api/member2
@PutMapping(value = "/member2")
public MemberDto postMemberDto2(@RequestBody MemberDto memberDto) {
return memberDto;
}
String 타입과 DTO 객체의 차이
- String 타입의 경우 보낸 요청 내용이 그대로 결괏값으로 전달되지만 HEADERS 항목의 content-type을 보면 'text/plain'으로서 결괏값으로 일반 문자열이 전달됐음을 확인할 수 있다.
- DTO 객체의 경우 형식만 유지한 채 전달한다. HEADERS 영역의 Content-Type 항목도 'application/json' 형식으로 전달된 것을 확인할 수 있다.
@RestController 어노테이션이 지정된 클래스는 @ResponseBody를 생략할 수 있는데, @ResponseBody 어노테이션은 자동으로 값을 JSON과 같은 형식으로 변환해서 전달하는 역할을 수행한다.
ResponseEntity를 활용한 PUT 메서드 구현
- 헤더와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행하는 HttpEntity 클래스가 있다.
- ResponseEntity는 서버에 들어온 요청에 대해 응답 데이터를 구성해서 전달할 수 있게 한다.
- HttpHeaders와 Body를 가지고 자체적으로 HttpStatus를 구현한다.
// http://localhost:8080/api/v1/put-api/member3
@PutMapping(value = "/member3")
public ResponseEntity<MemberDto> postMemberDto3(@RequestBody MemberDto memberDto) {
return ResponseEntity
.status(HttpStatus.ACCEPTED) // 응답코드 202
.body(memberDto);
}
DELETE API 만들기
- 저장소에 있는 리소스를 삭제할 때
- 리소스를 식별할 수 있는 값을 받아 데이터베이스나 캐시에 있는 리소스를 조회하고 삭제하는 역할
- GET메서드와 같이 URI에 값을 넣어 요청 받는 형식
@PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현
// http://localhost:8080/api/v1/delete-api/{String 값}
@DeleteMapping(value = "/{variable}")
public String DeleteVariable(@PathVariable String variable) {
return variable;
}
// http://localhost:8080/api/v1/delete-api/request1?email=value
@DeleteMapping(value = "/request1")
public String getRequestParam1(@RequestParam String email) {
return "e-mail : " + email;
}
REST API 명세를 문서화하는 방법 - Swagger
명세란?
해당 API가 어떤 로직을 수행하는지 설명하고 이 로직을 수행하기 위해 어떤 값을 요청하며, 이에 따른 응답값으로는 무엇을 받을 수 있는지를 정리한 자료
API는 개발과정에서 계속 변경되기에 작성한 명세 문서도 주기적인 업데이트가 필요
Swagger 관련 설정 코드
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.springboot.api"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Spring Boot Open API Test with Swagger")
.description("설명 부분")
.version("1.0.0")
.build();
}
}RequestHandlerSelectors.basePackage() 메서드는 Swagger에서 스캔한 패키지 범위를 설정
로깅 라이브러리 - Logback
로깅이란?
- 애플리케이션이 동작하는 동안 시스템의 상태나 동작 정보를 시간순으로 기록하는 것
- 비기능 요구사항
Logback의 특징
- 크게 5개의 로그 레벨
- ERROR : 로직 수행 중에 시스템에 심각한 문제가 발생해서 애플리케이션의 작동이 불가능한 경우를 의미
- WARN : 시스템 에러의 원인이 될 수 있는 경고 레벨을 의미
- INFO : 애플리케이션의 상태 변경과 같은 정보 전달을 위해 사용
- DEBUG : 애플리케이션의 디버깅을 위한 메시지를 표시하는 레벨을 의미
- TRACE : DEBUG 레벨보다 더 상세한 메시지를 표현하기 위한 레벨을 의미
- 실제 운영 환경과 개발 환경에서 각각 다른 출력 레벨을 설정해서 로그를 확인할 수 있다.
- Logback의 설정 파일을 일정 시간마다 스캔해서 애플리케이션을 재가동하지 않아도 설정을 변경할 수 있다.
- 별도의 프로그램 지원 없이도 자체적으로 로그 파일을 압축할 수 있다.
- 저장된 로그 파일에 대한 보관 기간 등을 설정해서 관리할 수 있다.
Logback 설정
리소스 폴더 안에 logback-spring.xml의 이름으로 설정 파일을 생성
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_PATH" value="./logs"/>
<!-- Appenders -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
</encoder>
</appender>
<appender name="INFO_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<file>${LOG_PATH}/info.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/info_${type}.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
</encoder>
</appender>
<!-- TRACE > DEBUG > INFO > WARN > ERROR > OFF -->
<!-- Root Logger -->
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="INFO_LOG"/>
</root>
</configuration>
Appender 영역
로그의 형태를 설정하고 어떤 방법으로 출력할지를 설정하는 곳
- ConsoleAppender : 콘솔에 로그를 출력
- FileAppender : 파일에 로그를 저장
- RollingFileAppender : 여러 개의 파일을 순회하면서 로그를 저장
- SMTPAppender : 메일로 로그를 정송
- DBAppender : 데이터베이스에 로그를 저장
Root 영역
설정 파일에 정의된 Appender를 활용하려면 Root 영역에서 Appender를 참조해서 로깅 레벨을 설정한다.
'Spring' 카테고리의 다른 글
| 스프링 부트 핵심 가이드(6장) (1) | 2024.09.29 |
|---|---|
| 스프링 부트 핵심 가이드(1~2장) (0) | 2024.09.27 |