
안녕하세요, 오늘은 Spring Annotation 에 대해 정리해보았습니다.
💡 우리가 이미 과제와 강의를 통해 사용하고 있는 Spring의 다양한 기능들이 있다. 어떤 기능들이 있고 어떻게 사용하는지 알아보자.Spring의 다양한 기능
Log Level 설정을 통하여 Error 메세지만 출력하도록 하도록 하기도 하고
로그 메세지를 일자별로 모아서 저장하여 AWS에 보관하기도 한다.
@Slf4j → Slf4j는 인터페이스이고 그 구현체로 Logback같은 라이브러리를 선택한다.
Spring Boot가 기본으로 제공하는 Logback을 대부분 사용한다.

💡 우리가 사용하고 있는 @Controller와 @RestController는 분명한 차이점이 있다.@Controller VS @RestController
@Controller는 View가 있는 경우에 사용한다.
→ return 값이 String이면 viewResolver에 의해 View Name으로 인식된다.
@RestController는 응답할 Data가 있는 경우에 사용한다.
→ 현재는 대부분 @RestController를 사용하여 API가 만들어진다.
→ return 값으로 View를 찾는것이 아니라 HTTP Message Body에 Data를 입력한다.
→ View가 아닌 HTTP Message Body에 Data가 들어가는 이유는 @Responsebody와 관련이 있다.

@RestController
public class MappingController {
@RequestMapping("/example")
public String example() {
// logic
return "hello-world";
}
}
@RequestMapping**({**”/example”, “/example2”, “/example3”**})**@RestController
public class MappingController {
@RequestMapping(value = "/example-v1", method = RequestMethod.GET)
public String exampleV1() {
// logic
return "hello-world";
}
}
만약, 속성으로 설정된 method로 요청이 오지 않는다면?
→ HTTP 응답 상태코드 405(Client측 에러), Method Not Allowed Exception
@GetMapping

Target(ElementType.METHOD) Method Level에 해당 어노테이션을 적용한다 라는 의미@RequestMapping(method = RequestMethod.GET) 을 사용하고 있다.@RestController
public class MappingController {
// Post, GET, Put, Patch, Delete 모두 가능
@GetMapping(value = "/example-v2")
public String exampleV2() {
// logic
return "hello-world";
}
}
@PostMapping, @PutMapping, @DeleteMapping, @PatchMapping
모두 위의 @GetMapping과 같은 구조를 가지고 있다.




user/{id}@PathVariable로 설정된 경로 변수는 반드시 값을 가져야하며@GetMapping("/path/{variable}")
public String pathVariable(@PathVariable("variable") String data) {
// logic
return "path-variable";
}
// 변수명과 같다면 생략가능
@GetMapping("/path/{variable}")
public String pathVariable(@PathVariable String variable) {
// logic
return "path-variable";
}
최근에는 Restful API 를 사용하기 때문에 해당 방법을 많이 사용한다.
ex) /users/{id}, /memo/{id}
@GetMapping("/users/{userId}/posts/{postId}")
public String pathVariable(
@PathVariable String userId,
@PathVariable String postId
) {
// logic
return "path-variable";
}
@GetMapping("/path/{variable}", params = "level=test")
public String pathVariable(@PathVariable String variable) {
// logic
return "path-variable";
}
@GetMapping("/path/{variable}", headers = "level=test")
public String pathVariable(@PathVariable String variable) {
// logic
return "path-variable";
}

@PostMapping("/media-type", consumes = "application/json") // MediaType.APPLICATION_JSON_VALUE
public String mediaTypeConsumes() {
// logic
return "media-type";
}
consumes 속성 value값으로는
이미 Spring에서 제공되는 Enum인 MediaType.APPLICATION_JSON_VALUE 형태로 사용한다.

Postman → Headers → Content-Type 의 Value 설정

맞지 않으면 HTTP 상태코드 415 Unsupported Media Type Exception 발생
3-2. MediaType 매핑 produces(제공)
위 consumes 속성 사용법과 같음
@PostMapping("/media-type", produces = "text/html")
public String mediaTypeProduces() {
// logic
return "media-type";
}
HTTP 요청 Accept Header Media Type이 있어야함

위에 나온 모든 MediaType은 Spring이 제공하는 Enum을 사용한다.
ex) (produces = “application.json”) → (produces = MediaType.APPLICATION_JSON_VALUE)

