[Spring] 기초 Spring 4주차

Yuri·2025년 1월 22일

Spring

목록 보기
5/21

🧑‍🏫 목표

  • Spring이 지원하는 다양한 기능과 파라미터 매핑, 애노테이션 활용 방법을 학습한다

Spring Annotation

@Slf4j

Slf4j는 인터페이스이고 그 구현체로 Logback같은 라이브러리를 선택한다. 실제 개발에서는 Spring Boot가 기본으로 제공하는 Logback을 대부분 사용

Logging

  • Thread 정보, 클래스 이름과 같은 부가 정보를 함께 확인
  • System.out.println(); 대신 별도의 로깅 라이브러리를 사용하여 로그를 출력
  • Log Level을 설정하여 Error 메세지만 출력, 로그 메세지를 일자별로 모아 외부 저장소에 보관
    • Log Level
      TRACE > DEBUG > INFO > WARN > ERROR
  • 전체 로그 레벨 설정: application.properties
# com.example.springbasicannotation 하위 경로들의 로그 레벨을 설정한다.
logging.level.com.example.springbasicannotation=TRACE

설정하지 않으면 Default 레벨인 INFO 로 설정된다.
→ 본인 포함 하위 레벨인 INFO > WARN > ERROR 만 출력

➕) log는 코드가 실행되는 시점에 맞춰서 문자 연산을 실행한다.

🤨 문제가 되는 이유?
로그 레벨이 낮아서 출력되지 않는 로그의 문자 연산도 실행하여 불필요한 동작을 수행할 수 있다.

log.info("문자 info={}", "info"); // 문자 연산을 진행하지 않는다.
log.trace("문자 trace " + "trace"); // 문자 연산을 먼저 해버린다.
  • 첫번째 매개변수: 중괄호({})를 포함한 문자열
  • 두번째 매개변수: 문자열(변수)
    → 중괄호가 두번째 매개변수로 치환된다.

👉 위와 같이 매개변수로 치환하는 형태로 로그를 작성하자!

@Controller VS @RestController

Annotation 기반의 Spring에서 Controller(Handler)를 만들 때 사용하는 어노테이션

@Controller

  • View가 있는 경우에 사용한다.
  • 즉, Template Engine인 Thymeleaf, JSP 등을 사용하는 경우
package com.example.springbasicannotation.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ViewController {

    @RequestMapping("/view")
    public String example() {
        // logic
        return "sparta"; // ViewName이 return
    }

}

여기서 반환된 View는 Thymeleaf로 작성되어 View와 Resolver가 이미 존재한다. → 의존성만 추가하면 Spring Boot가 알아서 View Rendering 해준다.

  • Thymeleaf 예시
    • SpringBoot build.gradle 의존성 추가
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    ...
}

👉 main/resources/templates 가 기본 경로로 설정된다.

↳ 해당 경로에 존재하는 Thymeleaf 코드

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>
<body>
<h2>Thymeleaf Template Sample</h2>
</body>
</html>
  • 동작 순서
  • return 값이 String이면ThymeleafViewResolver에 의해 View Name으로 인식

@RestController

  • 응답할 Data가 있는 경우에 사용
  • 현재는 대부분 @RestController를 사용하여 API가 만들어진다 (RESTful API)
  • return 값으로 View를 찾는것이 아니라 HTTP Message Body에 Data를 입력한다.
package com.example.springbasicannotation.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ResponseController {

    @RequestMapping("/string")
    public String example() {
        // logic
        return "sparta"; // ViewName이 return 되는게 아니라, String Data가 반환된다.
    }

}

💡 View가 아닌 HTTP Message Body에 Data가 들어가는 이유는 아래에서 배울 @Responsebody와 관련이 있다.

  • 동작 순서

Annotation 자세히 보기

  1. @Component
  • Spring Bean에 등록하는 역할 수행
    Spring Bean은 애플리케이션의 구성 요소를 정의하는 객체
  • @Indexed
    클래스가 컴포넌트 스캔의 대상으로 Spring Bean에 더 빠르게 등록되도록 도와준다.
  1. @Target
    @Target이 선언된 하위 어노테이션이 어떤 범위에 적용되는지 설정
    👉 다른 어노테이션의 범위 설정
  • ElementType Enum
    TYPE(Class, Interface, annotation, enum, recode),
    FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, etc
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
    String value() default "";
}

