(笔记)Spring实战_面向切面的Spring(3)_在XML中声明切面

来源:互联网 发布:两位数相乘最快算法 编辑:程序博客网 时间:2024/04/27 15:32
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 moreCaused 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>
0 0
原创粉丝点击