Detta inlägg ingår i serien Spring från början och behandlar grunderna i Spring AOP.
Den här guiden förklarar springs AOP-stöd och går inte igenom grunderna i AOP eller användningsområden. Artikeln förutsätter en grundläggande förståelse av Spring DI. Spring har ett stöd för AOP som är kraftfullt och mycket lätt att komma igång med. Vi kastar oss rätt in i lite exempelkod som jag snart ska förklara.
Starter
public class Starter {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationcontext.xml");
ExampleBean bean = (ExampleBean) ctx.getBean("myBean");
System.out.println(bean.getText("hello"));
}
}
ExampleBean
public class ExampleBean {
public String getText(String begin) {
return begin + " World";
}
}
ExampleAdvice
public class ExampleAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target)
throws Throwable {
if (method.getParameterTypes().length > 0 &&
method.getParameterTypes()[0].equals(String.class))
args[0] = ((String) args[0]).toUpperCase();
}
}
applicationcontext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
default-autowire="no">
<bean id="myBean" class="examplepkg.ExampleBean"/>
<bean id="advice" class="examplepkg.ExampleAdvice"/>
<bean id="advisor"
class="org....support.RegexpMethodPointcutAdvisor">
<property name="pattern" value=".*."/>
<property name="advice" ref="advice"/>
</bean>
<bean id="beanProxyCreator"
class="org....framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>
Vid körning av detta program blir utskriften ”HELLO world”. Starter kör bean.getText med argumentet ”hello” och i examplebean appendas World på intexten för att sedan returneras. Utan AOP skulle alltså utskriften vara ”hello World”.
Vi börjar att titta på applicationcontext.xml. Rad 10 är första intressanta raden, jag väljer att stänga av autowiring för tydlighetens skull, inte på något sätt ett krav. Med autowire påslaget kan man skippa rad 16. Följande rader skapar bönorna mybean samt advice med respektive klassers defaultkonstruktorer.
Nästa böna heter advisor och är en instans av RegexpMethodPointcutAdvisor som följer med Spring. En advisor är springs implementation av en aspekt och består av en eller flera pointcuts och advice.
En pointcut är helt enkelt en beskrivning av punkter i koden, i detta fall utgörs vår pointcut av ett mönster som beskriver en mängd metodnamn. RegexpMethodPointcutAdvisor som används skapar själv en pointcut av, som namnet beskriver, ett reguljärt uttryck som beskriver metodnamn.
Det advice som används är vår tidigare definierade advice böna. Javaklassen ExampleAdvice implementerar MethodBeforeAdvice och det framgår ganska väl av namnet vad det handlar om.
Rad 19 är verkligt intressant. Här skapar vi en DefaultAdvisorAutoProxyCreator som är en spring-klass. Vid instansiering kommer den att leta upp samtliga advisors i vår applicationContext och applicera dem på samtliga bönor.
Om någon pointcut i advisorn matchar på bönan skapas automatiskt en proxy för bönan. Bönan, i detta fall myBean, byts ut mot proxyobjektet och i Starter är det proxyobjektet som används.
Exempel på alternativ till DefaultAdvisorAutoProxyCreator är BeanNameAutoProxyCreator, den används om man vill definiera vilka bönor som ska vara prospekts för proxy-generering. Om man vill köra exemplet utan AOP kan man kommentera ut rad 19-20 eller mer korrekt rad 10-20.
Mer om Advisors
Det finns flera färdigimplementerade advisors och det är heller ingen konst att implementera en egen. Ett exempel på en annan advisor är DefaultPointcutAdvisor som tar en pointcut och ett advice(dvs pointcut istället för pattern), om jag hade använt denna advisor istället hade jag behövt definiera min pointcut separat genom att till exempel använda JdkRegexpMethodPointcut. Vad RegexpMethodPointcutAdvisor gör är att den internt skapar en sådan pointcut med det pattern man anger.
Mer om Pointcuts
Hittills har vi bara tittat på en pointcut som använder ett reguljärt uttryck för att hitta metoder, utan att bekymra sig över vad indata är till metoderna.
Det finns flera färdiga/halvfärdiga pointcut implementationer men det är även lätt att själv implementera interfacet.
Pointcut interfacet har två metoder, getMethodMatcher samt getClassFilter. Att implementera ett classfilter är mycket enkelt, interfacet har bara en metod, matches(Class cl).
Det gör det enkelt att tex matcha på classnamn.
Methodmatcher interfacet är lite knöligare, där finns det tre metoder.
boolean matches(Method method, Class targetClass, Object[] args)
boolean matches(Method method, Class targetClass)
boolean isRuntime()
Kort kan man säga att om isRuntime returnerar true körs båda matches metoderna annars, först den kortare och om den returnerar true, även den längre. Om isRuntime() returnerar false körs endast den kortare.
Utifrån det känns det naturligt att returnera true i isRuntime för maximal valmöjlighet.
Problemet är att den längre matches metoden behöver köras för varje invokation (så länge den kortare returnerar true) men så är inte fallet för den kortare. Det är därför en bra tumregel att bara returnera true från isRuntime() om man är intresserad av parametervärdena till metoden. Den pointcut vi hittills använt, RegexpMethodPointcutAdvisor, är inte intresserad av parametervärdena till metoden och utvecklarna av Spring har därför hårdkodat isRuntime att returnera false.
Mer om Advice
Hittills har vi definierat en pointcut som definierat metoder av intresse och använt oss av ett advice som körts före den/dessa metoder.
Förutom att använda MethodBeforeAdvice för att exekvera före ett metodanrop finns det andra typer av advice, ett ex är MethodInterceptor. Detta advice tillåter att man exekverar före och efter en metod. Det går bra att helt hindra en metod från att exekvera eller att manipulera returvärdet.