๐ ํ์ต๋ชฉ๋ก & GPT์๊ฒ ๋ฌผ์ด๋ณธ ์ง๋ฌธ ๋ชฉ๋ก
๐ 1. ์คํ๋ง ํ์ ์ปจ๋ฒํฐ
1) Converter
์ปจ๋ฒํฐ๋ org.springframework.convert.converter.Converter
public interface Conveter<S,T> {
T convert(S source);
}
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ฌ ๋ง๋ ๋ค.
2) ConversionService
์คํ๋ง์ ๊ฐ๋ฐ ์ปจ๋ฒํฐ๋ฅผ ๋ชจ์๋๊ณ ๊ทธ๊ฒ์ ๋ฌถ์ด์ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ ์ค๋ค
๊ทธ๊ฒ์ด ConversionService ์ธํฐํ์ด์ค
package org.springframework.core.convert;
import java.util.List;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
<T> T convert(Object source, Class<T> targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
๋ฉ์๋๋ก canConvert ์ convert๋ฅผ ์ ๊ณตํ๋ค.
๊ตฌํ์ฒด๋ก๋ DefaultConversionService๊ฐ ์๋ค.
(* ConversionService์ ConversionRegistry๋ฅผ ๊ตฌํํ ConfigurableConversionService๋ฅผ ๊ตฌํํ GenericConversionService๋ฅผ ์์๋ฐ์ ๊ตฌํ์ฒด๋ค..)
// ๋ฑ๋ก
DefaultConversionService๋ addConverter() ์ด์ฉํด ์ปจ๋ฒํฐ๋ฅผ ๋ฑ๋กํ ์ ์๋ค.
// ์ฌ์ฉ
DefaultConversionService๋ convert() ๊ฐ๋ฅ
‘๋ฑ๋ก’๊ณผ ‘์ฌ์ฉ’ ๋ถ๋ฆฌ
๋ฑ๋กํ๋ ์ ์ฅ์์๋ ๋ฑ๋กํ ์ปจ๋ฒํฐ๋ค์ ๋ช ํํ ์์์ผ ํ๋ค.
ํ์ง๋ง ์ฌ์ฉํ๋ ์ ์ฅ์์๋ ๋ชฐ๋ผ๋๋๋๊น ConversionService์๋ง ์์กดํ๋ค.
๐ ์ธํฐํ์ด์ค ๋ถ๋ฆฌ ์์น (ISP)
- ํด๋ผ์ด์ธํธ๊ฐ ์์ ์ด ์ด์ฉํ์ง ์๋ ๋ฉ์๋์ ์์กดํ์ง ์์์ผํ๋ค
์ธํฐํ์ด์ค๋ฅผ ๋ถ๋ฆฌํ๋ฉด ์ปจ๋ฒํฐ๋ฅผ ๋ฑ๋กํ๋ ํด๋ผ์ด์ธํธ์ ์ปจ๋ฒํฐ๋ฅผ ์ฌ์ฉํ๋ ํด๋ผ์ด์ธํธ์ ๊ด์ฌ์ฌ๋ฅผ ๋ช ํํ ๋ถ๋ฆฌํ ์ ์๋ค.
์ฆ ์ปจ๋ฒํฐ๋ฅผ ์ฌ์ฉํ๋ ํด๋ผ์ด์ธํธ๋ ConversionService ์ธํฐํ์ด์ค๋ง ์์กดํ๋ฉด ๋๋๊น ๋๋จธ์ง๋ ๊ด์ฌ์๊ณ ์ ํ์๋ ์๋ค.
์ด์ฐ๋๋ ์คํ๋ง์ ๋ด๋ถ์์ ConversionService๋ฅผ ์ด์ฉํด์ ํ์ ์ ๋ณํํ๊ณ , @RequestParam, @ModelAttribute, @PathVariable ๊ฐ์ ์ ๋ํ ์ด์ ๋ ConversionService๋ฅผ ์ด์ฉํด์ ๋ณํํ๋ค.
@RequestParam์ @RequestParam์ ์ฒ๋ฆฌํ๋ ArgumentResolver์ธ RequestParamMethodArgumentResolver์์ ConversionService๋ฅผ ์ฌ์ฉํด์ ํ์ ์ ๋ณํํ๋ค.
3) WebMvcConfigurer ์ addFormatters ๊ตฌํ
์คํ๋ง ์นMVC์ ๋ญ๊ฐ ๋ฑ๋กํ ๋๋ WebMvcConfigurer๊ตฌํํ ํด๋์ค @Configuration์ผ๋ก ๋ฑ๋กํ๋ค
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToIpPortConverter());
…
}
}
์คํ๋ง์ ๋ด๋ถ์์ ConversionService๋ฅผ ์ ๊ณตํ๊ณ ์ฐ๋ฆฌ๋ addFormatters๋ฅผ ์ฌ์ฉํด์ ์ถ๊ฐํ๊ณ ์ถ์ converter๋ฅผ ๋ฑ๋กํ๋ฉด๋๋ค.
๊ทธ๋ฌ๋ฉด ์คํ๋ง์ ๋ด๋ถ์์ ์ฌ์ฉํ๋ ConversionService์ converter๋ฅผ ๋ฑ๋กํด์ค๋ค.
1) ๋ฌธ์์ด → ํ์ OK
2) View์์ ํ์ → ๋ฌธ์์ด์?
ํ์๋ฆฌํ์์ ${{...}} ์ด๋ ๊ฒ ํ๋ฉด ConversionService(ํ์ → ๋ฌธ์์ด) ์ ์ฉ๊ฐ๋ฅ
* th:field๋ ${...}๋ง ํด๋ ConversionService ์ ์ฉ๋๋ค.
* th:value๋ ${{...}}ํด์ผ ํจ.
4) Converter vs Formatter
Converter๋ ๋ฒ์ฉ : ๊ฐ์ฒด -> ๊ฐ์ฒด
Formatter๋ ๋ฌธ์์ ํนํ : ๋ฌธ์ -> ๊ฐ์ฒด, ๊ฐ์ฒด -> ๋ฌธ์ + Locale(ํ์งํ) (Converter์ ํน๋ณ ๋ฒ์ )
Printer ์ธํฐํ์ด์ค์ Parser์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ๊ฒ์ด Formatter ์ธํฐํ์ด์ค
import org.springframework.format.Formatter;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
@Component
public class LocalDateFormatter implements Formatter<LocalDate> {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public LocalDate parse(String text, Locale locale) {
try {
return LocalDate.parse(text, formatter);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid date format. Please use yyyy-MM-dd");
}
}
@Override
public String print(LocalDate object, Locale locale) {
return object.format(formatter);
}
}
1000 <-> 1,000 ์๋ฐ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ NumberFormat์ ์ด์ฉํด์ ๋ง๋ค์ locale์ ๋ณด์๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋ง๋ค์ด์ค.
package hello.typeconverter.formatter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.Formatter;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
@Slf4j
public class MyNumberFormatter implements Formatter<Number> {
@Override
public Number parse(String text, Locale locale) throws ParseException {
log.info("text={}, locale={}", text, locale);
NumberFormat format = NumberFormat.getInstance(locale);
return format.parse(text);
}
@Override
public String print(Number object, Locale locale) {
log.info("object={}, locale={}", object, locale);
return NumberFormat.getInstance(locale).format(object);
}
}
5) Formatter๋ฅผ ์ง์ํ๋ ConversionService
ConversionService๋ Converter๋ง ๋ฑ๋กํ ์ ์๋ค.
๊ทธ๋ฌ๋ ์ ์๊ฐํด๋ณด๋ฉด Formatter๋ ๋ฌธ์ ↔ ๊ฐ์ฒด ๋ฅผ ํด์ฃผ๋ ํน์ํ Converter์ผ ๋ฟ์ด๋ค.
Formatter๋ฅผ ์ง์ํ๋ ConversionService๋ฅผ ์ฌ์ฉํ๋ฉด ConversionService์ Formatter๋ฅผ ๋ฑ๋กํ ์ ์๋ค.
๋ด๋ถ์์ Adapter Pattern์ ์ฌ์ฉํด์ Formatter๊ฐ Converter์ฒ๋ผ ๋์ํ๋๋ก ์ง์ํ๋ค.
FormattingConversionService ๋ Formatter๋ฅผ ์ง์ํ๋ ConversionService
DefaultFormattingConversionService ๋ FormattingConversionService ์ ๊ธฐ๋ณธ์ ์ธ ํตํ, ์ซ์
๊ด๋ จ ๋ช๊ฐ์ง ๊ธฐ๋ณธ Formatter๋ฅผ ์ถ๊ฐํด์ ์ ๊ณตํ๋ค.
public class FormattingConversionServiceTest {
@Test
void formattingConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
//์ปจ๋ฒํฐ ๋ฑ๋ก
conversionService.addConverter(new StringToIpPortConverter());
conversionService.addConverter(new IpPortToStringConverter());
//ํฌ๋งทํฐ ๋ฑ๋ก
conversionService.addFormatter(new MyNumberFormatter());
//์ปจ๋ฒํฐ ์ฌ์ฉ
IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080));
//ํฌ๋งทํฐ ์ฌ์ฉ
assertThat(conversionService.convert(1000,String.class)).isEqualTo("1,000");
assertThat(conversionService.convert("1,000", Long.class)).isEqualTo(1000L);
}
}
๐ DefaultFormattingConversionService ์์ ๊ด๊ณ
FormattingConversionService ๋ ConversionService ๊ด๋ จ ๊ธฐ๋ฅ์ ์์๋ฐ๊ธฐ ๋๋ฌธ์ Converter๋ Formatter๋ ๋ชจ๋ ๋ฑ๋กํ ์ ์๋ค. ์ฌ์ฉํ ๋๋ ConversionService ๊ฐ ์ ๊ณตํ๋ convert๋ฅผ ์ฌ์ฉํ๋ฉด ๋จ.
์คํ๋ง ๋ถํธ๋ DefaultFormattingConversionService ๋ฅผ ์์ ๋ฐ์ WebConversionService๋ฅผ ๋ด๋ถ์์ ์ฌ์ฉํจ.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
//์ฃผ์์ฒ๋ฆฌ ์ฐ์ ์์
// * MyNumberFormatter ๋ ์ซ์ ๋ฌธ์, ๋ฌธ์ ์ซ์๋ก ๋ณ๊ฒฝํ๊ธฐ ๋๋ฌธ์ ๋์ ๊ธฐ๋ฅ์ด ๊ฒน์นจ.
// * ์ฐ์ ์์๋ ์ปจ๋ฒํฐ๊ฐ ์ฐ์ ํ๋ฏ๋ก ํฌ๋งทํฐ๊ฐ ์ ์ฉ๋์ง ์๊ณ , ์ปจ๋ฒํฐ๊ฐ ์ ์ฉ๋จ.
//registry.addConverter(new StringToIntegerConverter());
//registry.addConverter(new IntegerToStringConverter());
registry.addConverter(new StringToIpPortConverter());
registry.addConverter(new IpPortToStringConverter());
//์ถ๊ฐ
registry.addFormatter(new MyNumberFormatter());
}
}
6) ์คํ๋ง์ด ์ ๊ณตํ๋ ๊ธฐ๋ณธ Formatter
์คํ๋ง์ ์๋ฐ์์ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋ ํ์ ๋ค์ ๋ํด ์ ๋ง์ Formatter๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณต.
IDE์์ Formatter ์ธํฐํ์ด์ค์ ๊ตฌํ ํด๋์ค๋ฅผ ์ฐพ์๋ณด๋ฉด ์ ๋ง์ ๋ ์ง๋ ์๊ฐ ๊ด๋ จ ํฌ๋งทํฐ๊ฐ ์ ๊ณต๋๋ ๊ฒ์ ํ์ธ ๊ฐ๋ฅ
๊ทธ๋ฐ๋ฐ Formatter๋ ๊ธฐ๋ณธ ํ์์ด ์ง์ ๋์ด ์๊ธฐ ๋๋ฌธ์, ๊ฐ์ฒด์ ๊ฐ ํ๋๋ง๋ค ๋ค๋ฅธ ํ์์ผ๋ก ํฌ๋งท์ ์ง์ ํ๊ธฐ๋ ์ด๋ ต๋ค.
์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ ๋
ธํ
์ด์
๊ธฐ๋ฐ์ผ๋ก ์ํ๋ ํ์์ ์ง์ ํด์ ์ฌ์ฉํ ์ ์๋ ๋งค์ฐ ์ ์ฉํ Formatter 2๊ฐ์ง๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณต.
@NumberFormat : ์ซ์ ๊ด๋ จ ํ์ ์ง์ Formatter ์ฌ์ฉ
@DateTimeFormat : ๋ ์ง ๊ด๋ จ ํ์ ์ง์ Formatter ์ฌ์ฉ
@Controller
public class FormatterController {
@GetMapping("/formatter/edit")
public String formatterForm(Model model) {
Form form = new Form();
form.setNumber(10000);
form.setLocalDateTime(LocalDateTime.now());
model.addAttribute("form", form);
return "formatter-form";
}
@PostMapping("/formatter/edit")
public String formatterEdit(@ModelAttribute Form form) {
return "formatter-view";
}
@Data
static class Form {
@NumberFormat(pattern = "###,###")
private Integer number;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form th:object="${form}" th:method="post">
number <input type="text" th:field="*{number}"><br/>
localDateTime <input type="text" th:field="*{localDateTime}"><br/>
<input type="submit"/>
</form>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li>${form.number}: <span th:text="${form.number}" ></span></li>
<li>${{form.number}}: <span th:text="${{form.number}}" ></span></li>
<li>${form.localDateTime}: <span th:text="${form.localDateTime}" ></span></li>
<li>${{form.localDateTime}}: <span th:text="${{form.localDateTime}}" ></span></li>
</ul>
</body>
</html>
7) ConversionService์ HttpMessageConverter
๋ฉ์์ง ์ปจ๋ฒํฐ(HttpMessageConverter)์๋ ConversionService๊ฐ ์ ์ฉ๋์ง ์๋๋ค.
๊ฐ์ฒด ↔ JSON ์ผ๋ก ๋ณํํ ๋ HttpMessageConverter๋ฅผ ์ฌ์ฉํ๋ฉด์ ์ด ๋ถ๋ถ์ ๋ง์ด ์ฐฉ๊ฐํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. HttpMessageConverter ์ ์ญํ ์ HTTP ๋ฉ์์ง body์ ๋ด์ฉ์ ๊ฐ์ฒด๋ก ๋ณํํ๊ฑฐ๋ ๊ฐ์ฒด๋ฅผ HTTP ๋ฉ์์ง ๋ฐ๋์ ์
๋ ฅํ๋ ๊ฒ์ด๋ค.
(HttpMessage body ↔ ๊ฐ์ฒด)
JSON์ ๊ฐ์ฒด๋ก ๋ณํํ๋ ๋ฉ์์ง ์ปจ๋ฒํฐ๋ ๋ด๋ถ์์ Jackson ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค.
๊ฐ์ฒด๋ฅผ JSON์ผ๋ก ๋ณํํ๋ค๋ฉด ๊ทธ ๊ฒฐ๊ณผ๋ ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ฌ๋ฆฐ ๊ฒ.
๋ฐ๋ผ์ JSON ๊ฒฐ๊ณผ๋ก ๋ง๋ค์ด์ง๋ ์ซ์๋ ๋ ์ง ํฌ๋งท์ ๋ณ๊ฒฝํ๊ณ ์ถ์ผ๋ฉด ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ๊ณตํ๋ ์ค์ ์ ํตํด์ ํฌ๋งท์ ์ง์ ํด์ผ ํ๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ์ด๊ฒ์ ์ปจ๋ฒ์ ์๋น์ค์ ์ ํ ๊ด๊ณ๊ฐ ์๋ค.
์ปจ๋ฒ์ ์๋น์ค๋ @RequestParam , @ModelAttribute , @PathVariable , ๋ทฐ ํ
ํ๋ฆฟ ๋ฑ์์ ์ฌ์ฉํ ์ ์๋ค.
๐ 2. ํ์ผ ์ ๋ก๋
1) HTML Form ์ ์ก๋ฐฉ์ 2๊ฐ์ง
1. application/x-www-form-urlencoded → form ํ๊ทธ์ ๋ณ๋ enctype ์ต์ ์์ด ์ ์ก
- ์น ๋ธ๋ผ์ฐ์ ๋ ์์ฒญ HTTP ๋ฉ์์ง์ ํค๋์ ๋ค์๋ด์ฉ์ ์ถ๊ฐํ๋ค. Content-Type: application/x-www-form-urlencoded
- ํผ์ ์ ๋ ฅํ ์ ์กํ ํญ๋ชฉ์ HTTP Body์ ๋ฌธ์๋ก username=kim&age=20 ์ ๊ฐ์ด & ๋ก ๊ตฌ๋ถํด์ ์ ์ก
2. multipart/form-data → form ํ๊ทธ์ ๋ณ๋ enctype ์ต์ ์ถ๊ฐ, multipart/form-data ๋ผ๋ ์ ์ก ๋ฐฉ์ ์ฌ์ฉ
- multipart/form-data ๋ฐฉ์์ ๋ค๋ฅธ ์ข ๋ฅ์ ์ฌ๋ฌ ํ์ผ๊ณผ ํผ์ ๋ด์ฉ ํจ๊ป ์ ์ก ๊ฐ๋ฅ.
POST /save HTTP/1.1
Host: localhost:8080
Content-Type: multipart/form-data; boundary=------XXX
Content-Length: 34521
------XXX
Content-Disposition: form-data; name="username"
kim
------XXX
Content-Disposition: form-data; name="age"
20
------XXX
Content-Disposition: form-data; name="file1"; filename="img32.png"
Content-Type: image/png
12dfkli1k1k23kj3kkj4
------XXX--
2) ์๋ฒ์์ multipart/form-data ๋ฉ์ธ์ง ์ฌ์ฉ
๐ Reference
์ ํ์ต ์์ฝ์ ๊น์ํ๋์ '์คํ๋ง MVC ๊ฐ์ 2ํธ' ์ ๊ธฐ์ด๋ก ์์ฑ๋์์ต๋๋ค.
'spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
HandlerMapping & RequestMapping (0) | 2024.12.02 |
---|---|
SpringMVC ArgumentResolver (0) | 2024.12.02 |
Entity ํด๋์ค์ DTO๋ฅผ ๋ถ๋ฆฌํ๋ ์ด์ (0) | 2024.11.30 |
@RequestBody @Valid Object obj, BindingResult br ํ์ ์๋ฌ๊ฐ br์ ๋ด๊ธฐ์ง ์๋ ์ด์ (0) | 2024.11.30 |
๋๋ฉ์ธ ๋ชจ๋ธ ํจํด vs ํธ๋์ญ์ ์คํฌ๋ฆฝํธ ํจํด (0) | 2024.11.25 |
๋๊ธ