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 {...}
}