= 클래스 상위에 @Component 어노테이션을 사용하면 그 하위의 클래스는 Component로 등록된다.

  1. @Retention
    하위의 어노테이션이 얼마나 오래 유지되는지를 결정
  • RetentionPolicy Enum
    SOURCE: 소스코드(.java)에서만 유지 컴파일러에 의해서 클래스 파일로 저장되지 않는다,
    CLASS: 컴파일된 클래스 파일(.class)에 저장되지만, JVM이 실행 시 읽지 않는다.(주석과 같음),
    RUNTIME: 클래스 파일(.class)에 저장되고, JVM에 의해 런타임 시점에 읽을 수 있다. 실제 런타임 시점 코드에 반영되어 영향을 준다.

👉 @Component을 그대로 읽어들이는 것이 아니라 @Retention을 확인하고 Spring Bean으로 등록할지 결정

@Controller VS @RestController

개발에서 우선순위는 항상 자세히 선언된것이 우선순위가 높다.
(RestController 는 Controller를 구체화한 것이다.)

@Controller

  • @Target(ElementType.Type)
    • Class, Interface, Annotation, Enum, Record Declaration(Java16) 에 적용할 수 있다.
  • @Retention(RetentionPolicy.RUNTIME)
    • 클래스 파일(.class)에 저장되고, JVM에 의해 런타임 시점에 읽을 수 있다.
  • @Document
    • Javadoc 등의 문서화 도구에 의해 문서화되어야 함을 나타낸다.
  • @Component
    • Spring Bean에 등록한다.
    • 싱글톤으로 관리된다.

@RestController

  • @Controller에 @ResponseBody가 결합된 어노테이션
  • @RestController는 @Controller와 달리 각 메서드마다 @ResponseBody를 추가하지 않아도 된다

    👉 @ResponseBody: Class, Method 모두 적용 가능하다.
    단, @RestController 의 @Target이 ElementType.TYPE 으로 제한되었으므로 @RestController의 @ResponseBody는 TYPE 레벨에만 적용 가능하다.

@RequestMapping

특정 URL로 Request를 보내면 들어온 요청을 Controller 내부의 특정 Method와 Mapping 하기 위해 사용

