AOP配置元素 | 描述 |
---|---|
<aop:advisor> | 定义AOP通知器 |
<aop:after> | 定义AOP后置通知(不管被通知的方法是否执行成功) |
<aop:after-returning> | 定义AOP after-returning通知 |
<aop:after-throwing> | 定义AOP after-throwing通知 |
<aop:around> | 定义AOP环绕通知 |
<aop:aspect> | 定义切面 |
<aop:aspectj-autoproxy> | 启用@AspectJ注解驱动的切面 |
<aop:before> | 定义AOP前置通知 |
<aop:config> | 顶层的AOP配置元素。大多数的<aop:*> 元素必须包含在<aop:config> 元素内 |
<aop:declare-parents> | 为被通知的对象引入额外的接口,并透明地实现 |
<aop:pointcut> | 定义切点 |
为选秀节目创建一个观众类
package com.springinaction.springidol;
public class Audience
{
// 表演之前
public void takeSeats()
{
System.out.println("The audience is taking their seats.");
}
// 表演之前
public void turnOffCellPhones()
{
System.out.println("The audience is turning off their cellphones.");
}
// 表演之后
public void applaud()
{
System.out.println("CLAP CLAP CLAP CLAP");
}
// 表演失败之后
public void demandRefund()
{
System.out.println("Boo!We want our money back!");
}
}
<bean id="audience" class="com.springinaction.springidol.Audience" />
1.声明前置和后置通知
<aop:config>
<aop:aspect ref="audience">
<aop:before
pointcut="execution(* com.springinaction.springidol.Performer.perform(..))"
method="takeSeats" />
<aop:before
pointcut="execution(* com.springinaction.springidol.Performer.perform(..))"
method="turnOffCellPhones" />
<aop:after-returning
pointcut="execution(* com.springinaction.springidol.Performer.perform(..))"
method="applaud" />
<aop:after-throwing
pointcut="execution(* com.springinaction.springidol.Performer.perform(..))"
method="demandRefund" />
</aop:aspect>
</aop:config>
关于Spring AOP配置元素,第一个需要注意的事项是大多数的AOP配置元素必须在<aop:config>
元素的上下文内使用。
为了避免重复定义切点,可以使用<aop:pointcut>
元素定义一个命名切点。
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut
expression="execution(* com.springinaction.springidol.Performer.perform(..))"
id="performance" />
<aop:before pointcut-ref="performance" method="takeSeats" />
<aop:before pointcut-ref="performance" method="turnOffCellPhones" />
<aop:after-returning pointcut-ref="performance"
method="applaud" />
<aop:after-throwing pointcut-ref="performance"
method="demandRefund" />
</aop:aspect>
</aop:config>
<aop:pointcut>
元素所定义的切点可以被同一个<aop:aspect>
元素之内的所有通知元素所引用。如果想让定义的切点能够在多个切面使用,可以把<aop:pointcut>
元素放在<aop:config>
元素的作用域内。
2.声明环绕通知
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.2.17.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
记录开始时间、结束时间
public void watchPerformance(ProceedingJoinPoint joinpoint)
{
try
{
// 表演之前
System.out.println("The audience is taking their seats.");
System.out.println("The audienct is turning off their cellphones.");
long start = System.currentTimeMillis();
joinpoint.proceed();// 执行被通知的方法
// 表演之后
long end = System.currentTimeMillis();
System.out.println("CLAP CLAP CLAP CLAP");
System.out.println("The performance took " + (end - start) + " milliseconds.");
}
catch (Throwable e)
{
System.out.println("Boo!We want out money back!");// 表演失败之后
}
}
ProceedingJoinPoint能让我们在通知里调用被通知方法。
更有意思的是,正如我们可以忽略调用proceed()方法来阻止执行被通知的方法,我们还可以在通知里多次调用被通知的方法。
<aop:around pointcut-ref="performance" method="watchPerformance" />
调试:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'carl' defined in class path resource [com/springinaction/springidol/spring-idol.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 6): Property or field 'song' cannot be found on object of type 'com.sun.proxy.$Proxy4' - maybe not public?
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:191)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:636)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:938)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at com.springinaction.springtest.Demo1.test5(Demo1.java:52)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 6): Property or field 'song' cannot be found on object of type 'com.sun.proxy.$Proxy4' - maybe not public?
at org.springframework.context.expression.StandardBeanExpressionResolver.evaluate(StandardBeanExpressionResolver.java:142)
at org.springframework.beans.factory.support.AbstractBeanFactory.evaluateBeanDefinitionString(AbstractBeanFactory.java:1315)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.evaluate(BeanDefinitionValueResolver.java:214)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:186)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1419)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1160)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
... 34 more
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 6): Property or field 'song' cannot be found on object of type 'com.sun.proxy.$Proxy4' - maybe not public?
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:211)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:85)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.access$000(PropertyOrFieldReference.java:43)
at org.springframework.expression.spel.ast.PropertyOrFieldReference$AccessorLValue.getValue(PropertyOrFieldReference.java:338)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:82)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:93)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:89)
at org.springframework.context.expression.StandardBeanExpressionResolver.evaluate(StandardBeanExpressionResolver.java:139)
... 40 more
<!-- <property name="song" value="#{kenny.song}" /> -->
3.为通知传递参数
有时候通知并不仅仅是对方法进行简单包装,还需要校验传递给方法的参数值,这时候为通知传递参数就非常有用了。
读心者
package com.springinaction.springidol;
public interface MindReader
{
void interceptThoughts(String thoughts);
String getThoughts();
}
package com.springinaction.springidol;
public class Magician implements MindReader
{
private String thoughts;
public void interceptThoughts(String thoughts)
{
System.out.println("Intercepting volunteer's thoughts");
this.thoughts = thoughts;
}
public String getThoughts()
{
return thoughts;
}
}
志愿者
package com.springinaction.springidol;
public interface Thinker
{
void thinkOfSomething(String thoughts);
}
package com.springinaction.springidol;
public class Volunteer implements Thinker
{
private String thoughts;
public void thinkOfSomething(String thoughts)
{
this.thoughts = thoughts;
}
public String getThoughts()
{
return thoughts;
}
}
spring-idol.xml
<bean id="magician" class="com.springinaction.springidol.Magician" />
<aop:config>
<aop:aspect ref="magician">
<aop:pointcut
expression="execution(* com.springinaction.springidol.Thinker.thinkOfSomething(String) and args(thoughts))"
id="thinking" />
<aop:before pointcut-ref="thinking" method="interceptThoughts"
arg-names="thoughts" />
</aop:aspect>
</aop:config>
切点标识了Thinker的thinkOfSomething()方法,指定了String参数。然后再args参数中标识了将thoughts作为参数。
同样,<aop:before>
元素引用了thoughts参数,标识该参数必须传递给Magician的interceptThoughts()方法。
Test Demo
package com.springinaction.springtest;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.springinaction.springidol.MindReader;
import com.springinaction.springidol.Thinker;
public class Demo10
{
@Test
public void test()
{
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/springinaction/springidol/spring-idol.xml");
Thinker volunteer = (Thinker)ctx.getBean("volunteer");
volunteer.thinkOfSomething("Hello World!");
MindReader magician = (MindReader)ctx.getBean("magician");
assertEquals("Hello World!", magician.getThoughts());
}
}
4.通过切面引入新功能
回顾一下,切面只是实现了它们所包装Bean的相同接口的代理。如果除了实现这些接口,代理还能发布新接口的话,切面所通知的Bean看起来实现了新的接口,即便底层实现类并没有实现这些接口。
package com.springinaction.springidol;
public interface Contestant
{
void receiveAward();
}
package com.springinaction.springidol;
public class GraciousContestant implements Contestant
{
public void receiveAward()
{
System.out.println("Get $100000 award.");
}
}
<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="com.springinaction.springidol.Performer"
implement-interface="com.springinaction.springidol.Contestant"
default-impl="com.springinaction.springidol.GraciousContestant" />
</aop:aspect>
</aop:config>
<aop:declare-parents>
声明了此切面所通知的Bean在它的对象层次结构中拥有新的父类型。
类型匹配Performer接口(由types-matching属性指定)的那些Bean会实现Contestant接口(由implement-interface属性指定)。
这里有两种方式标识所引入接口的实现。使用default-impl属性通过它的全限定类名来显式指定Contestant的实现。或者,我们还可以使用delegate-ref属性来标识。
<bean id="contestantDelegate" class="com.springinaction.springidol.GraciousContestant" />
<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="com.springinaction.springidol.Performer"
implement-interface="com.springinaction.springidol.Contestant"
delegate-ref="contestantDelegate" />
</aop:aspect>
</aop:config>