3 grudnia 2007

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:

Jacek Laskowski pisze...

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 ;-)

Mariusz Lipiński pisze...

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