Dynamiczne proxy i adnotacje, czyli dobrana para
Dzisiaj dodam nieco smaczku (dla zaostrzenia apetytu) do rozwiązania, które opisałem ostatnio w artykule "Mechanizm dynamicznego proxy z Reflection API". Zmodyfikuję pokazany tam kod w ten sposób, by metody proxy odczytywały adnotację umieszczoną na interfejsie klasy biznesowej i wykonywały akcję na podstawie wartości parametrów tejże adnotacji. Zacznę od pokazania jeszcze raz interfejsu i klasy, dla których budujemy proxy, ale tym razem ze wspomnianą adnotacją. Oto one:
public interface MyService {
@MyAnnotation(“my annotation value”)
public void myOperation();
}
public class MyServiceBean implements MyService {
public void myOperation () {
System.out.println("Message from myOperation()");
}
}
Naturalnie użyta tu adnotacja MyAnnotation nie jest jakąś magiczną adnotacją, musimy ją sami zdefiniować tak jak definiujemy klasy i interfejsy. Oto odpowiedni kod:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
public @interface MyAnnotation {
String value();
}
Podkreślę tutaj, iż to dzięki temu, że jedyny parametr naszej adnotacji nazywa się 'value' możemy używać notacji skróconej i zamiast @MyAnnotation(value = "my annotation value") pisać @MyAnnotation("my annotation value") tak jak to pokazałem w przykładzie na początku artykułu. Naturalnie, jakby komuś było za mało można dodać kolejne parametry.
Zmodyfikujmy teraz interfejs ProxyAction i jego testową implementację tak, aby metody doBefore() i doAfter() akceptowały parametr typu String będący wartością naszej adnotacji. Oto kod:
public interface ProxyAction {
public boolean doBefore(String annotationValue);
public void doAfter(String annotationValue);
}
public class InvokeAction implements ProxyAction {
public boolean doBefore(String annotationValue) {
System.out.println("Message from doBefore(" + annotationValue + ")");
return true;
}
public void doAfter(String annotationValue) {
System.out.println("Message from doAfter("+ annotationValue + ")");
}
}
Pozostaje jeszcze tylko zmodyfikować kod klasy ProxyHandler implementującej interfejs InvocationHandler tak aby odczytywał i przekazywał w wywołaniach wartość adnotacji. W tym celu dodajmy metodę getValue() i zmodyfikujmy metodę invoke() tak jak pokazano poniżej:
public Object invoke(Object proxy, Method proxiedMethod, Object[] args)
throws Throwable {
Object result = null;
try {
String annotationValue = getValue(proxiedMethod);
if (proxyAction.doBefore(annotationValue))
result = proxiedMethod.invoke(proxiedInstance, args);
proxyAction.doAfter(annotationValue);
} catch (InvocationTargetException invocationExc) {
throw invocationExc.getTargetException();
}
return result;
}
private String getValue(Method proxiedMethod) {
for(Annotation annotation : proxiedMethod.getAnnotations()) {
if(annotation instanceof MyAnnotation) {
return ((MyAnnotation)annotation).value();
}
}
throw new RuntimeException("Annotation not found");
}
Prawdopodobnie dobrze by jeszcze było zastąpić wyjątek RuntimeException rzucany z metody getValue() jakimś własnym, ale to już szczegół. Nie wiem jak wy, ale ja jestem zachwycony elegancją, prostotą i siłą wyrazu tego rozwiązania i niewykluczone, że zaprezentuje jeszcze coś ciekawego na bazie tych mechanizmów.
2 komentarze:
Jeszcze trochę, a dotrzesz do pokładów EJB3 i jego interceptorów. Właśnie metody przechwytujące jak @AroundInvoke, czy @Pre*/@Post* udostępniają podobną funkcjonalność.
Czuję, że chcę więcej. Zgłębiaj temat dalej ;-)
Hmmm...
nie specjalnie chcę docierać z tym tematem do EJB, raczej wprost przeciwnie. Adnotacje o których wspominasz nie są też ekwiwalentem tego o czym pisze - zauważ, że w moim przykładzie to co się dzieje "dookoła" metod jest zdefiniowane w całości poza klasą biznesową. Rzeczą do której zmierzam jest implementacja mechanizmu bezpieczeństwa dla aplikacji JSF, ale trzeba będzie jeszcze sporo napisać zanim będzie jasne o co mi dokładnie chodzi. W każdym razie, elementem bezpieczeństwa aplikacji WWW/JSF jest zabezpieczenie (autoryzacja) klas będących serwisami (czyli wykonujacych prawdziwą robotę). Tak się składa, że ostatnio z dnia na dzień coraz bardziej lubię Spring'a (nie bez powodów) i zdryfowałem w stronę zagadnień zabezpieczania aplikacji JSF + Spring, tak więc bezpieczeństwo dla serwisów staje się bezpieczeństwem dla ziaren zarządzanych Springa. A do osiągnięcia tego, od mechanizmu który opisałem w tym i poprzednim artykule już bardzo blisko - wystarczy, że zamiast adnotacji @MyAnnotation damy adnotacje @RoleRequired a w metodzie doBefore(String adnotationValue) sprawdzimy czy zalogowana tożsamość ma przypisaną daną rolę i voilla... Choć czas pokaże, czy nie pokuszę się o napisanie, dlaczego uważam że to co tu prezentuje jest inne od wszelakich technik AOP. A może przekonam się że różnica jest mniejsza niż mogło by się wydawać... zobaczymy
Prześlij komentarz