اولین مثال عملی که اجرا خواهیم کرد، طراحی یک سیستم ساده Interception برای متدهای Service است. این سیستمی است که در Java EE، Jakarta EE و سایر Framework های کاربردی سازمانی وجود دارد. مفهوم ساده است: یک Annotation روی یک متود اضافه میکنید، این Annotation یک کلاس بهعنوان ویژگی (Attribute) میگیرد و بهجای متود Service شما، متود خاصی از آن کلاس فراخوانی میشود.
مفهوم قطع (Intercept) یک فراخوانی متود بسیار ساده است. بر دو عنصر تکیه دارد: یک Service که دارای یک متود Service است و یک Interceptor که آن نیز یک متود دارد. وقتی متود Service خود را فراخوانی میکنید، آنچه میخواهید این است که بهجای آن، متود Interceptor فراخوانی شود.
سپس این متود میتواند کارهای مختلفی انجام دهد:
این نوع Interceptor را میتوان با استفاده از اشیاء Proxy پیادهسازی کرد، که خارج از محدوده این فصل است. پس این قابلیت را به شکل سادهتری پیادهسازی خواهیم کرد.
بیایید با Annotation مورد نیاز شروع کنیم. این Annotation باید مشخص کند چه کلاس Interceptorای استفاده شود. برای سادگی، فرض میکنیم Interceptor ها یک Interface خاص را پیادهسازی میکنند.
توجه کنید این Interface از Type های پارامتریک (Parameterized Types) برای شیء Intercept شده و Type بازگشتی متود Intercept شده استفاده میکند:
public interface Interceptor<T, R> {
R intercept(T interceptedObject, Method method, Object... arguments);
}
سپس Annotation مورد نیاز را طراحی کنید:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Intercept {
Class<? extends Interceptor<?, ?>> value();
}
بیایید یک Service با متودی که باید Intercept شود ایجاد کنیم:
public class SomeInterceptedService {
@Intercept(MessageInterceptor.class)
public String message(String input) {
return input.toUpperCase();
}
}
با نوشتن این کد، انتظار دارید:
message() کلاس SomeInterceptedService داشته باشید.intercept() کلاس MessageInterceptor قبل از فراخوانی متود message() اجرا شود.کد شما میتواند به این شکل باشد:
void main() {
String output = ServiceFactory.invoke(SomeInterceptedService.class, "message", "Hello");
IO.println("output = " + output);
}
این کلاس پیادهسازی Interface Interceptor است و مسئولیت تصمیمگیری برای فراخوانی یا عدم فراخوانی Service Intercept شده را دارد.
سه عنصر برای فراخوانی Reflective این متود لازم است:
Methodدر اینجا ابتدا اعتبارسنجی آرگومانها انجام میشود. اگر آرگومانها شامل دقیقاً یک عنصر String نباشند، Exception پرتاب شده و متود Intercept شده فراخوانی نمیشود. سپس متود Intercept شده فراخوانی شده و نتیجه با پیام [was intercepted] ترکیب میشود:
public class MessageInterceptor implements Interceptor<SomeInterceptedService, String> {
@Override
public String intercept(
SomeInterceptedService service, Method interceptedMethod, Object... arguments) {
try {
if (arguments.length == 1) {
// validating the arguments
String input = (String) arguments[0];
Objects.requireNonNull(input, "Input is null");
if (input.isEmpty()) {
throw new IllegalArgumentException("Input is empty");
}
// calling the service method
String result = (String)interceptedMethod.invoke(service, arguments);
return result + " [was intercepted]";
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
throw new IllegalArgumentException(
"Arguments should contain exactly one argument of type String");
}
}
این کلاس فنیتر است و از Reflection API بهره میبرد.
کلاسی که متود روی آن اعلام شده، نام متود Intercept شده و آرگومانها را دریافت میکند. مراحل کار به این صورت است:
value() آن فراخوانی میشود تا کلاس Interceptor بازگردانده شود.intercept() آن فراخوانی میشود.اگر Annotation یافت نشود، متود Service بهصورت Reflective و بدون Interception فراخوانی میشود:
public class ServiceFactory {
public static <T, R> R invoke(
Class<? extends T> serviceClass, String methodName, Object... arguments) {
try {
// Getting an instance of the service
T service = serviceClass.getConstructor().newInstance();
// Locating the service method
Class<?>[] parameterClasses =
Arrays.stream(arguments).map(Object::getClass).toArray(Class<?>[]::new);
Method method = serviceClass.getDeclaredMethod(methodName, parameterClasses);
// locating the Intercept annotation
if (method.isAnnotationPresent(Intercept.class)) {
Intercept intercept = method.getDeclaredAnnotation(Intercept.class);
Class<? extends Interceptor<T, R>> interceptorClass =
(Class<? extends Interceptor<T, R>>) intercept.value();
// creating an instance of the interceptor
Interceptor<T, R> interceptor = interceptorClass.getConstructor().newInstance();
// intercepting the service method
R returnedObject = interceptor.intercept(service, method, arguments);
return returnedObject;
} else {
// invoking the service method
R returnedObject = (R) method.invoke(service, arguments);
return returnedObject;
}
} catch (InstantiationException | IllegalAccessException |
InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
همانطور که میبینید، این کد نسبتاً پیچیده، تا حدی مبهم و در زمان کامپایل Type-Safe نیست. این وضعیت در بیشتر مواردی که از Reflection API در موارد کاربردی واقعی استفاده میکنید، صادق است.
بیایید کلاس دیگری اضافه کنیم تا ثابت شود مکانیزم Interception بهدرستی کار میکند:
public class SomeNonInterceptedService {
String message(String input) {
return input.toUpperCase();
}
}
حالا که تمام عناصر آماده هستند، کد زیر را اجرا کنید:
public static void main(String[] args) {
String interceptedOutput =
ServiceFactory.invoke(SomeInterceptedService.class, "message", "Hello");
IO.println("Intercepted output = " + interceptedOutput);
String nonInterceptedOutput =
ServiceFactory.invoke(SomeNonInterceptedService.class, "message", "Hello");
IO.println("Non intercepted output = " + nonInterceptedOutput);
}
خروجی:
Intercepted output = HELLO [was intercepted]
Non intercepted output = HELLO
نکته مهم: این مثال بهدرستی کار میکند چون Type ها بهخوبی تعریف شدهاند. متود invoke() به دنبال متودی به نام message() است که یک String بهعنوان پارامتر بگیرد. اگر متود message() روی Type CharSequence بهجای String تعریف شده بود، فراخوانی getDeclaredMethod() این متود را پیدا نمیکرد.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/