어노테이션 기반 Spring의 Controller는 다양한 파라미터를 지원한다
어떤 파라미터들을 지원하는지 알아보자
@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
) {
log.info("request={}", request);
log.info("response={}", response);
log.info("headerMap={}", headerMap);
log.info("host={}", host);
log.info("cookie={}", cookie);
log.info("httpMethod={}", httpMethod);
log.info("Locale={}", locale);
return "success";
}
}
Postman API 호출
log 출력 결과
hashMap={
user-agent=[PostmanRuntime/7.35.0],
accept=[*/*],
postman-token=[5f324c1c-7902-4750-9e01-2c4d093e8ad6],
host=[localhost:8080],
accept-encoding=[gzip, deflate, br],
connection=[keep-alive]
}
HttpServletRequest.getParameter(”key”)1. GET - Query Param
- URL의 쿼리 파라미터를 사용하여 데이터 전달
http://localhost:8080/param?key1=value1&key2=value2
예시 Controller
@Slf4j
@Controller
public class RequestParamController {
@GetMapping("/request/params")
public void params(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
String key1Value = request.getParameter("key1");
String key2Value = request.getParameter("key2");
log.info("key1Value={}, key2Value={}", key1Value, key2Value);
response.getWriter().write("success");
}
}
Postman 요청

log 출력결과

. POST - HTML Form(x-www-form-urlencoded)
- Request Body에 쿼리 파라미터 형태로 전달
POST /param
content-type: application/x-www-form-urlencoded
key1=value1&key2=value2
예시 Controller
@Slf4j
@Controller
public class RequestBodyController {
@PostMapping("/request/body")
public void requestBody(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
String key1Value = request.getParameter("key1");
String key2Value = request.getParameter("key2");
log.info("key1Value={}, key2Value={}", key1Value, key2Value);
response.getWriter().write("success");
}
}
Postman


HTTP Request Body - 데이터(JSON, TEXT, XML 등)를 직접 담아서 사용
- 주로 RestController에서 사용한다, 대부분 JSON으로 데이터 전달
ex) POST, PUT, PATCH Method에서 사용 GET, DELETE는 거의 Body가 없음 → 권장X

@Controller
public class RequestParamController {
@ResponseBody
@GetMapping("/request/param")
public String requestParam (
@RequestParam("name") String userName,
@RequestParam("age") int userAge
) {
// logic
return "success";
}
}
@Controller 이지만 Method에 @ResponseBody를 붙이게되면
View를 찾는것이 아니라 ResponseBody 데이터에 “success”가 들어간다.
→ 위 코드가 @RestController의 원본코드라 생각하시면 됩니다.
@RequestParam : 파라미터 이름으로 바인딩
@RequestParam(”속성값”) : 속성값이 파라미터 이름으로 매핑된다
“속성값”과 변수명이 같으면 생략이 가능하다.
ex) @RequestParam(name) String name
// GET http://localhost:8080/request/param?name=wonuk&age=100
@Controller
public class RequestParamController {
@ResponseBody
@GetMapping("/request/param")
public String requestParam (
@RequestParam String name,
@RequestParam int age
) {
// logic
return "success";
}
}
충격적이게도 사실 모두 생략해도된다.
참고 : 생략하면 (required=false) 속성이 default로 설정된다
// GET http://localhost:8080/request/param?name=wonuk&age=100
@Controller
public class RequestParamController {
@ResponseBody
@GetMapping("/request/param")
public String requestParam (
String name,
int age
) {
// logic
return "success";
}
}
하지만 위의 방식은 권장하지 않는다, 명시적으로 표시되어있지 않으면
팀의 협의가 있지 않다면, 다른 개발자들에게 혼동을주기 충분하다.
@RequestParam이 있어야 요청 파라미터에서 데이터를 읽는구나..! 하게된다
파라미터 필수 여부 설정 required 속성
💡 API 스펙을 규정할 때 사용한다. 필수값 설정@Controller
public class RequestParamController {
@ResponseBody
@GetMapping("/request/param")
public String requestParam (
@RequestParam(required = true) String name, // 필수
@RequestParam(required = false) int age // 필수가 아님
// @RequestParam(required = false) Integer age
) {
// logic
return "success";
}
}
@RequestParam을 사용하면 기본 Default값은 True이다.
http://localhost:8080/request/param?age=100http://localhost:8080/request/paramrequired = false 설정이 되어있으면 해당 파라미터는 없어도 된다.
하지만! 여기서 만약 http://localhost:8080/request/param?name=wonuk 로 요청한다면?
500 Error가 발생한다
int Type에는 null을 넣을 수 없기 때문에! 0이라도 들어가야한다
따라서 보통 null을 허용하는 Integer로 사용하거나 default 옵션을 사용한다.

파라미터 Key값만 있고 Value가 없는 경우 http://localhost:8080/request/param?name=
@Controller
public class RequestParamController {
@ResponseBody
@GetMapping("/request/param")
public String requestParam (
@RequestParam(required = true, defaultValue = "wonuk") String name,
@RequestParam(required = false, defaultValue = "1") int age
) {
// logic
return "success";
}
}
http://localhost:8080/request/param?age=100http://localhost:8080/request/paramhttp://localhost:8080/request/param?name=wonukhttp://localhost:8080/request/param@Controller
public class RequestParamController {
@ResponseBody
@GetMapping("/request/param")
public String requestParam (
@RequestParam Map<String, Object> paramMap
) {
// logic
String name = paramMap.get("name");
Integer age = paramMap.get("age");
return "success";
}
}
key=valuehttp://localhost:8080/request/param?name=wonuk&age=100key=[value1, value2]http://localhost:8080/request/param?name=wonuk1&name=wonuk2&name=wonuk3파라미터 Map의 Value가 1개인 경우에는 Map,
여러개인 경우 MultiValueMap을 사용한다
하지만 대부분 한개인 경우로 구성되어 있다.
오늘의 코드카타
class Solution {
public int[][] solution(int n) {
int[][] answer = new int[n][n];
int value = 1;
int row = 0;
int col = 0;
int direction = 0;
while (value <= n * n) {
answer[row][col] = value++;
if (direction == 0) {
if (col == n - 1 || answer[row][col + 1] != 0) {
direction = 1;
row++;
} else {
col++;
}
} else if (direction == 1) {
if (row == n - 1 || answer[row + 1][col] != 0) {
direction = 2;
col--;
} else {
row++;
}
} else if (direction == 2) {
if (col == 0 || answer[row][col - 1] != 0) {
direction = 3;
row--;
} else {
col--;
}
} else if (direction == 3) {
if (row == 0 || answer[row - 1][col] != 0) {
direction = 0;
col++;
} else {
row--;
}
}
}
return answer;
}
}
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Solution solution = new Solution();
System.out.println(Arrays.deepToString(solution.solution(4)));
System.out.println(Arrays.deepToString(solution.solution(5)));
}
}
정수를 나선형으로 배치하기