Annotation ها بهطور گسترده با Reflection API استفاده میشوند. بسیاری از Framework ها از آنها بهصورت گسترده و با موفقیت بالا استفاده میکنند. این شامل Framework های ORM، Dependency Injection، برخی Framework های اعتبارسنجی و برخی Framework های امنیتی میشود. در اصل، Java EE، Jakarta EE و تمام Framework های مشابه مجموعه Annotation های خود را تعریف میکنند که روی کلاسها و اعضای کلاس اضافه میشوند تا رفتار خاصی را فعال کنند. این بخش نحوه عملکرد این سیستم در پشت صحنه و نحوه استفاده از Annotation ها در runtime را نشان میدهد.
کلاسهای زیر از Reflection API Interface AnnotatedElement را پیادهسازی میکنند که به شما امکان دسترسی به Annotation های چنین عنصری را میدهد: Field، Method، Constructor و Class. به یاد داشته باشید که کلاس Class کلاسها، کلاسهای انتزاعی، Interface ها، Enum ها، Record ها و آرایهها را مدلسازی میکند.
فرض کنید Enum و دو Annotation @Bean و @Serialized را در برنامه خود دارید:
enum SerializedFormat {
BINARY, XML, JSON
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface Bean {}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface Serialized {
SerializedFormat format() default SerializedFormat.JSON;
}
هر دو Annotation روی Type ها قابل اعمال هستند و در runtime در دسترس هستند. Annotation @Serialized یک ویژگی به نام format دارد که سه مقدار BINARY، XML و JSON میتواند بگیرد.
فرض کنید کلاس Person را به این صورت تعریف کردهاید:
@Serialized @Bean
public class Person {
}
Reflection API متدهای متعددی برای کشف این Annotation ها ارائه میدهد:
isAnnotationPresent(Class<? extends Annotation>): بررسی میکند آیا یک Annotation خاص روی این عنصر وجود دارد یا خیر. کلاسی که بهعنوان آرگومان پاس میدهید باید یک Annotation Class باشد.getAnnotations() و getDeclaredAnnotations(): آرایهای از Annotation های موجود روی این عنصر را برمیگرداند. این آرایه اگر Annotationای وجود نداشته باشد میتواند خالی باشد. متود getDeclaredAnnotations() فقط Annotation های اعلامشده روی همین عنصر را برمیگرداند، بدون Annotation های ارثبریشده.getAnnotation(Class) و getDeclaredAnnotation(Class): یک Annotation برای Type خاص پاسدادهشده برمیگردانند.getAnnotationsByType(Class) که در Java SE 8 برای پشتیبانی از Repeating Annotation ها اضافه شده. این متود آرایهای از Annotation های مرتبط با این عنصر برمیگرداند.بیایید این متدها را در عمل ببینیم.
اول، میتوانید بررسی کنید آیا کلاس Person Annotationای دارد و آنها را کشف کنید:
Class<?> c = Person.class;
boolean isBean = c.isAnnotationPresent(Bean.class);
IO.println("isBean = " + isBean);
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
IO.println("annotation = " + annotation);
}
isBean = true
annotation = @org.devjava.Serialized(format=JSON)
annotation = @org.devjava.Bean()
نکته مهم: چیزی که دریافت میکنید نمونههایی از کلاسهای Annotation تعریفشده شماست که میتوانید متدهای تعریفشده در آنها را فراخوانی کنید:
Class<?> c = Person.class;
Serialized annotation = c.getAnnotation(Serialized.class);
SerializedFormat format = annotation.format();
IO.println("format = " + format);
format = JSON
بیایید یک Annotation تکرارپذیر جدید ایجاد کنیم:
enum ValidationRules {
NON_NULL, NON_EMPTY, NON_ZERO
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Validators {
Validator[] value();
}
@Target(ElementType.FIELD)
@Repeatable(Validators.class)
@interface Validator {
ValidationRules value();
}
و کلاس Person را تغییر دهید تا یک فیلد با این Annotation تکرارپذیر اضافه شود:
public class Person {
@Validator(ValidationRules.NON_NULL)
@Validator(ValidationRules.NON_EMPTY)
private String name;
}
از دیدگاه فایل کلاس، در واقع فقط یک Annotation روی فیلد name وجود دارد: @Validators. میتوانید این نمونه را دریافت کرده و Annotation های @Validator درون آن را با فراخوانی متود value() بازیابی کنید:
Class<?> c = Person.class;
Field nameField = c.getDeclaredField("name");
Annotation[] annotations = nameField.getAnnotations();
IO.println("# annotations = " + annotations.length);
Validators validators = (Validators)annotations[0];
for (Validator validator : validators.value()) {
IO.println("validator = " + validator);
}
# annotations = 1
validator = @org.devjava.Validator(NON_NULL)
validator = @org.devjava.Validator(NON_EMPTY)
متود getAnnotationsByType(Class) این کار را سادهتر میکند. فراخوانی آن با کلاس Validator بهعنوان آرگومان، Annotation های مورد نظر شما را برمیگرداند:
Class<?> c = Person.class;
Field nameField = c.getDeclaredField("name");
Annotation[] annotations = nameField.getAnnotationsByType(Validator.class);
IO.println("# annotations = " + annotations.length);
for (Annotation annotation : annotations) {
IO.println("annotation = " + annotation);
}
# annotations = 2
annotation = @org.devjava.Validator(NON_NULL)
annotation = @org.devjava.Validator(NON_EMPTY)
Annotation های اعلامشده روی Type ها (کلاسها، کلاسهای انتزاعی و Interface ها) میتوانند توسط زیرکلاسهایشان ارث برده شوند. این کار برای Enum ها و Record ها معنایی ندارد، چون این دو کلاس Final هستند و قابل ارثبری نیستند.
متدهای getDeclaredAnnotations() و getDeclaredAnnotation(Class) رفتار سختگیرانهای دارند و فقط Annotation های اعلامشده روی Type ای را برمیگردانند که متود را روی آن فراخوانی میکنید. از طرف دیگر، getAnnotations() و getAnnotation(Class) ممکن است Annotation های ارثبریشده را نیز بازگردانند.
فرض کنید Annotation Bean زیر قابل ارثبری است:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Bean {}
و کلاسهای زیر را دارید. توجه کنید کلاس Person با @Bean Annotation شده، نه کلاس User که از آن ارث میبرد:
@Bean
class Person {}
class User extends Person {}
Annotation های موجود روی کلاس User را بررسی کنید:
Class<?> c = User.class;
Annotation[] declaredAnnotations = c.getDeclaredAnnotations();
IO.println("# declared annotations = " + declaredAnnotations.length);
Annotation[] annotations = c.getAnnotations();
IO.println("# annotations = " + annotations.length);
for (Annotation annotation : annotations) {
IO.println("annotation = " + annotation);
}
# declared annotations = 0
# annotations = 1
annotation = @org.devjava.Bean()
Annotated Type ها Annotation هایی هستند که روی Type ها اعلام شدهاند و در هر جای کد قابل استفاده هستند.
Reflection API از طریق Interface AnnotatedType به برخی از این Annotation ها دسترسی میدهد.
فرض کنید Annotation زیر را دارید. این Annotation در جایی که Type ها استفاده میشوند قابل اعمال است، اما روی اعلام Type ها نه:
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface NonNull {}
میتوانید این Annotation را به این شکل استفاده کنید. توجه کنید این Annotation روی کلاس ارثبر و Interface پیادهسازیشده اعلام شده است:
public class Person {}
public class User
extends @NonNull Person
implements @NonNull Serializable {
}
Reflection API به شما این دسترسی را میدهد:
Class<?> c = User.class;
AnnotatedType superClass = c.getAnnotatedSuperclass();
Annotation[] superClassAnnotations = superClass.getAnnotations();
for (Annotation annotation : superClassAnnotations) {
IO.println("annotation on the super class = " + annotation);
}
AnnotatedType[] interfaces = c.getAnnotatedInterfaces();
for (AnnotatedType implementedInterface : interfaces) {
Annotation[] interfaceAnnotations = implementedInterface.getAnnotations();
for (Annotation interfaceAnnotation : interfaceAnnotations) {
IO.println("annotation on the implemented interface = " + interfaceAnnotation);
}
}
annotation on the super class = @org.devjava.NonNull()
annotation on the implemented interface = @org.devjava.NonNull()
فرض کنید میخواهید Annotation روی یک Exception کشف کنید. این Exception یک RuntimeException نیست، پس باید آن را بهصورت صریح مدیریت کنید. برای حل این مشکل، Annotationای ایجاد میکنید تا به کد Client بگویید آیا میتواند این Exception را دوباره پرتاب (Rethrow) کند یا نه:
class EmptyStringException extends Exception {
public EmptyStringException(String message) {
super(message);
}
}
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReThrowAsRuntimeException {}
public class Message {
private final String name;
public Message(String name) throws @ReThrowAsRuntimeException EmptyStringException {
Objects.requireNonNull(name);
if (name.isEmpty()) {
throw new EmptyStringException("name is empty");
}
this.name = name;
}
public String name() {
return name;
}
}
و کد Client زیر را میتوانید بنویسید:
try {
Message message = new Message("");
} catch (EmptyStringException e) {
Class<?> c = Message.class;
Constructor<?> constructor = c.getConstructor(String.class);
AnnotatedType[] annotatedExceptionTypes = constructor.getAnnotatedExceptionTypes();
for (AnnotatedType annotatedExceptionType: annotatedExceptionTypes) {
boolean annotationPresent =
annotatedExceptionType.isAnnotationPresent(ReThrowAsRuntimeException.class);
if (annotationPresent){
throw new RuntimeException(e);
}
}
IO.println(e.getMessage());
e.printStackTrace();
}
خروجی نشان میدهد Exception بهصورت Runtime Exception دوباره پرتاب شده است:
Exception in thread "main" java.lang.RuntimeException: org.devjava.EmptyStringException: name is empty
at org.devjava.Main.main(ScrapMain.java:32)
Caused by: org.devjava.EmptyStringException: name is empty
at org.devjava.Message.<init>(ScrapMain.java:18)
at org.devjava.Main.main(ScrapMain.java:45)
Exception in thread "main" java.lang.RuntimeException: org.devjava.EmptyStringException: name is empty
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/