@Configuration과 @EnableScheduling을 통해 해당 클래스를 빈으로 등록 및 스케줄러로써 사용하도록 하고, @EnableRetry를 통해 스케줄러의 실행에 실패했을 때의 재시도 등을 진행할 수 있도록 하였다.
추가로, 다음과 같이 스케줄과 관련된 구체적인 설정들을 메서드에 적용하였다.
이슈 1
하지만, 여러 개의 스케줄러를 구현하다보니 다음과 같은 이슈(또는 고치고 싶은 부분?)가 생기게 되었다.
1. 스케줄러 클래스에 적용된 어노테이션의 갯수가 너무 많음(가독성이 떨어짐) 2. 스케줄러의 시작과 끝에 로그를 찍고자 함
따라서, 2가지 이슈를 어떻게 해결할 수 있을 지를 고민하였고, 커스텀 어노테이션을 적용하기로 하였다.
해결 1
먼저, 다음과 같이 PoscoSchedulerAnnotation을 만들었다. 해당 어노테이션은 스케줄러 관련 어노테이션들을 실행 시점에도 Enable하기 위해 Retention을 RUNTIME으로 주었고, Target또한 클래스에 적용 할 것이기 때문에 TYPE으로 주었다.
다음으로, Aspect 클래스를 작성하였다.
@Pointcut, 즉 조인 포인트를 매칭하는 값으로는 해당 어노테이션이 달린 클래스 내부의 모든 메서드를 어드바이스 부여 대상으로 선택하였다. @Before와 @AfterReturning의 경우, 특정 메서드를 호출하기 전과 메서드가 종료되었을 때 실행 할 로직을 의미한다. execution의 경우는 생략을 해도 무방하다.
해당 방법으로, 요구사항을 만족하는 듯.. 하였지만, 다른 이슈가 생기게 되었다.
이슈2
the return value of "java.lang.reflect.Method.getAnnotation(java.lang.Class)" is null
리플렉션 과정에서 오류가 발생을 하였는데, 스케줄러 클래스에 작성해 놓은 PoscoSchedulerAnnotation이 스케줄러 클래스에 존재하지 않아서 생겨버린 것이었다.
즉, 스케줄러 클래스인 UpdateEmployeeLatestInfoScheduling가 아닌, 해당 클래스를 상속받은 클래스가 타겟이었기 때문에 어노테이션이 존재하지 않은 것이었다.
스프링의 경우, 특정 클래스를 빈으로 등록을 할 때 해당 클래스의 인스턴스를 그대로 사용하지 않고, 그 클래스를 상속한 클래스의 인스턴스를 빈으로 등록한다. 그리고 해당 과정에서 CGLIB이라는 코드 생성 라이브러리를 사용한다.
다시 설명하면, CGLIB은 Spirng에서 AOP를 적용할 때 런타임시 프록시를 생성하는데, 이때 인터페이스 기반 클래스는 JDK Dynamic Proxy로, 단순클래스 기반은 CGLIB를 사용한다. 하지만 Spring Boot AOP에서는 CGLIB만을 기본적으로 사용하고 있다.
(CGLIB는 타겟에 대한 정보를 직접적으로 제공 받아 바이트 코드를 조작하여 프록시를 생성한다. 때문에 리플렉션을 사용하는 JDK Dynamic Proxy에 비해 성능이 좋다. 또한 CGLIB는 메소드가 처음 호출 되었을 때 동적으로 타겟 클래스의 바이트 코드를 조작하고, 이후 호출 시엔 조작된 바이트 코드를 재사용한다.)
해결 2
따라서, 스케줄러 클래스 뿐 아니라 그 클래스를 상속받은 프록시 클래스의 인스턴스에도 커스텀 어노테이션이 런타임에 적용되도록 하면 되었다.
어떻게 적용할 지를 고민하던 중 어노테이션의 옵션 중 하나인 @Inherited를 발견하였고, 해당 옵션(어노테이션)은 그 타겟 어노테이션이 적용된 클래스 뿐 아니라 클래스를 상속한 서브클래스에도 어노테이션이 자동으로 적용되도록 하는 옵션이었다.
따라서, 해당 옵션을 넣어주었고, 원하는 대로 스케줄러의 전,후로 로그를 출력할 수 있게 되었다.