컨트롤러 데이터 수신

-----
@RequestBody 
HTTP 요청으로 넘어오는 body의 내용을 HttpMessageConverter를 통해 Java Object로 역직렬화한다.

multipart 요청이 아닌, 즉 어떤 바이너리 파일을 포함하고 있지 않은 데이터를 받는 역할을 한다.

HttpMessageConverter란?
간단하게 HTTP 요청과 응답에 대해서 "전략 패턴"을 사용해서 converting 해주는 역할.

전략 패턴은 하나의 메서드가 여러 가지의 대응 방법을 미리 준비해두고 
필요한 상황마다 대응 방법을 달리 하는 방법을 말한다.
 

RequestBody는 HTTP 요청으로 같이 넘어오는 Header의 Content-type을 보고 
어떤 Converter를 사용할지 정하기에 Content-type을 반드시 명시해야 한다.

Content-type의 종류 (중 일부)

application/json: {key : value}의 형태로 전송
application/x-www-form-urlencoded: key=value&key=value 형태로 전송(HTML form의 default 값)
multipart/form-data: 파일 업로드시 사용되며 '파일을 비롯한 
여러 데이터가 있음'이라는 뜻을 가짐. (@RequestBody로 받을 수 없음 !!)
 

사용 방법: REST API를 통해 단순 데이터를 주고 받는 일반적인 경우에 사용할 수 있다. 

Controller에서 받을때 어노테이션 생략시 @ModelAttribute가 default이므로 
@RequestBody를 사용하고자 하는 경우에는 반드시 기술해야 한다.

* 기본생성자 필요 

-----

@RequestPart

RequestPart는 HTTP request body에 multipart/form-data 가 포함되어 있는 경우에 
사용하는 어노테이션이다. ( 스프링에서 제공하는 어노테이션이다. )
 MultipartFile이 포함되어 있는 경우에는 MultipartResolver가 동작하여 
역직렬화를 하게 된다. ( Byte로 되어있는 데이터를 객체 형태로 변환한다. ) 
만약 MultipartFile이 포함되어있지 않다면, RequestBody와 마찬가지로 동작하게 된다.

Content-type이 'multipart/form-data'와 관련된 경우에 사용한다.

MultipartFile이 포함되는 경우에 MutliPartResolver가 동작하여 
(여기서도 전략 패턴이 사용된다) 역직렬화를 하게 됨.

MultipartFile이 포함되지 않는 경우는 @RequestBody와 같이 
HttpMessageConverter가 동작하게 된다.

사용 방법: @RequestBody가 필요하지만 Binary Stream이 포함되는 경우
(MultipartFile과 같은)에 사용할 수 있다.

* 이거 한꺼번에 받으려면 프론트에서 한번 감싸서 보내야 한다. 한꺼번에 받을 키값이 있어야 함
* consumes = {MediaType.MULTIPART_FORM_DATA_VALUE} 없어도 받기는 가능 (디폴트가 multipart)
* 만약 JSON 과 같이 받으려면 
{MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE} 둘다 선언
생략시 415 Unsupported Media Type 오류 발생 가능성 존재 하나 오류발생은 안하는듯

* DTO에 Multipart 타입의 필드가 있으면, form 데이터는 특별한 어노테이션 없이
바로 dto로 받아도 됨
* 같이 보낼때 JOSN은 new Blob([JSON.stringify(requestData)], {type: "application/json"}) 
* 무조건 파일과 제이슨은(블롭처리된) 따로 받아야 한다

// Dto 가 아니아 JSON 을 받을때
@PostMapping("/api/endpoint")
public void handleRequest(
  @RequestPart("file") MultipartFile file,
  @RequestPart("data") String jsonData
) {
  // 파일 및 JSON 데이터 처리
  YourDtoClass dto = new ObjectMapper().readValue(jsonData, YourDtoClass.class);
  // JSON 데이터를 DTO 객체로 변환
}

