/**
* @typedef {object} address
* @property {string} street
* @property {string} city
*/
/**
* @typedef {object} User
* @property {string} name
* @property {string} email
* @property {Address} address
*/
/**
* @returns {Promise<User>}
*/
function fetchUser() {
return axios.get(url);
}
fetchUser().then(function(response){
response.address.cit; // 에러 사전 방지.
})
코드 자동 완성 가이드.
function sum(a, b) {
return a + b;
}
sum(10, 20); // 30
sum(10, '20'); // 1020
아래와 같이 입력 타입, 반환 타입 설정 가능.
타입에 맞지 않는 값이 들어오는 경우 코드상에서 에러 표시 됨.
타입에 따라 제공되는 api 자동완성 기능 사용 가능.
function add(a: number, b: number): number {
return a + b;
}
var result = add(10, 20);
//add(10, '20'); // 정해진 타입이 아닌 경우, 코드상에서 에러 표시.
result.toLocaleString(); // 제공되는 타입에 따른 api 사용 가능.
자바스크립트를 타입스크립트처럼 코딩하는 방법
// @ts-check
/**
* @param {number} a 첫 번째 숫자
* @param {number} b 두 번째 숫자
*/
function sum(a, b) {
return a + b;
}
sum(10, 20);
sum(10, '20'); // @ts-check 설정 시 ts 사용하는 것 같은 효과 제공.
이동: https://getbootstrap.com/docs/5.0/getting-started/download/
Compiled CSS and JS 항목을 다운로드.
압축 출고 bootstrap.min.css 를 복사해 아래 폴더에 추가.
resources/static/css/bootstrap.min.css
상품 등록 폼의 URL과 실제 상품 등록을 처리하는 URL을 동일하게 맞추고 HTTP 메서드로 두 기능을 구분.
상품 등록 폼 : GET /basic/items/add
상품 등록 처리 : POST /basic/items/add
이렇게 하면 하나의 URL로 등록 폼과, 등록 처리를 깔끔하게 처리할 수 있다.
## 상품 등록 처리_@ModelAttribute
POST - HTML Form
content-type: application/x-www-form-urlencoded
메시지 바디에 쿼리 파리미터 형식으로 전달 itemName=itemA&price=10000&quantity=10
예) 회원 가입, 상품 주문, HTML Form 사용
요청 파라미터 형식을 처리해야 하므로 @RequestParam 사용.
상품 등록 처리_@RequestParam
addItemV1 - BasicItemController에 추가
@PostMapping("/add")
public String addItemV1(@RequestParam String itemName,
@RequestParam int price,
@RequestParam Integer quantity,
Model model) {
Item item = new Item();
item.setItemName(itemName);
item.setPrice(price);
item.setQuantity(quantity);
itemRepository.save(item);
model.addAttribute("item", item);
return "basic/item";
}
addItemV2 - BasicItemController에 추가
@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item) {
itemRepository.save(item);
// model.addAttribute("item", item); // 자동 추가, 생략 가능
return "basic/item";
}
@ModelAttribute 중요 기능
1. 요청 파라미터 처리
@ModelAttribute 는 Item 객체를 생성, 요청 파라미터의 값을 프로퍼티 접근법(setXxx)으로 입력해준다.
2. Model 추가
모델(Model)에 @ModelAttribute 로 지정한 객체를 자동으로 넣어준다.
위 코드에서 model.addAttribute("item", item) 가 주석처리 되어 있어도 잘 동작.
모델에 데이터를 담을 때는 이름이 필요. 이름은 @ModelAttribute 에 지정한 name(value) 속성을 사용.
만약 다음과 같이 @ModelAttribute 의 이름을 다르게 지정하면 다른 이름으로 모델에 포함된다.
@ModelAttribute("hello") Item item -> 이름을 hello 로 지정
model.addAttribute("hello", item); -> 모델에 hello 이름으로 저장
addItemV3 - BasicItemController에 추가
@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item) {
// @ModelAttribute("item")에서 ("item") 생략 시 Item -> item이 modelAttribute 담기게 됨.
itemRepository.save(item);
// model.addAttribute("item", item); // 자동 추가, 생략 가능
return "basic/item";
}
@ModelAttribute 의 이름을 생략할 수 있다. (@ModelAttribute 의 이름을 생략 시 모델에 저장될 때 클래스명을 사용. 이때 클래스의 첫글자만 소문자로 변경해서 등록한다.)
@ModelAttribute 클래스명 모델에 자동 추가되는 이름
Item -> item
HelloWorld -> helloWorld
addItemV4 - BasicItemController에 추가
@PostMapping("/add")
public String addItemV4(Item item) {
itemRepository.save(item);
// model.addAttribute("item", item); // 자동 추가, 생략 가능
return "basic/item";
}
@ModelAttribute 자체도 생략가능.
대상 객체는 모델에 자동 등록.
나머지 사항은 기존과 동일하다.
## 상품 수정.
BasicItemController : editForm 추가.
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "basic/editForm";
}
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
itemRepository.update(itemId, item);
return "redirect:/basic/items/{itemId}";
}
상품 수정은 상품 등록과 전체 프로세스가 유사.
GET /items/{itemId}/edit : 상품 수정 폼
POST /items/{itemId}/edit : 상품 수정 처리
리다이렉트
상품 수정은 마지막에 뷰 템플릿을 호출하는 대신 상품 상세 화면으로 이동하도록 리다이렉트를 호출.
스프링은 redirect:/... 으로 편리하게 리다이렉트를 지원.
redirect:/basic/items/{itemId}" 컨트롤러에 매핑된 @PathVariable 의 값은 redirect 에서도 사용 가능.
redirect:/basic/items/{itemId} {itemId} 는 @PathVariable Long itemId 의 값을 그대로 사용.
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
/**
* Strategy interface for converting from and to HTTP requests and responses.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 3.0
* @param <T> the converted object type
*/
public interface HttpMessageConverter<T> {
/**
* Indicates whether the given class can be read by this converter.
* @param clazz the class to test for readability
* @param mediaType the media type to read (can be {@code null} if not specified);
* typically the value of a {@code Content-Type} header.
* @return {@code true} if readable; {@code false} otherwise
*/
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
/**
* Indicates whether the given class can be written by this converter.
* @param clazz the class to test for writability
* @param mediaType the media type to write (can be {@code null} if not specified);
* typically the value of an {@code Accept} header.
* @return {@code true} if writable; {@code false} otherwise
*/
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
/**
* Return the list of media types supported by this converter. The list may
* not apply to every possible target element type and calls to this method
* should typically be guarded via {@link #canWrite(Class, MediaType)
* canWrite(clazz, null}. The list may also exclude MIME types supported
* only for a specific class. Alternatively, use
* {@link #getSupportedMediaTypes(Class)} for a more precise list.
* @return the list of supported media types
*/
List<MediaType> getSupportedMediaTypes();
/**
* Return the list of media types supported by this converter for the given
* class. The list may differ from {@link #getSupportedMediaTypes()} if the
* converter does not support the given Class or if it supports it only for
* a subset of media types.
* @param clazz the type of class to check
* @return the list of media types supported for the given class
* @since 5.3.4
*/
default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return (canRead(clazz, null) || canWrite(clazz, null) ?
getSupportedMediaTypes() : Collections.emptyList());
}
/**
* Read an object of the given type from the given input message, and returns it.
* @param clazz the type of object to return. This type must have previously been passed to the
* {@link #canRead canRead} method of this interface, which must have returned {@code true}.
* @param inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
* @throws HttpMessageNotReadableException in case of conversion errors
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
/**
* Write an given object to the given output message.
* @param t the object to write to the output message. The type of this object must have previously been
* passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
* @param contentType the content type to use when writing. May be {@code null} to indicate that the
* default content type of the converter must be used. If not {@code null}, this media type must have
* previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
* returned {@code true}.
* @param outputMessage the message to write to
* @throws IOException in case of I/O errors
* @throws HttpMessageNotWritableException in case of conversion errors
*/
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
HTTP 메시지 컨버터는 HTTP 요청, HTTP 응답 둘 다 사용.
canRead() , canWrite() : 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크.
read() , write() : 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능.
클래스 타입: 객체 또는 HashMap , 미디어타입 application/json 관련
요청 예) @RequestBody HelloData data
응답 예) @ResponseBody return helloData 쓰기 미디어타입 application/json 관련
HTTP 요청 데이터 읽기
HTTP 요청이 오고, 컨트롤러에서 @RequestBody , HttpEntity 파라미터를 사용한다.
메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 호출.
대상 클래스 타입을 지원하는가 : 예) @RequestBody 의 대상 클래스 ( byte[] , String , HelloData )
HTTP 요청의 Content-Type 미디어 타입을 지원하는가 : 예) text/plain , application/json , */*
canRead() 조건을 만족하면 read() 를 호출해서 객체 생성하고, 반환.
HTTP 응답 데이터 생성
컨트롤러에서 @ResponseBody , HttpEntity 로 값 반환.
메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 를 호출.
대상 클래스 타입을 지원하는가 : 예) return의 대상 클래스 ( byte[] , String , HelloData )
HTTP 요청의 Accept 미디어 타입을 지원하는가(정확히는 @RequestMapping 의 produces) : 예) text/plain , application/json , */*
canWrite() 조건을 만족하면 write() 를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성.
## 요청 매핑 헨들러 어뎁터 구조 (RequestMappingHandlerAdapter)
@RequestMapping 을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter (요청 매핑 헨들러 어뎁터)
ArgumentResolver (파라미터 처리!)
애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용. HttpServletRequest , Model 은 물론이고, @RequestParam , @ModelAttribute 같은 애노테이션 그리고 @RequestBody , HttpEntity 같은 HTTP 메시지를 처리하는 부분까지 큰 유연함을 보여주었다.
이렇게 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver 덕분.
애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdaptor 는 바로 이 ArgumentResolver 를 호출해 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성. 그리고 파리미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.
스프링은 30개가 넘는 ArgumentResolver 를 기본으로 제공. (HandlerMethodArgumentResolver 인데 줄여서 ArgumentResolver 라고 부른다)
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Strategy interface for resolving method parameters into argument values in
* the context of a given request.
*
* @author Arjen Poutsma
* @since 3.1
* @see HandlerMethodReturnValueHandler
*/
public interface HandlerMethodArgumentResolver {
/**
* Whether the given {@linkplain MethodParameter method parameter} is
* supported by this resolver.
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter;
* {@code false} otherwise
*/
boolean supportsParameter(MethodParameter parameter);
/**
* Resolves a method parameter into an argument value from a given request.
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to {@link #supportsParameter} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null} if not resolvable
* @throws Exception in case of errors with the preparation of argument values
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
동작방식 : ArgumentResolver 의 supportsParameter() 를 호출 해당 파라미터를 지원하는지 체크, 지원하면 resolveArgument() 를 호출해서 실제 객체를 생성. 그리고 이렇게 생성된 객체가 컨트롤러 호출 시 넘어간다.
ReturnValueHandler
HandlerMethodReturnValueHandler 를 줄여 ReturnValueHandle 라고 부른다.
ArgumentResolver 와 비슷한데, 이것은 응답 값을 변환하고 처리.
컨트롤러에서 String으로 뷰 이름을 반환해도, 동작하는 이유가 바로 ReturnValueHandler 덕분이다.
스프링은 10여개가 넘는 ReturnValueHandler 를 지원. (ModelAndView , @ResponseBody , HttpEntity , String 등)
HTTP 메시지 컨버터
HTTP 메시지 컨버터를 사용하는 @RequestBody 도 컨트롤러가 필요로 하는 파라미터의 값에 사용.
@ResponseBody 도 컨트롤러의 반환 값을 이용.
ArgumentResolver은 HTTP 메서지 컨버터 사용.
요청
@RequestBody 를 처리하는 ArgumentResolver, HttpEntity 를 처리하는 ArgumentResolver 가 존재.
이 ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성하는 것.
응답
@ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 가 존재. 그리고 여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.
스프링 MVC는 @RequestBody @ResponseBody 가 있으면 RequestResponseBodyMethodProcessor (ArgumentResolver), HttpEntity 가 있으면 HttpEntityMethodProcessor (ArgumentResolver)를 사용.