반응형
Controller 기본
get
@GetMapping("/api/get")
public Map<String, Object> get(
@RequestHeader(required = false) Map<String, Object> headers,
@RequestParam(required = false) Map<String, Object> params
) {
System.out.println(headers);
System.out.println(params);
return params;
}
post
@PostMapping("/api/post")
public Map<String, Object> post(
@RequestHeader(required = false) Map<String, Object> headers,
@RequestParam(required = false) Map<String, Object> params,
@RequestBody(required = false) String body
) {
System.out.println(headers);
System.out.println(params);
System.out.println(body);
return params;
}
multipart - 기본
- application.yml
spring: servlet: multipart: file-size-threshold: 1MB location: C:/temp max-file-size: 100MB max-request-size: 100MB
- Controller
@PostMapping("/api/multipart") public Map<String, Object> multipart( @RequestHeader(required = false) Map<String, Object> headers, @RequestParam(required = false) Map<String, Object> params, @RequestPart(required = false) List<MultipartFile> files ) throws IOException { System.out.println(headers); System.out.println(params); System.out.println(files); for (MultipartFile file : files) { File desc = new File("C:/download/" + file.getOriginalFilename()); file.transferTo(desc); } return params; }
multipart - DTO
test.http
### test
POST http://localhost:8080/api/multipart
Content-Type: multipart/form-data; boundary=WebAppBoundary
--WebAppBoundary
Content-Disposition: form-data; name="files[0].file"; filename="HELP.md"
Content-Type: text/plain
< ./HELP.md
--WebAppBoundary
Content-Disposition: form-data; name="files[0].url";
/help.md
--WebAppBoundary
Content-Disposition: form-data; name="files[1].file"; filename="build.gradle.kts"
Content-Type: text/plain
< ./build.gradle.kts
--WebAppBoundary
Content-Disposition: form-data; name="files[1].url";
/build.gradle.kts
--WebAppBoundary
Content-Disposition: form-data; name="message";
Hello World
--WebAppBoundary
java controller
@PostMapping("/api/multipart")
public void multipart(Request request) {
log.info("message: {}", request.message);
request.files.forEach(it -> log.info("url: {}, filename: {}", it.url, it.file.getOriginalFilename()));
}
@Data
public static class Request {
private String message;
private List<Item> files;
}
@Data
public static class Item {
private String url;
private MultipartFile file;
}
kotlin controller
@PostMapping("/api/multipart")
fun multipart(request: Request) {
log.info("message: {}", request.message)
request.files.forEach { log.info("url: {}, filename: {}", it.url, it.file?.originalFilename) }
}
data class Request( // 주의) 기본 생성자를 가질 수 있도록 멤버 필드들 모두 디폴트값 세팅해주어야함. 그렇지 않으면 오류 발생.
var message: String = "",
var files: List<Item> = mutableListOf(), // 주의) mutableListOf()가 아닌 listOf()일 경우 오류 발생.
)
data class Item(
var url: String = "", // 주의) var가 아닌 val을 사용하면 요청값이 매핑되지 않음.
var file: MultipartFile? = null,
)
RequestMappingHandlerMapping를 활용한 Api Versioning
설명
- RequestMappingHandlerMapping란 요청 URL과 매칭되는 컨트롤러를 검색하고, 어떤 컨트롤러가 처리할지를 결정하는 클래스를 말한다.
- 아래에는 ApiVersion 어노테이션을 컨트롤러에 설정하여 어노테이션에 지정된 버전값에 적절하게 매칭되는 컨트롤러로 서비스할 수 있도록 RequestMappingHandlerMapping를 커스터마이징하는 코드를 제시한다.
ApiVersion
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ApiVersion {
int[] value();
}
ApiVersionRequestCondition
@RequiredArgsConstructor
public class ApiVersionRequestCondition implements RequestCondition<ApiVersionRequestCondition> {
private final int[] versions;
@Override
public ApiVersionRequestCondition getMatchingCondition(HttpServletRequest request) {
Matcher matcher = Pattern.compile("/api/v(\\d+)/(.*)").matcher(request.getRequestURI());
if (matcher.find()) {
int version = Integer.parseInt(matcher.group(1));
if (versions.length == 1) {
if (versions[0] <= version) {
return this;
}
} else {
if (versions[0] <= version && versions[1] >= version) {
return this;
}
}
}
return null;
}
@Override
public ApiVersionRequestCondition combine(ApiVersionRequestCondition other) {
throw new IllegalStateException("Not Supported");
}
@Override
public int compareTo(ApiVersionRequestCondition other, HttpServletRequest request) {
throw new IllegalStateException("Not Supported");
}
}
ApiVersionHandlerMapping
public class ApiVersionHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
return createCondition(AnnotationUtils.findAnnotation(handlerType, ApiVersion.class));
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
return createCondition(AnnotationUtils.findAnnotation(method, ApiVersion.class));
}
private RequestCondition<?> createCondition(ApiVersion apiVersion) {
return Optional.ofNullable(apiVersion)
.map(item -> new ApiVersionRequestCondition(item.value()))
.orElse(null);
}
}
WebMvcConfig
@Configuration
public class WebMvcConfig {
@Bean
public WebMvcRegistrations webMvcRegistrations() {
return new WebMvcRegistrations() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new ApiVersionHandlerMapping();
}
};
}
}
UserController
@RestController
public class UserController {
@GetMapping("/api/v*/users")
@ApiVersion({1, 2}) // v1 ~ v2까지는 여기
public String getUserV1() {
return "johnV1";
}
@GetMapping("/api/v*/users")
@ApiVersion(3) // v3 이상부터는 여기
public String getUserV3() {
return "johnV3";
}
}
참고
- https://seungwoo0429.tistory.com/37
- https://programmer.group/customized-request-mapping-handler-mapping-for-spring-mvc.html
반응형
'Development > Spring' 카테고리의 다른 글
[Spring] BeanFactory & ApplicationContext (0) | 2020.12.27 |
---|---|
[Spring] Paging (0) | 2020.12.27 |
[Spring] FileDownload (0) | 2020.12.27 |
[Spring] Spock Test (0) | 2020.12.27 |
[Spring] Spring Boot 프로젝트 세팅 (0) | 2020.12.27 |