Validation API
@Valid annotation ์ฌ์ฉ
- **pom.xml์ dependency ์ถ๊ฐ**
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.7.Final</version>
</dependency>
- dto์ ์ ์ฝ์กฐ๊ฑด ์ถ๊ฐ
@Data
@AllArgsConstructor
public class User {
private Integer id;
@Size(min=2, message = "Name์ 2๊ธ์ ์ด์ ์
๋ ฅํด์ฃผ์ธ์.")
private String name;
private String password;
@Past // ๊ณผ๊ฑฐ ๋ ์ง๋ง ๊ฐ๋ฅํ ์ ์ฝ ์กฐ๊ฑด
private Date joinDate;
}
- controller์ ์ถ๊ฐ
- JDK์ ํฌํจ๋ API์ hibernate library์ ํฌํจ๋ validation ๊ธฐ๋ฅ
- @Valid : ์ฌ์ฉ์ ์ถ๊ฐํ ๋ validation ๊ฒ์ฆ
- @RequestBody : ํ์ฌ variable์ด requestBody ํ์์ผ๋ก ๋ค์ด์ด์ ์๋ฆผ
- exception handler ์ถ๊ฐ
@ResponseStatus(HttpStatus.NOT_FOUND) // ์๋ต ์ํ ์ฝ๋ ๊ฐ ์ง์
// CustomizedResponseEntityExceptionHandler.class์์ UserNotFoundException์ด ๋ฐ์ํ์ ๋, Exception Response๋ฅผ ๋ฐํ(Http status code๋ ๋ฐํ)
// => ResponseStatus ์๋ต ๊ฐ๋ฅ
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message); // ex.getMessage()
}
}
๋ค๊ตญ์ด ์ฒ๋ฆฌ๋ฅผ ์ํ Internationalization ๊ตฌํ
- yml ํ์ผ์ spring.messages.basename={์ฌ์ฉํ properties ํ์ผ์ ์ด๋ฆ}
#๋ค๊ตญ์ด ํ์ผ ์ค์
spring:
messages:
basename: messages # ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ message ํ์ผ์ ์ด๋ฆ
- resources ๋ฐ์ {yml ํ์ผ์์ ์ค์ ํ ์ด๋ฆ}.properties๋ฅผ ๋ง๋ค์ด์ค๋ค
-> ๋ค๊ตญ์ด ๋ฒ์ ์ผ๋ก ์ฌ๋ฌ ๊ฐ ์์ฑ
# properties : ์๋ฐ์ ๋ฆฌ์์ค ๋ฒ๋ค
# ํน์ key๊ฐ๊ณผ value๋ฅผ ์ ์ฅํด ๋๊ณ ์ฌ์ฉํ ์ ์๋ ์ฉ๋๋ก ์ฌ์ฉ๋จ
greeting.message=์๋
ํ์ธ์
// messages_fr.properties
greeting.message=Bonjour
- @SpringBootApplication๋ฐ์ @Bean์ ์์ฑํด์ localeResolver ์์ฑ
@Bean
// @SpringBootApplication์ @Bean์ ๋ฑ๋กํ๊ฒ ๋๋ฉด Spring Boot๊ฐ ์ด๊ธฐํ๋ ๋,
// ํด๋น ์ ๋ณด์ ํด๋น๋๋ ๊ฐ์ด ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ผ๊ฐ์ ๋ค๋ฅธ ํด๋์ค๊ฐ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค.
public LocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.KOREA); // default : ํ๊ตญ์ด ์ฒ๋ฆฌ
return localeResolver;
}
- Controller์์ ์ค์ -> Header๋ก language ๊ฐ์ ๋ฐ์์ ํด๋น ์ธ์ด์ ๋ง๋ ๋ฉ์ธ์ง๋ฅผ ์ถ๋ ฅ
@Autowired // annotation์ ํตํ ์์กด์ฑ ์ฃผ์
: spring framework์ ๋ฑ๋ก๋์ด ์๋ bean ์ค ๊ฐ์ ํ์
์ ๊ฐ์ง bean์ ์๋์ผ๋ก ์ฃผ์
private MessageSource messageSource;
@GetMapping(path="/hello-world-internationalized")
public String helloWorldInternationalized(
@RequestHeader(name="Accept-Language", required = false) Locale locale) {
// "Accept-Language" ๊ฐ์ด ์ค์ ๋์ง ์์์ ๋๋ ์๋์ผ๋ก default๊ฐ์ธ ํ๊ตญ์ด๋ก ์ค์ (๊ธฐ๋ณธ locale ๊ฐ)
// "Accept-Language" ๊ฐ ์ค์ ๋์ด ์์ผ๋ฉด locale์ ์ ์ฅ
// ์ฒซ๋ฒ์งธ ์ธ์: ์ค์ ํ์ผ์์์ key ๊ฐ
// ๋๋ฒ์งธ ์ธ์: ๋ง์ฝ parameter๋ฅผ ๊ฐ์ง ๊ฐ๋ณ๋ณ์๋ผ๋ฉด parameter ์ง์
// ์ธ๋ฒ์งธ ์ธ์: RequestHeader๋ฅผ ํตํด ์ ๋ฌ๋ฐ์ locale
return messageSource.getMessage("greeting.message", null, locale);
}
@Bean๊ณผ @Autowired
- @Bean @SpringBootApplication์ @Bean์ ๋ฑ๋กํ๊ฒ ๋๋ฉด Spring Boot๊ฐ ์ด๊ธฐํ๋ ๋, ํด๋น ์ ๋ณด์ ํด๋น๋๋ ๊ฐ์ด ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ผ๊ฐ์ ๋ค๋ฅธ ํด๋์ค๊ฐ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ค.
- @Autowired
annotation์ ์ด์ฉํ ์์กด์ฑ ์ฃผ์ : spring framework์ ๋ฑ๋ก๋์ด ์๋ bean ์ค ๊ฐ์ ํ์ ์ ๊ฐ์ง bean์ ์๋์ผ๋ก ์ฃผ์
Response ๋ฐ์ดํฐ ํ์ ๋ฐํ - XML format
- Postman header์์ key: Accept, value: application/xml๋ก ๋ณํ ์ xmlํ์ผ ํธ์ถ ๊ฐ๋ฅ
2. pom.xml์์ dependency ์ถ๊ฐ
<!--xml ํ์ผ ๋ณํ ์ค์ -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.10.2</version>
</dependency>
⇒ ์ค์ ํ์ง ์์ ๊ฒฝ์ฐ 406 ์๋ฌ๊ฐ ๋๋ค
HTTP(HyperText Transfer Protocol) 406 Not Acceptable ํด๋ผ์ด์ธํธ ์ค๋ฅ ์๋ต ์ฝ๋๋ ์๋ฒ๊ฐ ์์ฒญ์ ์ฌ์ ์ฝํ ์ธ ํ์ ํค๋์ ์ ์๋ ํ์ฉ ๊ฐ๋ฅํ ๊ฐ ๋ชฉ๋ก๊ณผ ์ผ์นํ๋ ์๋ต์ ์์ฑํ ์ ์์ผ๋ฉฐ ์๋ฒ๊ฐ ๊ธฐ๋ณธ ํํ์ ์ ๊ณตํ ์์ฌ๊ฐ ์์์ ๋ํ๋ ๋๋ค.
์ฌ์ ์ปจํ ์ธ ํ์ ํค๋์๋ ๋ค์์ด ํฌํจ๋ฉ๋๋ค.
- [Accept](<https://runebook.dev/ko/docs/http/headers/accept>)
- [Accept-Encoding](<https://runebook.dev/ko/docs/http/headers/accept-encoding>)
- [Accept-Language](<https://runebook.dev/ko/docs/http/headers/accept-language>)
์ค์ ๋ก ์ด ์ค๋ฅ๋ ๊ฑฐ์ ์ฌ์ฉ๋์ง ์์ต๋๋ค. ์ต์ข ์ฌ์ฉ์์๊ฒ๋ ์ํธํ๋์ด ์์ ํ๊ธฐ ์ด๋ ค์ด ์ด ์ค๋ฅ ์ฝ๋๋ฅผ ์ฌ์ฉํ์ฌ ์๋ตํ๋ ๋์ ์๋ฒ๋ ๊ด๋ จ ํค๋๋ฅผ ๋ฌด์ํ๊ณ ์ฌ์ฉ์์๊ฒ ์ค์ ํ์ด์ง๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์์ ํ ๋ง์กฑํ์ง ์๋๋ผ๋ ์ค๋ฅ ์ฝ๋๋ณด๋ค ์ ํธํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
์๋ฒ๊ฐ ์ด๋ฌํ ์ค๋ฅ ์ํ๋ฅผ ๋ฐํํ๋ฉด ๋ฉ์์ง ๋ณธ๋ฌธ์ ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฆฌ์์ค ํํ ๋ชฉ๋ก์ด ํฌํจ๋์ด ์์ด์ผ ์ฌ์ฉ์๊ฐ ์ ํํ ์ ์์ต๋๋ค.
Response ๋ฐ์ดํฐ ์ ์ด๋ฅผ ์ํ Filtering
ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌ๋๋ ์ ๋ณด์ ๊ฐ์ ์ ์ด
- ๊ฐ์ ์ํธํํ๊ฑฐ๋ ์นํํ์ฌ ์ ๋ฌ
- null๋ก ๋ฐํ
⇒ @JsonIgnore , @JsonIgnoreProperties ์ฌ์ฉ
@JsonIgnore // ๋ฐํ ์ Json๊ฐ์ ํฌํจ๋์ง ์์ ->๋ฐํ๊ฐ ์ ์ด
privateString password;
@JsonIgnore
privateString ssn;//์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ
//@JsonIgnoreProperties(value={"password", "ssn"}) // ํด๋์ค ๋ธ๋ก ๋จ์๋ก ํํฐ๋ง
๊ฐ๋ณ ์ฌ์ฉ์ ์กฐํ
// domain
// @JsonIgnoreProperties(value={"password", "ssn"}) // ํด๋์ค ๋จ์๋ก ํํฐ๋ง
@JsonFilter("UserInfo") // ๋ถ์ฌ๋ ํํฐ๊ฐ์ controller๋ service ํด๋์ค์์ ์ฌ์ฉ๋๋ค
@RequestMapping("/admin") // ๋ชจ๋ api๊ฐ ๊ณตํต์ ์ผ๋ก ๊ฐ์ง๊ณ ์๋ ์ด๋ฆ์ ํด๋์ค ๋ธ๋ก์ ์ ์ธ
@GetMapping("/users/{id}")
// setFilters๋ setSerialView๋ฅผ ์ฌ์ฉ ์ ๊ผญ MappingJacksonValue ํ์
์ผ๋ก ๊ฐ์ธ์ ๋ฐํ
public MappingJacksonValue retrieveUser(@PathVariable int id) { // ๊ด๋ฆฌ์ : ์ ์ ์ ์ ๋ณด๋ฅผ ๋ชจ๋ ๊ฐ์ ธ์ฌ ์ ์๋๋ก!
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] is not found", id));
}
// bean์ property๋ฅผ ์ ์ด
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn"); // ํฌํจ์ํค๊ณ ์ถ์ ํํฐ๊ฐ ์ ์ธ
// User์ @JsonFilter("UserInfo") ์ง์
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(user); // ์ ์ ๋ฐ์ดํฐ ์ ๋ฌ
mapping.setFilters(filters); // ํํฐ ์ ์ฉ
return mapping;
}
์ ๋ฆฌ
ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌ๋๋ ์ ๋ณด์ ๊ฐ์ ์ ์ดํ๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ (1) ๊ฐ์ ์ํธํํ๊ฑฐ๋ ์นํํ์ฌ ์ ๋ฌ (2) null์ ๋ฃ์ด ์ ๋ฌ
-> ์ฐ๋ฆฌ๋ @JsonIgnore, @JsonIgnoreProperties๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ์ ์ดํ๋ค
- @JsonIgnore : ๋จ์ ํ๋ ๊ฐ์ ์ ์ดํ ๋ ์ฌ์ฉ. ๋ฐํ ์ json ๊ฐ์ ํฌํจ๋์ง ์๋๋ค
- @JsonIgnoreProperties(value={"ํ๋๊ฐ"}) : ํด๋์ค๋ฅผ ๋ธ๋ก ๋จ์๋ก ํํฐ๋ง -> ์กฐ๊ธ ๋ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ :@JsonFilter ์ฌ์ฉ
- domain์ @JsonFilter("ํํฐ๋ช ") ์ฌ์ฉ : ๋ถ์ฌ๋ ํํฐ๊ฐ์ controller๋ service ํด๋์ค์์ ์ฌ์ฉ
- controller์์ return๊ฐ์ MappingJacksonValue๋ก ์ ์ธ
- SimpleBeanPropertyFilter๋ฅผ ์ด์ฉํ์ฌ ํฌํจ์ํค๊ณ ์ถ์ ํํฐ๊ฐ ์ ์ธ
- FilterProvider์ domain์์ ์ง์ ํ ํํฐ ์ง์
- MappingJacksonValue์ FilterProvider๋ฅผ ์ ์ฉํ์ฌ ๋ฆฌํด
REST API ๋ฒ์ ๊ด๋ฆฌ
URL์ ์ด์ฉํ ๋ฒ์ ๊ด๋ฆฌ
url์ ๋ฒ์ ์ ํ์ํจ์ผ๋ก์จ ๋ฒ์ ์ ํ์ํ ์ ์๋ค. ๋ฒ์ ๊ด๋ฆฌ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ค ์ฝ๋ ๋ฟ๋ง ์๋๋ผ ์ ๊ณตํ๋ ค๋ ์๋น์ค์ ๋ฆฌ์์ค์ ๋ํด์๋ ๊ด๋ฆฌํด์ฃผ๋ ๊ฒ์ด ์ข๋ค.
// ๋ฐํ๋ฐ์ User๋ฅผ UserV2๋ก ๋ณํ
UserV2 userV2 = new UserV2();
// ๋ ์ธ์คํด์ค ๊ฐ์ ๊ณตํต๋ ํ๋๊ฐ ์์ ๊ฒฝ์ฐ ํด๋น ๊ฐ์ copyํจ
BeanUtils.copyProperties(user, userV2); // id, name, joinDate, password, ssn
userV2.setGrade("VIP");
Request Parameter๋ฅผ ์ด์ฉํ ๋ฒ์ ๊ด๋ฆฌ
// ๋ฐ์ดํฐ ๋ค์ ๋ฒ์ ์ ๋ํ ์ ๋ณด๊ฐ ์ถ๊ฐ์ ์ผ๋ก ์ ๋ฌ๋์ด์ผ ํ๊ธฐ ๋๋ฌธ์ URI ๋ค์ "/"๊ฐ ๋ถ๋๋ค
@GetMapping(value = "/users/{id}/", params = "version=1")
http://localhost:8088/admin/users/1/?version=1 ๋ฐฉ์์ผ๋ก ํธ์ถ
Header๋ฅผ ์ด์ฉํ ๋ฒ์ ๊ด๋ฆฌ
@GetMapping(value = "/users/{id}", headers = "X-API-VERSION=2")
MIME ํ์ ์ ์ด์ฉํ ๋ฒ์ ๊ด๋ฆฌ
MIME ํ์ → Multi-Purpose Internet Mail Extension(ํ์ผ ํ์ ์ง์ )
์ด๋ฉ์ผ๊ณผ ํจ๊ป ์ ์ก๋๋ ๋ฉ์ผ๋ฅผ ํ ์คํธ ๋ฌธ์๋ก ๋ณํํด์ ์ด๋ฉ์ผ ์๋ฒ์ ์ ๋ฌํ๊ธฐ ์ํ ๋ฐฉ๋ฒ
// ์ ๊ณตํ๊ณ ์ ํ๋ MIME ํ์
์ง์
// ๊ธฐ์กด์ ์๋ ์๋ก์ด ๊ฐ -> application
// ๋ฒ์ ์ด๋ฆ : appv1
// ์ ๋ฌ ์ํค์กฐ์ ํ๋ ํ์
: json
@GetMapping(value = "/users/{id}", produces = "application/vnd.company.appv1+json")
๋ฒ์ ๊ด๋ฆฌ
๋จ์ํ๊ฒ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋ ํญ๋ชฉ์ ์ ์ดํ๋ ์ฉ๋๊ฐ ์๋๋ผ REST API์ ์ค๊ณ๊ฐ ๋ณ๊ฒฝ๋๊ฑฐ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์กฐ๊ฐ ๋ฐ๋ ๋๋ ๋ฒ์ ์ ๋ณ๊ฒฝํด์ฃผ์ด์ผ ํจ.
์ฌ์ฉ์์๊ฒ๋ ์ด๋ ํ API ๋ฒ์ ์ ์ฌ์ฉํด์ผ ํ๋์ง์ ๋ํ ์ ์ ํ ๊ฐ์ด๋ ์ ๊ณต ํ์
- ์ผ๋ฐ ๋ธ๋ผ์ฐ์ ์์ ์คํ ๊ฐ๋ฅ
- URI
- Request Parameter
- ์ผ๋ฐ ๋ธ๋ผ์ฐ์ ์์ ์คํ ๋ถ๊ฐ
- Media type versioning(content negotiation, accept header) → ํค๋
- (custom) headers versioning → MIME ํ์
- ์ ์ ์ฌํญ
- URI Pollution : ๋๋ฌด ์ง์ ๋ถํ๊ฑฐ๋ ๊ณผ๋ํ๊ฒ ์ ๋ณด๋ฅผ ํ๊ธฐํ๋ ๊ฒ์ ์ง์
- Misuse of HTTP headers : ์๋ชป๋ ํค๋๊ฐ ์ฃผ์
- Caching : ์ ์ ํ ์บ์ ์ญ์ ํด์ ์ ๋๋ก ๋ ๊ฐ์ ์ฌ์ฉํ ์ ์๋๋ก ํจ
- Can we execute the request on browser?
- API Documentation
'๐ฟ Spring > โ RESTful Web Service' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
5. Java Persistence API ์ฌ์ฉ (0) | 2023.06.20 |
---|---|
4. Spring Boot API ์ฌ์ฉ (0) | 2023.06.20 |
2. User Service API ๊ตฌํ (0) | 2023.06.20 |
1. Spring Boot๋ก ๊ฐ๋ฐํ๋ RESTful Service (0) | 2023.06.20 |
0. Web Service & Web Application (0) | 2023.06.20 |
๋๊ธ