우아한테크코스/레벨4, 레벨5
[http 서버 구현미션] Custom @GetMapping Annotation 만들기
nauni
2021. 8. 30. 18:09
http 서버 구현하기 미션을 하고 있다. 진행하면서 @GetMapping, @PostMapping 등으로 메소드 실행 분기처리를 하고 싶어졌다. 그래서 작성하는 Method 단위의 커스텀 어노테이션 만들기
1. 어노테이션 정의
- @interface로 어노테이션을 정의한다.
- 메타 어노테이션을 정의한다.
- @Target: ElementType.METHOD, ElementType.FIELD 등으로 다양하게 어디에 적용할 어노테이션인지 정의할 수 있다.
- @Retention: 런타임에도 참조할 수 있게 RetentionPolicy.RUNTIME 으로 해준다. 이것 역시 컴파일을 기준으로 언제 참조 가능한지 설정해 줄 수 있다.
- CustomAnnotation 참고 블로그
- 이번 경우에서는 path라는 속성으로 uri을 정의해 줄 것이므로 해당 내용을 넣어준다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GetMapping {
String path() default "";
}
2. 어노테이션 사용할 곳에 붙이기
- 메소드를 설정한 뒤, 해당 어노테이션을 붙이고 path에 지정할 uri 를 설정해 주었다.
public class Controller {
private final HttpService service;
public Controller() {
this.service = new HttpService();
}
@GetMapping(path = "/")
public String basic(String uri) {
return ResponseEntity
.responseBody("Hello world!")
.build();
}
@GetMapping(path = "/login")
public String login(String uri) throws IOException {
return ResponseEntity
.responseResource(uri)
.build();
}
}
3. 어노테이션이 있는 메소드 실행하기
- 메소드를 실행히길 설정을 해준다.
- Controller.class.getDeclaredMethods() 로 Controller 클래스에 선언된 메소드들을 불러올 수 있다.
- 해당 method 들을 돌면서 어노테이션이 존재하면 해당 path 와 일치하는지 여부를 확인한다.
- method.getAnnotation(PostMapping.class).path() : 어노테이션에 path 로 설정했으므로 .path()로 해당 요소를 불러올 수 있다.
- 메소드가 private 이라면 method.setAccessible(true); 를 해주어야 하지만 public 이므로 생략한다.
- method.invoke(obj, ...args) 로 해당 메소드를 실행하고 반환값을 반환한다.
- 매핑된 Controller가 없다면, 그냥 해당 소스를 찾아 리턴해주게 한다.
class FrontController{
// ...
public String response() throws Exception {
final String httpMethod = requestLine.getHttpMethod();
final String uri = requestLine.getUri();
Controller controller = new Controller();
if (HttpMethod.GET.equals(httpMethod)) {
return getMapping(uri, controller);
}
if (HttpMethod.POST.equals(httpMethod)) {
return postMapping(uri, controller);
}
throw new HttpException("설정되어 있지 않은 http 메소드입니다.");
}
private String getMapping(String uri, Controller controller) throws Exception {
for (Method method : Controller.class.getDeclaredMethods()) {
if (matchGetMapping(method, uri)) {
return (String) method.invoke(controller, uri);
}
}
Set<String> declaredGetMappings = collectDeclaredGetMappings();
if (!declaredGetMappings.contains(uri)) {
return ResponseEntity
.responseResource(uri)
.build();
}
throw new HttpException("잘못된 http get 요청입니다.");
}
private boolean matchGetMapping(Method method, String uri) {
if (method.isAnnotationPresent(GetMapping.class)) {
String path = method.getAnnotation(GetMapping.class).path();
return path.equals(uri);
}
return false;
}
// ...
}