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

本文介绍了Spring AOP的配置元素及应用案例,包括前置、后置通知、环绕通知的定义和使用,为通知方法传递参数的方法,以及如何通过切面为被通知对象引入新的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值