Dzisiaj zaprezentuję mechanizm dynamicznego proxy (ang. dynamic proxy) udostępniany przez Reflection API, mechanizm wykorzystywany (między innymi) przez Spring’a do implementacji AOP (akr. Aspect-Oriented Programming). Generalnie, dynamiczne proxy umożliwia wywoływanie metod naszej klasy za pośrednictwem metod innej klasy. Pozwala to wykonać pewne akcje przed jak i po wywołaniu właściwej metody albo też ewentualnie zadecydować o nie wykonywaniu tejże. Ale gdyby to było tylko tyle to byłoby to zwykłe proxy. Dlaczego jest ono dynamiczne? Otóż dlatego, że klasa będąca klasą proxy jest generowana dynamicznie w czasie wykonania. Nie mam pomysłu jak zwięźle i trafnie opisać, o co dokładnie chodzi, więc od razu przechodzę do meritum. Załóżmy, że mamy następujący interfejs i implementującą go klasę:
public interface MyService {
public void myOperation();
}
public class MyServiceBean implements MyService {
public void myOperation () {
System.out.println("Message from myOperation()");
}
}
Aby to, co chcę finalnie zbudować wyglądało ładnie użyjemy Spring’a (choć można i bez niego). Poniżej konfiguracja ziarna dla klasy pokazanej powyżej, póki co nic ciekawego, ale będziemy modyfikować:
<bean id="myService" class="mypackage.MyServiceBean">
</bean>
A teraz zdefiniujmy interfejs z dwiema metodami – doBefore() i doAfter() – które to będą wywołane przed i po właściwym wywołaniu metody biznesowej. Dodatkowo, metoda doBefore() będzie zwracała rezultat typu logicznego (typu boolean) o tym znaczeniu, że metoda biznesowa będzie wywoływana tylko wtedy, gdy rezultat ten będzie miał wartość ‘true’. Oto on wraz z implementacją testową:
public interface ProxyAction {
public boolean doBefore();
public void doAfter();
}
public class InvokeAction implements ProxyAction {
public boolean doBefore() {
System.out.println("Message from doBefore()");
return true;
}
public void doAfter() {
System.out.println("Message from doAfter()");
}
}
Naszym celem jest oczywiście takie skonstruowanie kodu, aby to, jaka implementacja interfejsu ProxyAction będzie użyta zależało od konfiguracji Spring’a. To co było banalne już mamy, teraz przechodzimy do sedna.
W dzisiejszej zabawie chodzi o to, aby zastąpić definicję elementu zarządzanego Spring pokazaną powyżej taką definicją, aby zamiast instancji klasy implementującej metody biznesowe dostać instancje innej klasy, utworzonej dynamicznie dzięki mechanizmowi dynamicznych proxy z Reflection API. Taka instancja naturalnie będzie implementowała te same interfejsy co nasza klasa biznesowa. To jest właśnie gwóźdź programu. W tym celu musimy napisać klasę implementującą interfejs java.lang.reflect.InvocationHandler. Oto kod:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ProxyHandler implements java.lang.reflect.InvocationHandler {
private Object proxiedInstance;
private ProxyAction proxyAction;
protected ProxyHandler(Object obj, ProxyAction proxyAction) {
this.proxiedInstance = obj;
this.proxyAction = proxyAction;
}
public Object invoke(Object proxy, Method proxiedMethod, Object[] args)
throws Throwable {
Object result = null;
try {
if (proxyAction.doBefore())
result = proxiedMethod.invoke(proxiedInstance, args);
proxyAction.doAfter();
} catch (InvocationTargetException invocationExc) {
throw invocationExc.getTargetException();
}
return result;
}
}
Kolejnym elementem układanki jest klasa implementująca wzorzec fabryki i tworząca instancje odpowiednio skonfigurowanych proxy. Oto kod:
public class ProxiedServiceFactory {
private ProxyAction proxyAction;
public Object newInstance(Object proxiedInstance) {
return java.lang.reflect.Proxy.newProxyInstance(
proxiedInstance.getClass().getClassLoader(),
proxiedInstance.getClass().getInterfaces(),
new ProxyHandler(proxiedInstance, proxyAction));
}
public void setProxyAction(ProxyAction proxyAction) {
this.proxyAction = proxyAction;
}
}
Działa to tak, że fabryka buduje obiekt dynamicznego proxy na bazie instancji klasy implementującej logikę biznesową przekazanej do wywołania metody newInstance(). Budując wspomniany obiekt proxy przekazujemy jednocześnie instancję klasy implementującą interfejs java.lang.reflect.InvocationHandler. Metoda invoke() tej klasy będzie wywoływana zamiast metod biznesowych i to właśnie tam decydujemy, co tak naprawdę się stanie. Ja akurat postanowiłem, że wywołamy tam metody doBefore() i doAfter() na instancji implementującej interfejs MyService zdefiniowanej w momencie tworzenia fabryki. Aby spiąć wszystko elegancko do kupy należy użyć konfiguracji Spring pokazanej poniżej (w miejsce tej pokazanej na początku artykułu):
<bean id="proxiedServiceFactory" class="mypackage.ProxiedServiceFactory">
<property name="proxyAction">
<bean class="mypackage.InvokeAction" />
</property>
</bean>
<bean id="myService" factory-bean="proxiedServiceFactory" factory-method="newInstance">
<constructor-arg>
<bean class="mypackage.MyServiceBean" />
</constructor-arg>
</bean>
To tyle. Naturalnie, z dokładnością do możliwości użycia operatora ‘instanceof’, ziarna zarządzanego ‘myService’ używamy tak, jakby to była normalna instancja naszej klasy biznesowej. Podkreślę jeszcze tylko, bo być może nie jest to jasne, że to co pokazałem to jest rodzaj szkieletu aplikacji (ang. framework). Mam tu na myśli to, że całą tę skomplikowaną infrastrukturę implementujemy tylko raz, niezależnie od tego ile mamy klas biznesowych. W następnym artykule mam nadzieję pokazać przykład ciekawego wykorzystania pokazanego tu mechanizmu.