JAVA/Reflection

애너테이션 with Reflection 4

최-코드 2024. 12. 1. 19:27

파라미터 어노테이션 사용 이유 

  • 컴파일링이 끝나면 파라미터명이 사라진다. 이 때 리플렉션으로 파라미터명을 가져오면 arg0와 같이 표현된다.
  • 그러면 runtime 시점에 값을 바인딩할 때 어려움이 있다. 따라서 @RequestParam("paramerName")와 같이 parameterName을 인자로 보내준다.

 

필드 어노테이션 사용 이유 

  • 파라미터와 다르게 컴파일 이후에도 필드명이 사라지지 않는다.
  • 보통 설정 파일이나 데이터베이스의 컬럼에서 데이터를 가져와 매핑하는 데에 사용한다.
  • 예를 들어 @Value, @Column이 있다.

반복 가능 애너테이션 

  • 기본적으로 동일한 애너테이션의 경우 한 타깃에 여러 개를 붙이면 컴파일 에러가 발생한다. 이를 해결하기 위한 애너테이션을 반복 가능 애너테이션이라고 칭한다.
  • 이를 위해선 추가적인 메타 애너테이션이 필요한데 @Repeatable 어노테이션이다. 이 어노테이션의 인자에는 컨테이너 에너테이션을 인자로 넣어야 한다.
  • 형식은 아래와 같다.
@Repeatable(Roles.class)
public @interface Role {
	Type vale();
}

//컨테이너 에너테이션
public @interface Roles {
	Role[] vale()
}
  • 여러 어노테이션이 붙은 형태는 컴파일 이후 컨테이너 어노테이션으로 변경된다.
@Role(~~)
@Role(~~)
⬇️
@Roles({...})
  • @Target을 설정할 시에 컨테이너 애너테이션과 기본 애너테이션이 같아야 한다.
  • 런타임 땐 컨테이너만 사용되므로 @Rentention(RetentionPolicy.RUNTIME)은 컨테이너만 붙이면 된다.
  • 추가적으로 반복 가능 애너테이션은 isAnnotationPresent() 메서드에서는 애너테이션이 존재해도 false가 나온다. 또한 getAnnotation() 메서드에서는 null이 반환된다. 그 이유는 컴파일러에 의해 컨테이너 애너테이션으로 변경되기 때문이다.

T[] getAnnotationsByType(Class<T> annotationClass)

  • 존재하면 애너테이션 객체 배열을 반환하고, 존재하지 않으면 빈 배열을 반환한다.
  • 이 때 annotationClass에 들어갈 값은 컨테이너 애너테이션 클래스가 아닌 @Repeatable이 붙은 애너테이션이다.

 

Example

public class Annotations {

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ScanPackages {
        String[] value();
    }

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ScheduledExecutorClass {
    }

    @Target(ElementType.METHOD)
    @Repeatable(ExecutionSchedules.class)
    public @interface ExecuteOnSchedule {
        int delaySeconds() default 0;
        int periodSeconds();
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExecutionSchedules {
        ExecuteOnSchedule[] value();
    }
}

@Annotations.ScheduledExecutorClass
public class Cache {

    @Annotations.ExecuteOnSchedule(periodSeconds = 5)
    @Annotations.ExecuteOnSchedule(delaySeconds = 10, periodSeconds = 1)
    public static void reloadCache() {
        System.out.println("Reloading cache");
    }
}

@Annotations.ScanPackages({"loaders"})
public class Main {
    public static void main(String[] args) throws Throwable {
        schedule();
    }

    public static void schedule() throws ClassNotFoundException, IOException, URISyntaxException {
        Annotations.ScanPackages scanPackages = Main.class.getAnnotation(Annotations.ScanPackages.class);
        if (scanPackages == null || scanPackages.value().length == 0) {
            return;
        }

        List<Class<?>> allClasses = getAllClasses(scanPackages.value());
        List<Method> scheduledExecutorMethods = getScheduledExecutorMethods(allClasses);

        for (Method method : scheduledExecutorMethods) {
            scheduleMethodExecution(method);
        }
    }

    private static void scheduleMethodExecution(Method method) {
        Annotations.ExecuteOnSchedule[] schedules = method.getAnnotationsByType(Annotations.ExecuteOnSchedule.class);

        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

        for (Annotations.ExecuteOnSchedule schedule : schedules) {
            scheduledExecutorService.scheduleAtFixedRate(
                    () -> runWhenScheduled(method),
                    schedule.delaySeconds(),
                    schedule.periodSeconds(),
                    TimeUnit.SECONDS);
        }
    }

    private static void runWhenScheduled(Method method) {
        Date currentDate = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

        System.out.printf("Executing at %s%n", dateFormat.format(currentDate));

        try {
            method.invoke(null);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private static List<Method> getScheduledExecutorMethods(List<Class<?>> allClasses) {
        List<Method> scheduledMethods = new ArrayList<>();

        for (Class<?> clazz : allClasses) {
            if (!clazz.isAnnotationPresent(Annotations.ScheduledExecutorClass.class)) {
                continue;
            }
            for (Method method : clazz.getDeclaredMethods()) {
                if (method.getAnnotationsByType(Annotations.ExecuteOnSchedule.class).length != 0) {
                    scheduledMethods.add(method);
                }
            }
        }
        return scheduledMethods;
    }

    public static List<Class<?>> getAllClasses(String... packageNames) throws URISyntaxException, IOException, ClassNotFoundException {...}
    private static List<Class<?>> getAllPackageClasses(Path packagePath, String packageName) throws IOException, ClassNotFoundException {...}
}