controller패키지 내 컨트롤러 기능을 구현할 클래스를 만든다. 클래스에 @RestController를 붙인다.
클래스에 '@RequestMapping("/api")'를 붙이면 클래스 내 사용할 메서드에서 사용할 공통 URL로 '/api'를 사용할 수 있다.
5.2.1 @RequestMapping으로 구현하기
클래스 내 메서드에 '@RequestMapping("/hello", method = RequestMethod.GET)'을 붙이면 GET메소드를 통한 HTTP 요청을 처리할 수 있다. 클래스에 '@RequestMapping("/api")'를 붙이고 메서드에 '@RequestMapping("/hello", method = RequestMethod.GET)'을 붙인 경우 해당 요청 URL은 'http://localhost:8080/api/hello'가 된다.
@RequestMapping 대신 각 HTTP메소드를 특정하여 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping을 사용할 수 있다.이와 같은 어노테이션을 붙이는 것이 명시적으로 HTTP 메소드를 알 수 있기 때문에 이와 같은 방식이 주로 사용된다.
5.2.2 매개변수가 없는 GET 메서드 구현
@GetMapping을 사용할 경우 '@GetMapping(value = "/name")'과 같이 URL 주소를 명시하여 사용한다. 'value ='을 생략하고 괄호 안에 바로 URL 주소를 명시할 수 있다.
5.2.3 @PathVariable을 활용한 GET 메서드 구현
@GetMapping(value = "/member/{memberId}")
public String getMemberId(@PathVariable String memberId) {
return memberId;
}
위와 같이 @PathVariable을 사용하면 URL에 memberId에 해당하는 값을 입력받아 메서드에서 변수를 사용할 수 있다. 위의 예시에서 URL 주소를 'http://localhost:8080/member/hello'와 같이 입력하여 요청하면, memberId변수의 값은 hello가 되므로 메서드가 호출되어 hello가 응답된다.
@GetMapping(value = "/member/{memberId}")
public String getMemberId(@PathVariable("memberId") String id) {
return id;
}
URL 주소의 변수명과 메서드의 변수명이 다를 경우 @PathVaribale("memberId")와 같이 어노테이션의 괄호 안에 URL 주소의 변수명을 입력하여 사용할 수 있다.
5.2.4 @RequestParam을 활용한 GET 메서드 구현
@GetMapping("/member")
public String getMember(
@RequestParam String name,
@RequestParam int age) {
return String.format("이름 : %s, 나이 : %d", name, age);
}
위와 같이 메서드를 구성해놓으면 쿼리 형식으로 값을 전달할 수 있다. 즉, 'http://localhost:8080/memeber?name=Robert&age=12'와 같이 요청을 하면 name변수에 Robert, age 변수에 12가 각각 할당되어, '이름 : Robert, 나이 : 12'가 RequestBody로 응답된다.
위 예제처럼 각 변수를 설정하지 않고 '@RequestParam Map<String, String> member'을 설정하면 쿼리로 들어오는 변수와 값을 map으로 받을 수 있다.
5.2.5 DTO 객체를 활용한 GET 메서드 구현
DTO(Data Transfer Object)는 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체이다. 보통 dto패키지를 만들어 클래스를 구현하여 사용한다.
@Getter
@Setter
@ToString
public class Member {
private String name;
private int age;
}
위와 같이 매개변수로 사용할 클래스를 구현할 수 있다. 보통 lombok을 사용하여 getter/setter와 toString을 사용할 수 있게끔 구현한다.
@GetMapping("/member")
public String getMember(Member member) {
return member.toString();
}
위와 같이 컨트롤러로 메서드를 구현하면 'http://localhost:8080/memeber?name=Robert&age=12'로 요청할 경우 Member클래스로 각 값들을 받을 수 있다.
GET API에서는 URL의 경로나 파라미터에 변수를 넣어 요청을 보냈지만, POST API에서는 저장하고자 하는 리소스나 값을 HTTP 바디에 담아 서버에 전달 한다.
5.3.2 @RequestBody를 활용한 POST 메서드 구현
일반적으로 POST 형식의 요청은 클라이언트가 서버에 리소스를 저장하는데 사용한다. 이때 HTTP Body에 값을 넣어 전송하는데 보통 JSON 형식으로 요청을 한다.
@PostMapping("/member")
public String postMember(@RequestBody Map<String, Object> data) {
StringBuilder sb = new StringBuilder();
data.entrySet().forEach(x -> {
sb.append(x.getKey() + " : " + x.getValue() + "\n");
});
return sb.toString();
}
위와 같이 @RequestBody를 이용해서 json형식의 데이터를 Map으로 받을 수 있다. 또한 GET과 마찬가지로 아래와 같이 dto를 활용해서 데이터를 전달받을 수도 있다.
@PostMapping("/member")
public String postMember(@RequestBody Member member) {
return member.toString();
}
PUT API는 웹 애플리케이션 서버를 통해 데이터베이스와 같은 저장소에 존재하는 리소스 값을 업데이트하는데 사용한다.
5.4.1 @RequestBody를 활용한 PUT 메서드 구현
PUT API를 사용하는 방법은 POST API의 @PostMapping을 @PutMapping으로 사용하면 된다.
@PutMapping("/member")
public String postMember(@RequestBody Member member) {
return member.toString();
}
컨트롤러에서 위와 같이 리턴값을 String형식으로 지정해서 응답하면, 지정한 문자열이 응답된다. 하지만 아래와 같이 dto객체를 응답하면 자동으로 ResponseBody에 json 형식으로 응답한다.
@PutMapping("/member")
public String postMember(@RequestBody Member member) {
return member;
}
5.4.2 ResponseEntity를 활용한 PUT 메서드 구현
@PutMapping("/member")
public String postMember(@RequestBody Member member) {
return ResponseEntity.status(HttpStatus.ACCEPTED)
.body(member);
}
위와 같이 리턴 값에 ResponseEntity를 사용하여 응답코드와 함께 값을 리턴할 수도 있다.
DELETE API는 웹 애플리케이션 서버를 거쳐 데이터베이스 등의 저장소에 있는 리소스를 삭제할 때 사용한다.
5.5.1 @PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현
DELETE API는 GET API와 같이 URL주소에 값을 받거나 쿼리스트링으로 값을 받아 처리한다.
api에 대한 설명을 작성하고, 요청 및 응답테스트를 위해 swagger를 많이 사용한다.
swagger를 사용하는 방법은 다음과 같다.
implementation 'io.springfox:springfox-boot-starter:3.0.0'
implementation 'io.springfox:springfox-swagger-ui:3.0.0'
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket aip(){
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("zerobase.weather"))
.paths(PathSelectors.any())
.build().apiInfo(apiInfo());
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("날씨 일기 프로젝트 :)")
.description("날씨 일기를 CRUD 할 수 있는 백엔드 API입니다.")
.version("2.0")
.build();
}
}
아래와 같이 기존 컨트롤러 코드의 메서드에 @ApiOperation을 붙이거나, 메서드 내 파라미터에 @ApiParam을 붙여 사용한다.
@ApiOperation(value = "GET 메서드 예제", notes = "@RequestParam을 활용한 GET 메서드")
@GetMapping("/member")
public String getMember(
@ApiParam(value = "이름", required = true) @RequestParam String name,
@ApiParam(value = "나이", required = true) @RequestParam int age) {
return String.format("이름 : %s, 나이 : %d", name, age);
}
로깅이란 애플리케이션이 동작하는 동안 시스템의 상태나 동작 정보를 시간순으로 기록하는 것을 의미한다. 로깅을 위해 logback라이브러리를 사용할 수 있다.(spring-boot-starter-web에 내장되어 있음)
로그레벨은 5개로 나뉜다.
5.7.1 Logback 설정
resources폴더에 logback-spring.xml파일을 생성하여 아래와 같이 설정내용을 입력한다.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!--변수값 설정-->
<property name="LOGS_PATH" value="./logs"/>
<property name="LOGS_LEVEL" value="INFO"/>
<!-- Console Appender -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--출력 패턴 설정 -->
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss} %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<!-- File Appender-->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 파일명과 경로 설정 -->
<file>${LOGS_PATH}/log_file.log</file>
<!--출력 패턴 설정 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- Rolling 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- .gz, .zip 등을 넣으면 자동 일자별 로그파일 압축 -->
<fileNamePattern>${LOGS_PATH}/%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- 파일당 최고 용량 -->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 일자별 로그파일 최대 보관주기(~일), 해당 설정일 이상된 파일은 자동으로 폐기 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
</appender>
<!-- Error Appender-->
<appender name="Error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 파일명과 경로 설정 -->
<file>${LOGS_PATH}/error_file.log</file>
<!--출력 패턴 설정 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- Rolling 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- .gz, .zip 등을 넣으면 자동 일자별 로그파일 압축 -->
<fileNamePattern>${LOGS_PATH}/%d{yyyy-MM-dd}_error.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- threshold filter 을 넣어서 eroor 이상의 로그만 걸러지도록 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<root level="${LOGS_LEVEL}">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</root>
</configuration>
5.7.2 Logback 적용하기
로그를 작성하고 싶은 곳의 클래스에 객체 선언을 하고, 로그를 남기고 싶은 곳에 아래와 같이 코드를 입력한다.
// 객체선언(어플리케이션 전체에서 사용할 경우)
private static final Logger logger = LoggerFactory.getLogger(Application.class);
// 로그 출력
logger.info("로그 기록이 출력됩니다.")
// 변수의 값을 로그로 출력도 가능(변수명이 variable인 경우 중괄호에 변수값 출력됨)
logger.info("{} 값을 출력", variable)