💡 Client로부터 요청이 왔을 때 어떤 Controller가 호출될지 Mapping하는것은 단순히 URL로 Mapping 하는것이 아니라 여러가지 요소(URL, Method 등)를 조합하여 Mapping

  1. Spring Boot 3.0 버전 이하

    • URL path /example, /example**/** 모두 허용(Mapping)한다.
  2. Spring Boot 3.0 버전 이상(현재 버전)

    • URL path /example 만 허용(Mapping)한다.
  3. 속성값들을 설정할 때 배열 형태로 다중 설정이 가능하다

    ex) @RequestMapping({”/example”, “/example2”, “/example3”})

  4. HTTP Method POST, GET, PUT, PATCH, DELETE, HEAD 모두 허용한다

  5. method 속성으로 HTTP 메서드를 지정하면 지정된것만 허용한다.

package com.example.springbasicannotation.controller;

import org.springframework.web.bind.annotation.*;

// 응답 데이터를 반환한다.
//@RequestMapping("/prefix")
@RestController
public class RequestMappingController {

    // HTTP Method 는 GET만 허용한다.
    @RequestMapping(value = "/v1", method = RequestMethod.GET)
    public String exampleV1() {
        // logic
        return "this is sparta!";
    }

    // Post, GET, Put, Patch, Delete 모두 가능
    @GetMapping(value = "/v2")
    public String exampleV2() {
        // logic
        return "this is sparta!";
    }

//    @PostMapping(value = "/v2")
//    public String exampleV2() {
//        // logic
//        return "this is sparta!";
//    }
//
//    @PutMapping(value = "/v2")
//    public String exampleV2() {
//        // logic
//        return "this is sparta!";
//    }
//
//    @PatchMapping(value = "/v2")
//    public String exampleV2() {
//        // logic
//        return "this is sparta!";
//    }
//
//    @DeleteMapping(value = "/v2")
//    public String exampleV2() {
//        // logic
//        return "this is sparta!";
//    }

//    // Post, GET, Put, Patch, Delete 모두 가능
//    @GetMapping(value = "/v3")
//    public String exampleV3() {
//        // logic
//        return "this is sparta!";
//    }

}

👉 속성으로 설정된 HTTP Method로 요청이 아닌 다른 Method로 요청하면?

405 Method Not Allowed 클라이언트 오류 발생

@RequestMapping을 Method 속성 지정을 편리하게 사용하기 위해 만듦 → 직관적이고 축약됨

  • @GetMapping()
  • @PostMapping()
  • @PutMapping()
  • @PatchMapping()
  • @DeleteMapping()

예) @GetMapping

  • 적용 범위: Method
  • @RequestMapping의 method 속성이 GET

👉 매개변수로 URL 값만 넘기는 경우 value= 생략 가능

@GetMapping(value = "/v2")
@GetMapping("/v2")

🤨 그럼 @RequestMapping 은 언제 쓰나요?

  • 적용 범위 : Class, Method
  • Restful API는 계층 구조로 URL이 만들어진다.
  • 계층 구조에서 공통적으로 들어가는 부분을 prefix로 선언 → Class 레벨에 적용
@RequestMapping("/prefix")
@RestController
public class RequestMappingController {
// Post, GET, Put, Patch, Delete 모두 가능
    @GetMapping(value = "/v3")
    public String exampleV3() {
        // logic
        return "this is sparta!";
    }

}

@PathVariable

HTTP 특성 중 하나인 비연결성을 극복하여 데이터를 전달하기 위한 방법 중 하나이다. URL로 전달된 값을 파라미터로 받아오는 역할을 수행한다.

  1. 경로 변수를 중괄호에 둘러싸인 값으로 사용할 수 있다.

    ex) user/{id}

  2. 기본적으로 @PathVariable로 설정된 경로 변수는 반드시 값을 가져야 하며 값이 없으면 응답 상태코드 404 Not Found Error가 발생한다.

  3. 최근 Restful API를 설계하는 것이 API의 기준이 되며 해당 어노테이션의 사용 빈도가 높아졌다.

💡 Restful API를 설계하게 되면 URL path 만으로 어떤 Resource을 사용하는지, HTTP Method 만으로 어떤 기능이 동작되는지 쉽게 알아볼 수 있다.

  • Restful API 설계 예시
    • postId글의 comment 댓글 작성
      • POST + posts/{postId}/comments
    • postId글의 comment 댓글 전체 조회
      • GET + posts/{postId}/comments
    • postId글의 commentId 댓글 단 건 조회
      • GET + posts/{postId}/comments/{commentId}
    • postId글의 commentId 댓글 수정
      • PUT + posts/{postId}/comments/{commentId}
    • postId글의 commentId 댓글 삭제
      • DELETE + posts/{postId}/comments/{commentId}
  • @PathVariable 규칙
  1. 파라미터 변수명과 PathVariable 변수명이 같으면 속성 값 생략 가능

    @RequestMapping("/posts")
    @RestController
    public class PathVariableController {
        // postId로 된 post 단건 조회
        @GetMapping("/{postId}")
        public String pathVariableV1(@PathVariable("postId") Long data) {
            // logic
            String result = "PathvariableV1 결과입니다 : " + data;
            return result;
        }
    }
    // 변수명과 같다면 속성값 생략가능
        @GetMapping("/{postId}")
        public String pathVariableV2(@PathVariable Long postId) {
            // logic
            String result = "PathvariableV2 결과입니다 : " + postId;
            return result;
        }
  2. @PathVariable 다중 사용 가능

    @GetMapping("/{postId}/comments/{commentId}")
        public String pathVariableV3(
                @PathVariable Long postId,
                @PathVariable Long commentId
        ) {
            // logic
            String result = "PathvariableV3 결과입니다 postId : " + postId + "commentsId : " + commentId;
            return result;
        }
  • @RequestMapping + @GetMapping 에 @PathVariable 다중 사용

    @RequestMapping("/posts/{postId}")
    @RestController
    public class PathVariableController {
        @GetMapping("/comments/{commentId}")
            public String pathVariableV4(
                @PathVariable Long postId,
                @PathVariable Long commentId
            ) {
                // logic
                String result = "PathvariableV4 결과입니다 postId : " + postId + "commentsId : " + commentId;
                return result;
            }
    
    }

특정 파라미터 매핑

속성 설정을 통하여 특정 헤더, 특정 파라미터와 Mapping 할 수 있다.

@RestController
public class ParameterController {

    // parms 속성값 추가
    @GetMapping(value = "/users", params = "gender=man")
    public String params() {
        // logic
        String result = "params API가 호출 되었습니다.";
        return result;
    }

Query String (Query Parameter)의 값과 params의 값 비교

  • 속성 작성 규칙
    1. params = "gender"
      • params의 key값은 커스텀이 가능하다
      • value는 없어도 된다.
    2. params = "!gender"
      • gender가 없어야 한다.
    3. params = "gender=man"
      • gender=man 이어야 한다.
    4. params = "gender!=man"
      • params의 value값이 man가 아니여야 한다.
    5. params = {"gender=man", "gender=woman"}
      • 배열로 속성 값을 여러 개 설정이 가능하다.

속성 작성 규칙에 위배되는 요청이 들어오면 서버는 400 Bad Request 반환

특정 Header 매핑

  • 특정 Header와 매핑하는 방법
    → 전달받는 데이터의 타입이 "json"이어야하는 경우
@RestController
public class ParameterController {
	
	// headers 속성값 추가
  @PostMapping(value = "/users", headers = "Content-Type=application/json")
  public String headers() {
      // logic
      String result = "headers API가 호출 되었습니다.";
      return result;
  }
	
}
  • 속성 작성 규칙은 위 params 속성 값의 규칙과 같다.

MediaType 매핑, consume(수용)

HTTP Header Content-Type(요청)과 매핑 된다.

// consumes 속성값 추가
    @PostMapping(value = "/users", consumes = "application/json") // MediaType.APPLICATION_JSON_VALUE
    public String consumes() {
        // logic
        String result = "consumes API가 호출 되었습니다.";
        return result;
    }
  • consumes 속성 value값으로는 이미 Spring에서 제공되는 Enum인 MediaType.APPLICATION_JSON_VALUE 형태로 사용

  • 파라미터가 없거나 다르다면?

  • 속성 작성 방법
    1. consumes=”application/json”
      • application/json 미디어 타입 허용
    2. consumes=”!application/json”
      • application/json 제외 미디어 타입 허용
    3. consumes=”application/*”
      • application/ 으로 시작하는 모든 미디어 타입 허용
    4. consumes=”*\/*”
      • 모두 허용

MediaType 매핑 produces(제공)

요청 헤더의 Accept 값에 따라서 produces 하는 값이 변한다.

@RestController
public class ParameterController {
	
	// produces 속성값 추가
  @GetMapping(value = "/users", produces = "text/plain")
  public String produces() {
      // logic
      String result = "text/plain 데이터 응답";
      return result;
  }
	
}

Spring이 지원하는 Parameter

@Controller 의 사용 가능한 파라미터 목록
Method Arguments
@Controller 의 사용 가능한 Response 목록
Return Values

HTTP 헤더 조회

  • Spring에서 요청 Header에 쉽게 접근할 수 있다.
    • HttpServletRequest와 같이 파라미터로 다룰 수 있다.
// 로깅
@Slf4j
@RestController
public class RequestHeaderController {

    @GetMapping("/request/headers")
    public String headers(
            HttpServletRequest request, // Servlet에서 사용한것과 같음
            HttpServletResponse response, // Servlet에서 사용한것과 같음
            @RequestHeader MultiValueMap<String, String> headerMap,
            @RequestHeader("host") String host,
            @CookieValue(value = "cookie", required = false) String cookie,
            HttpMethod httpMethod,
            Locale locale
    ) {
		    // Servlet
        log.info("request={}", request);
        log.info("response={}", response);
        
        // @RequestHeader
        log.info("headerMap={}", headerMap);
        log.info("host={}", host);
        
        // @CookieValue
        log.info("cookie={}", cookie);
        
        // HttpMethod
        log.info("httpMethod={}", httpMethod);
        
        // Locale
        log.info("Locale={}", locale);

        return "success";
    }
}

  • MultiValueMap

💡 Map과 유사하게 Key, Value 형식으로 구현되어 있지만 하나의 Key가 여러 Value를 가질 수 있다 HTTP Header, Reqeust Parameter와 같이 하나의 Key에 여러 값을 받을 때 사용한다.

ex) key1=value1&key1=value2

profile
안녕하세요 :)

0개의 댓글