* key-value 값으로 보낼때에는 Content-type을 application/json 타입으로 명시해주어야 
객체로 역직렬화가 정상적으로 이루어짐

* KEY로 파일을 등록하면 List (Postman 에서)

* Dto는 setter가 없어도 된다.

* 기본 생성자만 있어도 바인딩 가능
* Setter 만 있어도 바인딩 가능
* @AllArgsConstructor 혼자서는 바인딩 불가능 X

-----

@RequestParam
하나의 파라미터만을 받을때 사용된다. 
기본적으로 파라미터가 필수적으로 들어오게 설정되어 있기에 
파라미터가 들어오지 않는 경우 
BadRequest가 발생하므로 파라미터가 들어올 수도, 
들어오지 않을 수도 있다면 required = false를 주어야 한다.

@RequestParam 또한 @RequestPart와 같이 MultipartFile을 받을 때 사용할 수 있다.

@RequestPart와의 다른 점은 
@RequestParam의 경우 파라미터가 String이나 MultipartFile이 아닌 경우 
Converter나 PropertyEditor에 의해 처리 되지만 
@RequestPart는 HttpMessageConverter를 이용하여 
Content-type을 참고하여 처리한다는 점이다.
* form 의 데이터를 가져오는거 가능, form의 File 가능, 쿼리스트링의 데이터 가능

 

이때 하나의 요청 파라미터에만 대응한다고 해서 
1개의 MultipartFile만 받을 수 있는게 아닌 List<MultipartFile>의 형태로도 받을 수 있으며

모든 파라미터를 Map<String, String>처럼 
한 번에 받을 수 있으나 많은 데이터를 하나의 파라미터로 받는 것은 
유지 보수성의 측면에서 좋지 못 하므로 
많은 데이터를 주고받는 경우에는 
@RequestBody와 DTO를 이용하는 편이 좋다.

 
사용 방법: 간단한 name-value 형태의 데이터를 주고받을 때 사용할 수 있으며 
파라미터가 복잡해지는 경우 
@RequestBody나 @RequestPart를 이용하여 DTO를 사용하는 것이 좋다.

* 계속 선언해서 하나 하나씩 받을수 있음 (파일 포함)
* consumes 속성이 없음 그러므로 명시적 선언도 안해도 됨

-----

@ModelAttribute
상술했듯이 Spring Controller에서 값을 받을때 default가 @ModelAttribute이며 
다음과 같은 상황에 사용할 수 있다.

Content-type이 multipart/form-data의 형태를 받을때
HTTP 파라미터를 받는 경우
즉 HTTP body로 오든 파라미터로 오든 다 받을 수 있고 
body와 파라미터가 같이 오는 경우에도 값이 바인딩된다. 

이런 형태가 가능한 이유는 @ModelAttribute는 필드 내부와 
1:1로 값이 Setter나 Constructor를 통해 매핑되기 때문이다. 

@RequestPart와 다른 점은 HttpMessageConverter에 의해 
값이 바인딩되는 것이 아닌 적절한 Setter 혹은 Constructor를 통해 값이 주입된다는 점이다.

HttpMessageConverter가 동작하는 @RequestPart나 @RequestBody의 경우 
필드를 찾을때 ObjectMapper를 이용하고 
이 ObjectMapper는 NoArgsConstructor와 Getter나 Setter 등을 통해 
private field에 접근 할 수 있게 구현되어 있다.
이때 접근할 수 있는 이유는 Jackson 라이브러리는 
Reflection을 통해서 private field에 값을 할당할 수 있기 때문이다.

ObjectMapper를 Override하면 어떤 접근자도 필요없게 만들 수도 있다!

