
๊ธฐ์กด ๊ตฌ์กฐ์
view resolver๊ฐ ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ ๋์ , http ๋ฉ์ธ์ง๋ฅผ ๋ณด์ฌ์ค

์์ฒญ์ โข ArgumentResolver ์ฌ์ฉ
: @RequestBody, HttpEntity<>, RequestEntity<>
์๋ต์ โข ReturnValueHandler ์ฌ์ฉ
: @ResponseBody, HttpEntity<>, ResponseEntity<>
๐ก ๊ตฌ์กฐ์ ์๋ฐฉํฅ์ผ๋ก (์์ฒญ, ์๋ต) ์ ๋ถ ์๋ํ๋ค
โถ๏ธ
ByteโขStringโขJSON
์์ฒญ
- 1๏ธโฃ
RequestBody๋ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ธ๋ฉ- 2๏ธโฃ
canRead()๋ก ๋ฐ์ดํฐ ํ์ ์ ์กฐํ,Content-Typeํค๋์ ๋ฏธ๋์ดํ์ ์ง์์ฌ๋ถ ํ์ธ- 3๏ธโฃ
read()๋ก ๊ฐ์ฒด๋ก ๋ณํ
์๋ต
- 1๏ธโฃ ์ปจํธ๋กค๋ฌ๊ฐ
@ResponseBody๋HttpEntity<>๋ฆฌํดํจ- 2๏ธโฃ
canWrite()๋ก ๋ฐ์ดํฐ ํ์ ์ ์กฐํ,Acceptํค๋์ ๋ฏธ๋์ดํ์ ์ง์์ฌ๋ถ ํ์ธ- 3๏ธโฃ
write()๋ก ์๋ต๋ฉ์ธ์ง ๋ฐ๋์ ์ง์ ๋ฐ์ดํฐ ์ ๋ ฅ
๐ ์ปจํธ๋กค๋ฌ ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ฅผ ์๋์ผ๋ก ๋ฐ์ธ๋ฉํด์ฃผ๋ ์ธํฐํ์ด์ค
RequestMappingHandlerAdapter๊ฐ ํธ์ถํจ๐ ์ค์ ํด๋์ค ์ด๋ฆ : HandlerMethodArgumentResolver
๐ ์ปจํธ๋กค๋ฌ ๋ฉ์๋๊ฐ ๋ฐํํ๋ ๊ฐ์ HTTP ์๋ต์ผ๋ก ๋ณํํ๋ ์ธํฐํ์ด์ค
ModelAndView, @ResponseBody, HttpEntity<> ๋ฑ๐ ์ค์ ํด๋์ค ์ด๋ฆ : HandlerMethodReturnValueHandler
๐ก ์์ฒญ๊ณผ ์๋ต ๋ชจ๋์์ ์ฌ์ฉ๋๋ ํด๋์ค
โถ๏ธ
ArgumentResolver+ReturnValueHandler๋ฅผ ๋๋ค ๊ตฌํํ ํด๋์ค
ํด๋์ค๋ช ์ฒ๋ฆฌ ๋์ RequestResponseBodyMethodProcessor @RequestBody, @ResponseBody HttpEntityMethodProcessor HttpEntity<>, RequestEntity<>, ResponseEntity<> โ ๋ด๋ถ์์ HttpMessageConverter๋ฅผ ํธ์ถํด ๊ฐ์ฒด ์์ฑ ๋๋ ์๋ต ๋ณํ์ ์ฒ๋ฆฌํจ
์ค์ ์ ํตํด
Converter,Formatter๋ฅผ ์ถ๊ฐํ ์ ์๋ค
Converter, Formatter, HttpMessageConverter ๋ฑ์ ๋ฑ๋กํ ๋ ์ค๋ฒ๋ผ์ด๋ฉํด์ ์ฌ์ฉ@Configuration ๋ ์์ โข @Component๋ฅผ ํฌํจ โข Spring Bean์ผ๋ก ๋ฑ๋ก๊ฐ์ฒด์ ํ์ ์ ์๋ก ๋ณํํ ๋
HttpServletRequest
๊ธฐ๋ณธ์ ์ผ๋ก ์์ฒญ ํ๋ผ๋ฏธํฐ๋ ๋ฌธ์์ด๋ก ์ฒ๋ฆฌ๊ฐ ๋๊ธฐ ๋๋ฌธ์
์ซ์๋ก ๋ณํํ๊ณ ์ถ๋ค๋ฉด, ํ๋์ฉ ์ง์ ๋ณํํด์ค์ผํ๋ค
@Slf4j
@RestController
public class TypeConverterController {
@GetMapping("/param")
public void param(HttpServletRequest request) {
// ์กฐํ์ : String
String stringExample = request.getParameter("example");
// โ
Integer๋ก Type ๋ณํ
Integer integerExample = Integer.valueOf(stringExample);
log.info("integerExample = {}", integerExample);
}
}
@RequestParam@GetMapping("/v2/param")
public void paramV2(@RequestParam Integer example) {
// Integer ํ์
์ผ๋ก ๋ฐ์ธ๋ฉ
log.info("example = {}", example);
}ํน์ ํ์ ์ ๋ค๋ฅธ ํ์ ์ผ๋ก ๋ณํํ ๋ ์ฌ์ฉํ๋ ์ธํฐํ์ด์ค
localhost:8080/type-converter?person=wonuk:120Person ๊ฐ์ฒด๋ก ๋ณํpublic class Person {
private String name;
private String age;
}wonuk:120 โ TypeConverter โ name: wonuk , age : 10 (๊ฐ์์๋ฅผ ๋์ด๋ก)โ ๏ธ ์ฃผ์ : ๊ฐ์ ์ด๋ฆ์ ๊ฐ์ง
interface๊ฐ ๋ง์ผ๋ ์ฃผ์
org.springframework.core.convert.converter
โถ String โ Integer
@Slf4j
public class StringToIntegerConverter implements Converter<String, Integer> {
@Override
public Integer convert(String source) {
log.info("source = {}", source);
// ๊ฒ์ฆ
return Integer.valueOf(source);
}
}
โท Integer โ String
@Slf4j
public class IntegerToStringConverter implements Converter<Integer, String> {
@Override
public String convert(Integer source) {
log.info("source = {}", source);
return String.valueOf(source);
}
}
โธ String โ Person
(์์ฒญ์์ : localhost:8080/type-converter?person=wonuk:1200)
@Getter
public class Person {
// ์ด๋ฆ
private String name;
// ๋์ด
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
------------------
public class StringToPersonConverter implements Converter<String, Person> {
// source = "wonuk:1200"
@Override
public Person convert(String source) {
// ':' ๋ฅผ ๊ตฌ๋ถ์๋ก ๋๋์ด ๋ฐฐ์ด๋ก ๋ง๋ ๋ค.
String[] parts = source.split(":");
// ์ฒซ๋ฒ์งธ ๋ฐฐ์ด์ ์ด๋ฆ์ด๋ค. -> wonuk
String name = parts[0];
// ๋๋ฒ์งธ ๋ฐฐ์ด์ ๊ฐ์์์ด๋ค. -> 1200
int months = Integer.parseInt(parts[1]);
// ๊ฐ์์ ๋๋๊ธฐ 12๋ก ๋์ด๋ฅผ ๊ตฌํ๋ ๋ก์ง (12๊ฐ์ ๋จ์๋ง ๊ณ ๋ ค)
int age = months / 12;
return new Person(name, age);
}
}
------------------
public class PersonToStringConverter implements Converter<Person, String> {
@Override
public String convert(Person source) {
// ์ด๋ฆ
String name = source.getName();
// ๊ฐ์์
int months = source.getAge * 12;
// "wonuk:1200"
return name + ":" + months;
}
}
------------------
PersonToStringConverter converter = new PersonToStringConverter();
String source = "wonuk:1200";
converter.convert(source);
Converter
Converter<Source, Type>ConverterFactory
GenericConverter
ConditionalGenericConverter
1๋ฒ๋ง ์ธ์ฐ๊ณ , ๋๋จธ์ง๋ ํ์ํ ๋ ๊ณต์๋ฌธ์์์ ์ฐพ๋์ง~
์ปจ๋ฒํฐ๋ฅผ ๋ชจ์์ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ๊ฒ ํด์ค๋ค
ConversionService๋ฅผ ๊ตฌํํ ๊ตฌํ์ฒด, ConvertRegistry์ ๋ค์ํ Converter๋ฅผ ๋ฑ๋ก
Converter๋ฅผ ๋ฑ๋กํ๊ณ ๊ด๋ฆฌ
import static org.assertj.core.api.Assertions.*;// โ
Assertions static ์ํฌํธ ํ์
public class ConversionServiceTest {
@Test
void defaultConversionService() {
// given
DefaultConversionService dcs = new DefaultConversionService();
dcs.addConverter(new StringToPersonConverter());
Person wonuk = new Person("wonuk", 100);
// when
Person stringToPerson = dcs.convert("wonuk:1200", Person.class);
// then
assertThat(stringToPerson.getName()).isEqualTo(wonuk.getName());
assertThat(stringToPerson.getAge()).isEqualTo(wonuk.getAge());
}
}
์ ์ฝ๋๋ฅผ ํตํด ์์์๋ ๊ฒ โข> ์ปจ๋ฒํฐ ๋ฑ๋ก / ์ฌ์ฉ์ ๋ถ๋ฆฌ ! (ISP ์์น ์ ์ฉ)
1๏ธโฃ
WebMvcConfigurer๋ฅผ ๊ตฌํ
2๏ธโฃaddFomatters()๋ก ์ปจ๋ฒํฐ๋ฅผ ๋ฑ๋ก๋งํ๊ณ ์ฌ์ฉ
โถ๏ธ ๊ฒฐ๊ณผ
๊ฐ์ฒด๋ฅผ ํน์ ํ ํฌ๋งท์ ๋ง์ถฐ์ ๋ฌธ์๋ก ์ถ๋ ฅํ๋๋ฐ ํนํ (
Converter๋ณด๋ค ๋ ์ธ๋ถ๊ธฐ๋ฅ )
printer, parser ์์ ( ๊ฐ์ฒด๋ฅผ ๋ฌธ์๋ก ๋ณํ / ๋ฌธ์๋ฅผ ๊ฐ์ฒด๋ก ๋ณํ )Locale
en, ko / ๊ตญ๊ฐ์ฝ๋ US, KR1๏ธโฃ Formatter ๋ง๋ค๊ธฐ
@Slf4j
public class PriceFormatter implements Formatter<Number> {
@Override
public Number parse(String text, Locale locale) throws ParseException {
log.info("text = {}, locale={}", text, locale);
// ๋ณํ ๋ก์ง
// NumberFormat์ด ์ ๊ณตํ๋ ๊ธฐ๋ฅ
NumberFormat format = NumberFormat.getInstance(locale);
// "10,000" -> 10000L
return format.parse(text);
}
@Override
public String print(Number object, Locale locale) {
log.info("object = {}, locale = {}", object, locale);
// 10000L -> "10,000"
return NumberFormat.getInstance(locale).format(object);
}
}
2๏ธโฃ ๋ณํํ๊ธฐ
class PriceFormatterTest {
PriceFormatter formatter = new PriceFormatter();
@Test
void parse() throws ParseException {
// given, when
Number result = formatter.parse("1,000", Locale.KOREA);
// then
// parse ๊ฒฐ๊ณผ๋ Long
Assertions.assertThat(result).isEqualTo(1000L);
}
@Test
void print() {
// given, when
String result = formatter.print(1000, Locale.KOREA);
// then
Assertions.assertThat(result).isEqualTo("1,000");
}
}
ConversionService+Formatter๊ฒฐํฉ ๊ตฌํ์ฒด
์ด๋ํฐ ํจํด์ผ๋ก ํฌ๋งคํฐ๊ฐ ์ปจ๋ฒํฐ์ฒ๋ผ ๋์ํ๋๋ก ๋ง๋ค์ด์ค๋ค
FormattingConversionService + ํตํ, ์ซ์๊ด๋ จ Formatter๋ฅผ ์ถ๊ฐํ ๊ฒ
public class FormattingConversionServiceTest {
@Test
void formattingConversionService() {
// given
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
// Converter ๋ฑ๋ก
conversionService.addConverter(new StringToPersonConverter());
conversionService.addConverter(new PersonToStringConverter());
// Formatter ๋ฑ๋ก
conversionService.addFormatter(new PriceFormatter());
// when
String result = conversionService.convert(10000, String.class);
// then
Assertions.assertThat(result).isEqualTo("10,000");
}
}
@NumberFormat - ์ซ์๊ด๋ จ ์ง์ Formatter ์ฌ์ฉ
@DateTimeFormat - ๋ ์ง๊ด๋ จ ์ง์ Formatter ์ฌ์ฉ ๋ฑ๋ฑ,,,
๐ก์ธ์ธ ํ์ ์์ ๊ณต์๋ฌธ์์์ ์ฐพ์ผ์
๊ท์ฌ์ด ์ฝ์ฝ๋ณผ๋ค..