Najważniejszym elementem rozwiązania jest interceptor, który przechwytuje wywołania setterów:
package pl.kadamczyk.springpropertychange.interceptors;
import java.lang.reflect.Field;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import pl.kadamczyk.springpropertychange.model.BaseModelObject;
public class SetterMethodInterceptor implements MethodInterceptor {
/**
* Otacza faktyczne wywołanie metody
*/
public Object invoke(final MethodInvocation method) throws Throwable {
String jointPoint = method.getMethod().getName();
// wyznaczamy nazwe pola na podstawie nazwy wywolanego settera
String fieldName = jointPoint.replaceFirst("set", "");
fieldName = Character.toLowerCase(fieldName.charAt(0))
+ fieldName.substring(1);
try {
Object target = method.getThis();
Class clazz = target.getClass();
// pobieramy pole klasy o wyznaczonej wczesniej nazwie
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
// pobieramy stara wartosc pola
Object oldValue = field.get(target);
// zezwalamy na wywolanie settera
Object result = method.proceed();
if (method.getArguments().length == 1) {
triggerPropertyChange(target, method.getArguments()[0],
oldValue, fieldName);
}
return result;
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* Sprawdza, czy metoda została wykonana na obiekcie klasy dziedziczącej
* po BaseModelObject, a jeśli tak, wywołuje na nim firePropertyChange
*/
private void triggerPropertyChange(final Object target,
final Object newVal, final Object oldValue, final String fieldName) {
System.out.println("MethodInterceptor: Zmieniamy wartosc pola "
+ fieldName);
if (target instanceof BaseModelObject) {
// wywolujemy metode z klasy bazowej
((BaseModelObject) target).firePropertyChange(fieldName, oldValue,
newVal);
}
}
}
Jest to częściowo zmieniona wersja aspektu z poprzedniego posta, dostosowana do Springowego API.
Drugim krokiem jest odpowiednie skonfigurowanie interceptora:
Na uwagę zasługuje bean pointcut.setterAdvisor który odpowiada za powiązanie interceptora z pointcut'ami w których ma być zaaplikowany.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<aop:aspectj-autoproxy />
<bean id="someModelObject"
class="pl.kadamczyk.springpropertychange.model.SomeModelObject"
scope="prototype" />
<bean name="setterInterceptor"
class="pl.kadamczyk.springpropertychange.interceptors.SetterMethodInterceptor" />
<bean name="pointcut.setterAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="setterInterceptor" />
<property name="mappedName" value="set*" />
</bean>
</beans>
W odróżnieniu od pierwszego rozwiązania w którym zastosowałem czysty AspectJ oraz compile-time-weaving, tutaj aplikowanie porady wykonywane jest w trakcie działania.
Konsekwencją tego jest zmiana sposobu korzystania z klas modelu. Aby Spring AOP mógł wygenerować odpowiednie proxy w runtime, konieczne jest pobranie obiektu z fabryki - w naszym przypadku będzie to ClassPathXmlApplicationContext:
Zwróćmy uwagę, że bean someModelObject w pliku konfiguracyjnym Spring'a zdefiniowany jest z atrybutem scope="prototype" aby wywołanie context.getBean zwracało za każdym razem nową instancję.
public static void main(final String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext(
"beans.xml");
SomeModelObject modelObject = (SomeModelObject) context
.getBean("someModelObject");
modelObject.setSomeIntProperty(23);
modelObject.setSomeStringProperty("ala");
}
Podobnie jak poprzednio, wynikiem działania metody main jest napis na konsoli:
MethodInterceptor: Zmieniamy wartosc pola someIntProperty
MethodInterceptor: Zmieniamy wartosc pola someStringProperty

1 komentarze:
Witam
Fajny wpis mam jednak 2 uwagi:
1)jestem ciekaw czy jestes swiadomy czemu w twojej konfiguracji jest element aop:aspectj-autoproxy ?
Zgodnie z dokuemtacja element ten ma słuzyc do odpowiedniego "obrabiania" beanow które maja annotacje @Aspect.
U ciebie takich beanów nie ma ...
(Nie)świadomie wykorzystany został fakt, że w specyficznych przypadkach Spring (znam 2 takie przypadki )autoproxuje beany. Osobiscie uwazam to za dosc duze zagrozenie, zreszta jest to 1 z powodów dlaczego nie polecane jest mieszanie sposobów definiowania konfiguracji Spring AOP.
2) Stosowanie Spring AOP do interceptowania zmian na propertiesach JavaBean uwazam troche za sztuke dla sztuki, poniewaz obiekty modelu (dla których wydaje sie byc sensowne wychwytywac zmiany propertiesów) nie są pod "władaniem" kontenera DI (pomijajac uzywanie @Configurable), a 2 sprawa to, że bardzo często settery są metodami final, a tych Spring AOP (w szczególności Cglib) nie łapie...
Prześlij komentarz