즉 해당 DTO의 필드에 접근할 수 있는 적절한 수단이 존재하지 않으면 값이 바인딩 될 수 없다.
* Dto에 Setter 가 필요
* consumes = {MediaType.MULTIPART_FORM_DATA_VALUE} 없어도 받기는 가능 (기본값)
* setter 없으면 @AllArgsConstructor 이용해서 작동 (기본생성자 있으면 작동 X)
** @NoArgsConstructor + @AllArgsConstructor 둘다 있으면 바인딩 안됨
이유: 바인딩 시 파라미터 개수가 0개인 기본 생성자가 확인되면 
우선 인스턴스(객체)를 생성하고, setter 메서드를 통한 바인딩을 시도
이 경우는 Setter 가 있어야 바인딩 됨
** 기본 생성자 없이 @AllArgsConstructor 만 있으면 Setter 없이 바인딩 가능
** @NoArgsConstructor 만 있어도 작동 X

 

정리
@RequestBody
application/json을 주고받을때 주로 사용함. multipart/form-data이 포함되는 경우는 사용 불가.

@RequestPart
@RequestBody + multipart/form-data인 경우에 사용. 

 
RequestBody와 RequestPart는 HttpMessageConverter에 의해 동작하므로 
Setter 없이 Object 생성됨.

@RequestParam
1개의 HTTP 파라미터를 받을 때 사용. 

multipart/form-data을 받아야 되는 경우에 사용 가능.

기본 설정으로 필요 여부가 필수로 되어있음.

@ModelAttribute
@RequestPart와 유사하지만 동작 원리는 완전히 다르다.

값에 직접적으로 접근할 수 있는 수단이 필요.

@Setter 없으니 JSON 및 파일 못받아온다. @Setter 
추가해주자

배열


Array => 리스트 변환

1. Arrays.asList

Arrays.asList()로 반환된 list는 변경이 가능합니다. 
하지만, List.of()에서 반환된 메서드는 변경이 불가능합니다.

List<Integer> list = Arrays.asList(1, 2, null);
list.set(1, 10); // OK

List<Integer> list = Arrays.asList(1, 2, 3);
list.contains(null); // Returns false

-- 
(참고) Arrays.asList()가 반환하는 ArrayList 타입은 
java.util.ArrayList가 가 아니라 Arrays 내부 클래스입니다. 
add와 remove는 구현되어 있지 않아서, 크기는 변경할 수 없습니다.
-- 

-- 
Array.asList()는 null을 허용합니다. 
--

2. List.of

List<Integer> list = List.of(1, 2, 3);
list.set(1, 10); // Fails with UnsupportedOperationException

List<Integer> list = List.of(1, 2, 3);
list.contains(null); // Fails with NullPointerException

-- 
List.of()는 반환 객체가 생성될 때, 
내부적으로 파라미터들에 대한 null체크를 하고 null을 허용하지 않습니다.
--

-- 정리 --
Arrays.asList()는 크고 동적인 데이터에 사용하고, 
List.of()는 작고 변경되지 않는 데이터의 경우 사용합니다.

List.of()는 필드 기반 구현이 있고, 
내부적으로 힙 공간을 덜 사용하기에 요소 자체가 필요하다면 List.of()가 적절합니다.

Arrays.asList()는 변경이 가능하고 thread safety 하지 않고, 
List.of()는 불변하고 thread safety합니다. 

Arrays.asList()는 null 요소를 허용하고 
List.of()는 null 요소를 허용하지 않습니다.

Arrays.asList(), List.of() 모두 크기는 변경할 수 없다. 
크기를 바꾸려면 Collections을 생성해서 요소의 값을 옮겨야 합니다.

	set 사용 가능
new ArrayList<>() 원소를 추가/삭제->	가능,  ->	가능
Arrays.asList()	원소를 추가/삭제->	불가능, set -> 가능
List.of() 원소를 추가/삭제 ->	불가능, set ->	불가능

JWT Filter

JwtFilter 필터 구현 시
OncePerRequestFilter
상속 받아 
@Component
등록 시 자동으로 모든 서블릿 요청에 적용 되므로 주의 요함

SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>
상속받아
JwtConfigurer 구현시

configure에
커스텀 필터 속해 있고, Security 에 등록되어 있으므로 주석시 주의