본문 바로가기
개발/Armeria

[Java] Armeria Service에서 사용되는 유용한 Annotation

by Mingvel 2022. 7. 3.

이 글은 Armeria 1.16.0 Version을 기준으로 작성되었습니다.

 

이전 글 보러 가기

[Java] Armeria - Restful API 서버 구축하기 - (1) Application 생성

 

[Java] Armeria - Restful API 서버 구축하기 - (1) Application 생성

이번 글에서는 LINE에서 탄생 한 Open Source Framework인 Armeria를 이용하여, Restful API 서버를 간단하게 구축해보려고 합니다. Armeria는 LINE 내에서 사용 중이던 어떤 기술의 한계점을 극복하기 위해 자체

far-ming.tistory.com

[Java] Armeria - Restful API 서버 구축하기 - (2) 도메인서비스 연동

 

[Java] Armeria - Restful API 서버 구축하기 - (2) 도메인서비스 연동

이번 글에선 이전 글에 이어, Armeria Framework에 Restful HTTP API 서비스 연동을 진행해보겠습니다. 이전 글 보러 가기 - [Java] Armeria - Restful API 서버 구축하기 - (1) Application 생성 [Java..

far-ming.tistory.com

 

이번 글에서는 Armeria Restful API Server/Client에서 사용되는 흥미로운 Annotation들에 대해서 다뤄보려고 합니다

 

 

@ExceptionHandler

 

Armeria Service 내부 Class, Interface, Enum, method에 사용할 수 있는 어노테이션입니다.

해당 어노테이션을 명시할 경우, 명시된 구역 내에서 발생하는 Exception들에 대한 처리가 가능합니다.

ExceptionHandler 어노테이션은 Armeria에서 제공하는 인터페이스인 ExceptionHandlerFunction의 구현체를 사용합니다.

 

 

ExceptionHandler 어노테이션 사용 예

@ExceptionHandler(NoSuchElementExceptionHandler.class) // Exception Handler
@Get("/book/:id")
public HttpResponse findById(@Param long id) {
   Book book = BookRepository.findById(id);
   if (book == null) {
       throw new NoSuchElementException(id + " book is not found.");
   }
   return HttpResponse.ofJson(book);
}

 

 

NoSuchElementExceptionHandler.class

public class NoSuchElementExceptionHandler implements ExceptionHandlerFunction {
    @Override
    public HttpResponse handleException(ServiceRequestContext ctx, HttpRequest req, Throwable cause) {
        if (cause instanceof NoSuchElementException) {
            return HttpResponse.of(HttpStatus.NOT_FOUND);
        }
        return ExceptionHandlerFunction.fallthrough();
    }
}

 

ExceptionHandlerFunction 인터페이스는 다른 Handler에게 제어권을 넘길 수 있는 fallthrough 라는 메서드를 제공합니다

우선순위에 기반한 Handler 명시로 자연스러운 Exception Handling 흐름을 구현할 수 있습니다

 

 

 

@ResponseConverters

 

Java의 Converter 인터페이스와 유사하게 응답 데이터에 대한 후처리를 진행할 수 있는 어노테이션입니다

여기서 눈 여겨볼 점은 Response에 대한 Converter를 여러 개 명시하여, 원하는 Converting 작업이 가능하다는 것입니다

ResponseConverters 어노테이션은 ResponseConverter 어노테이션의 배열을 포함하며,

ResponseConverter 어노테이션은 ResponseConverterFunction 인터페이스의 구현체를 사용합니다

 

 

ResponseConverters 어노테이션 사용 예

@ResponseConverters({
            @ResponseConverter(BookResponseConverter.class),
            @ResponseConverter(BookDeletedFailedConverter.class)
})
@Delete("/book/:id")
public HttpResponse deleteById(@Param long id) {
    Book book = BookRepository.deleteById(id);
    return HttpResponse.ofJson(
            Objects.requireNonNullElseGet(book, () ->
                    new BookDeleteFailedResponse("failed",
                                                 "this is cause")));
}

 

 

BookResponseConverter.class

public class BookResponseConverter implements ResponseConverterFunction {

    private final ObjectMapper mapper = new ObjectMapper();

    @Override
    public HttpResponse convertResponse(ServiceRequestContext ctx, ResponseHeaders headers,
                                        @Nullable Object result, HttpHeaders trailers) throws Exception {

        if (result instanceof Book) {
            return HttpResponse.of(HttpStatus.OK,
                                   MediaType.JSON_UTF_8,
                                   "%s", mapper.writeValueAsString(result),
                                   trailers);
        }
        return ResponseConverterFunction.fallthrough();
    }
}

 

ResponseConverterFunction 인터페이스 역시 fallthrough 메서드를 통해 응답 데이터에 대한 자연스러운 Converting 흐름 구현이 가능합니다

 

 

 

@Produces - @Consumes

 

Class, Interface, method에 사용하는 어노테이션으로

Http 통신의 MediaType을 명시할 수 있습니다

 

 

Produces, Consumes 어노테이션 사용 예

@Produces(MediaTypeNames.JSON)
@Consumes(MediaTypeNames.JSON)
@Post("/book")
public HttpResponse createBook(@RequestObject Book book) {
    return HttpResponse.ofJson(BookRepository.createUpdateBook(book));
}

 

 

어노테이션의 Value 값으로는 의존성 Life Cycle을 고려해

armeria.common 패키지에서 제공하는 MediaTypeNames를 사용하였습니다

 

 

 

@ProducesJson - @ConsumesJson

 

Json 데이터를 주로 사용하는 Http 통신에서, Produces, Consumes 어노테이션으로 직접 명시할 필요 없이 MediaType은 Json을 사용한다는 명시적 어노테이션입니다.

ProducesJson은 @Produces("application/json; charset=utf-8") 과 같습니다

 

 

ProducesJson, ConsumesJson 어노테이션 사용 예

@ProducesJson
@ConsumesJson
@Post("/book")
public HttpResponse createBook(@RequestObject Book book) {
    return HttpResponse.ofJson(BookRepository.createUpdateBook(book));
}

 

 

ProducesJson Annotation

/**
 * An alias for {@code @Produces(MediaTypeNames.JSON_UTF_8)}.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Produces(MediaTypeNames.JSON_UTF_8)
public @interface ProducesJson {
}

 

 

@Param

 

Http 통신 PathVariable의 Key 값을 명시적으로 선언하는 곳에도 사용되고,

Request Object의 각 field생성자에도 사용할 수 있는 어노테이션입니다

 

 

Param 어노테이션 사용 예

@Get("/book/:id")
    public HttpResponse findById(@Param long id) { }

 

 

Request Object

public class Book {
    @Param("id")
    private final long id;
    @Param("name")
    @Default("default name") //default 값 명시
    private final String name;
    @Param("page")
    private final long page;
}

 

 

@Blocking

 

Blocking Task 수행이 요구되는 Class 혹은 method에 사용할 수 있는 어노테이션으로

해당 어노테이션이 사용된 부분에서는 Event Loop Thread 대신 Blocking Thread 동작을 수행할 수 있습니다

 

 

Blocking 어노테이션 예

@Blocking
@Delete("/book/:id")
public HttpResponse deleteById(@Param long id) {
    Book book = BookRepository.deleteById(id);
    return HttpResponse.ofJson(
            Objects.requireNonNullElseGet(book, () ->
                    new BookDeleteFailedResponse("failed",
                                                 "this is cause")));
}

 

 

 

Armeria에서 제공하는 Annotation에 대한 Reference이곳에서 확인할 수 있습니다

 

 

반응형

댓글