• Spring AOP-xml配置


    spring AOP(一)中介绍了AOP的基本概念和几个术语,现在学习一下在XML中如何配置AOP。 
    在XML中AOP的配置元素有以下几种:

    AOP配置元素描述
    <aop:config> 顶层的AOP配置元素,大多数的<aop:*>元素必须包含在<aop:config>元素内
    <aop:aspect> 定义切面
    <aop:aspect-autoproxy> 启用@AspectJ注解驱动的切面
    <aop:pointcut> 定义切点
    <aop:advisor> 定义AOP通知器
    <aop:before> 定义AOP前置通知
    <aop:after> 定义AOP后置通知(不管被通知的方法是否执行成功)
    <aop:after-returning> 定义成功返回后的通知
    <aop:after-throwing> 定义抛出异常后的通知
    <aop:around> 定义AOP环绕通知
    <aop:declare-parents> 为被通知的对象引入额外的接口,并透明地实现

    总之,个人理解AOP就是将切面的功能(通知),通过切点织入程序的执行过程中。下面是AOP术语的配置和关系图:

    这里写图片描述

    下面通过一个具体的栗子来学习,这个例子描述的是在一个正在表演的节目(相当于应用中一个正常的业务功能)中加入观众们的反响(相当于切面),要求在表演前观众们入座、手机关机或静音,表演中记录一下表演所用时间,表演后观众们喝彩或不满。

    表演类接口

    package com.springinaction.aop;
    
    public interface Performer {
        public void perform();
    }

    表演类的一个实现:话剧演出

    package com.springinaction.aop;
    
    public class Drama implements Performer {
    
        @Override
        public void perform() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("话剧正在进行中——");
            }
        }
    }

    观众类作为切面

    package com.springinaction.aop;
    
    public class Audience {
        public void takeSeats() {
            // 节目开始之前
            System.out.println("演出前——观众开始入座");
        }
    
        public void turnOffCellPhones() {
            // 节目开始之前
            System.out.println("演出前——观众关机或静音");
        }
    
        public void applaud() {
            // 节目成功结束之后
            System.out.println("成功演出很成功——观众鼓掌:啪啪啪");
        }
    
        public void demandRefund() {
            // 节目表演失败之后
            System.out.println("节目演出很失败——切!一点都不好看,我们要求退钱!");
        }
    }

    1 准备工作

    • 引入以下几个AOP包: 
      aopalliance-1.0.jar 
      aspectjrt-1.7.4.jar 
      aspectjweaver-1.7.4.jar

    • 在XML文件中加入包含关于Spring AOP命名空间的内容

      <?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"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
      
      </beans>

    2 定义切面

    在XML中通过元素<aop:config>开启AOP配置,使用<aop:aspect>配置切面,如下:

         <aop:config>
            <aop:aspect ref="audience">
            </aop:aspect>
         </aop:config>

    ref里面对应audience是切面bean的ID,也就是观众类对应的bean。观众类的bean和话剧表演的bean分别如下:

        <bean id="audience" class="com.springinaction.aop.Audience" />
        <bean id="drama" class="com.springinaction.aop.Drama" />

    3 定义切点

    3.1 AspectJ切点表达式语言

    Spring AOP中使用的是AspectJ的切点表达式语言来定义的切点,前面讲过Spring AOP是基于代理的,而AspectJ的某些切点表达式是与基于代理的AOP无关的,因此Spring AOP支持的AspectJ切点指示器仅有有限的几种,如下表所示:

    AspectJ指示器描述
    execution{} 匹配到的连接点的执行方法
    如:execution(* com.springinaction.aop.Performer.perform(..))
    表达式表示当Performer的perform()方法执行时会触发通知, * 表明我们不关心方法返回值的类型, (..) 标识切点选择任意的paly()方法,无论该方法的参数是什么
    args() 限制连接点匹配参数为指定类型的执行方法
    如:execution(* com.springinaction.aop.Thinker.thinkOfSomething(String)) and args(thoughts)
    注意到 thinkOfSomething(String) 有参数类型String,后面跟了一个 args(thoughts) ,这个是 thinkOfSomething() 方法要传给通知的参数,两个AspectJ知识器用and相连接,同样也可以使用or和not,当然我们可以使用&&、 || 和 ! 来代替and、or和not
    @args() 限制连接点匹配参数由制定注解标注的执行方法
    this() 限制连接点匹配AOP代理的Bean引用为指定类型的类
    target() 限制连接点匹配目标对象为制定类型的类
    within() 限制连接点匹配特定的类型
    如:execution(* com.springinaction.aop.Performer.perform(..)) && within(com.springinaction.aop.*)
    within(com.springinaction.aop.*)表示com.springinaction.aop包下任意类的方法被调用时
    @within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
    @annotation 限制匹配带有指定注解连接点
    bean() 限制连接点只匹配特定的bean
    如:execution(* com.springinaction.aop.Performer.perform(..)) adn bean(drama)
    其中drama是一个类的beanID,这个bean是Performer接口的一个实现。加上了bean(drama)以后就只能匹配这个bean了

    上面的所有指示器中,execution是主要使用的,是必须的;其他的指示器都是限制所匹配的切点。另外,当Spring尝试使用除了上面的AspectJ的其他指示器时,将会抛出IllegalArgumentException异常。

    3.2 在XML中配置切点

    使用元素<aop:pointcut>来配置切点,<aop:pointcut>既可以放在<aop:aspect>元素的作用域内,也可以放在<aop:config>元素的作用域下,对应的切点的作用范围就不同了。具体配置如下:

                <aop:pointcut 
                    id="performance"
                    expression="execution(* com.springinaction.aop.Performer.perform(..))" />

    id标识这个切点的id,expression里面就是上面讲到的AspectJ切点表达式语言。

    4 定义通知

    4.1 声明前置和后置通知

    分别使用<aop:before><aop:after-returning>以及<aop:after-throwing>元素来声明前置通知、返回结果后置通知和抛出异常后置通知,注意这几个元素是放在<aop:aspect>元素的作用域内。具体配置如下:

                <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"/>

    该切面应用了4个不同的通知。两个<aop:before>元素定义了匹配切点的方法执行之前调用前置通知方法——audience Bean的takeSeats()和turnOffCellPhones()方法(由method属性所声明)。<aop:after-returning>元素定义了一个返回后(after-returning)通知,在切点所匹配的方法调用之后再执行applaud()方法。同样,<aop:after-throwing>元素定义了抛出后通知,如果所匹配的方式执行时抛出任何异常,都将调用demandRefund()方法。下图展示了通知逻辑如何编织到业务逻辑中。

    这里写图片描述

    下面我们用测试类来测试AOP实现

    package com.springinaction.aop;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class AudienceTest {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext(
            "com/springinaction/aop/aop.xml");
            Performer drama = (Performer)ctx.getBean("drama");
            drama.perform();
        }
    }

    结果

    演出前——观众开始入座
    演出前——观众关机或静音
    话剧正在进行中——
    ......(重复一千次)
    成功演出很成功——观众鼓掌:啪啪啪

    4.2 声明环绕通知

    前面的前置通知和后置通知分别发生在业务功能的前面和后面,如果想要实现跨越业务功能的整个事件段呢?例如表演节目除了进场关机和结束了鼓掌,我们还希望观众一直关注演出,报告演出的时间。如果通过前置和后置通知来实现的话,我们可以在一个成员变量中保存时间。但是由于Audience是单例,所以会存在线程安全问题。 
    环绕通知的作用在这里就体现出来了,它可以在一个方法中实现整个逻辑,即将切点的方法内嵌到环绕通知的方法中去。

    4.2.1 编写环绕通知方法

    下面是我们在观众类Audience中新添加的一个watchPerformance()方法作为AOP环绕通知。

        public void watchPerformance(ProceedingJoinPoint joinpoint) {
            try {
                System.out.println("演出前——观众开始入座");
                System.out.println("演出前——观众关机或静音");
                long start = System.currentTimeMillis();
    
                joinpoint.proceed(); // 执行被通知的方法
    
                long end = System.currentTimeMillis();
    
                System.out.println("成功演出很成功——观众鼓掌:啪啪啪");
                System.out.println("演出持续了 " + (end - start) + " milliseconds");
            } catch (Throwable e) {
                System.out.println("节目演出很失败——切!一点都不好看,我们要求退钱!");
            }
        }

    对于这个通知方法,注意它使用了ProceedingJoinPoint作为方法的入参。这个对象能让我们在通知里调用被通知方法。通知方法首先完成它需要做的事情,如果希望把控制权转给被通知的方法,我们可以调用ProceedingJoinPoint的proceed()方法。

    4.2.2 在XML声明环绕通知

    声明环绕通知与声明其他类型的通知没有太大的区别,我们需要做的仅仅是使用<aop:around>元素。

                <aop:around pointcut-ref="performance" method="watchPerformance"/>

    然后我们通过测试类测试一下

    package com.springinaction.aop;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class AudienceTest {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext(
            "com/springinaction/aop/aop.xml");
            Performer drama = (Performer)ctx.getBean("drama");
            drama.perform();
        }
    }

    输出结果:

    演出前——观众开始入座
    演出前——观众关机或静音
    话剧正在进行中——
    ......(重复1000次)
    演出很成功——观众鼓掌:啪啪啪
    演出持续了 42 milliseconds

    4.3 为通知传递参数

    上面的栗子通知和被通知的方法之间虽然实现了有了先后逻辑,但是并没有参数的传递,有时候我们需要校验传递给方法的参数值,这个时候为通知传递参数就非常有用了。 
    还是通过一个实例来说明:某位魔术师自称自己继承了其师傅刘半仙的衣钵,可以未卜先知知道他人内心所想;有一个志愿者不服,说要让他猜猜自己到底想了什么。这里就将魔术师作为切面。

    读心术接口

    package com.springinaction.aop;
    
    public interface MindReader {
        void interceptThoughts(String thoughts);
    
        String getThoughts();
    }

    魔术师类实现读心术接口

    package com.springinaction.aop;
    
    public class Magician implements MindReader {
        private String thoughts;
    
        @Override
        public void interceptThoughts(String thoughts) {
            System.out.println("让我猜猜你在想什么?");
            this.thoughts = thoughts;
            System.out.println(this.getThoughts());
        }
    
        @Override
        public String getThoughts() {
            System.out.print("你在想:");
            return thoughts;
        }
    
    }

    思考者接口

    package com.springinaction.aop;
    
    public interface Thinker {
        void thinkOfSomething(String thoughts);
    }

    志愿者类实现思考者接口

    package com.springinaction.aop;
    
    public class Volunteer implements Thinker {
        private String thoughts;
    
        @Override
        public void thinkOfSomething(String thoughts) {
            this.thoughts = thoughts;
        }
    
        public String getThoughts() {
            return thoughts;
        }
    }

    XML配置

        <aop:config>
            <aop:aspect ref="magician">
                <aop:pointcut 
                    id="thinker" 
                    expression="execution(* com.springinaction.aop.Thinker.thinkOfSomething(String)) 
                                   and args(thoughts)"/>
    
                <aop:before 
                    pointcut-ref="thinker" 
                    method="interceptThoughts"
                    arg-names="thoughts"/>
            </aop:aspect>
        </aop:config>

    从XML配置中我们可以看出,实现被通知方法向通知传参的关键有两点:

    1. 切点元素<aop:pointcut>的expression属性中使用了AspectJ切点表达式语言中的args()指示器,在本例中是args(thoughts),传递的是thinkOfSomething(String thoughts)方法的参数。
    2. 通知元素<aop:before>中加入了 arg-names 属性,这个属性的值就是切点传过来的对应的参数thoughts。

    测试类

    package com.springinaction.aop;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MagicianTest {
    
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext(
            "com/springinaction/aop/aop.xml");
            Thinker thinker = (Thinker) ctx.getBean("volunteer");
            thinker.thinkOfSomething("你猜我在想什么?");
        }
    
    }

    输出结果

    让我猜猜你在想什么?
    你在想:你猜我在想什么?
    • 1
    • 2
    • 1
    • 2

    5 定义引入

    5.1 通过切面引入的原理

    讲解引入之前先思考一下我们之前都做到了什么。我们已经通过AOP实现了为目标对象拥有的方法添加了新的功能,这是通过代理实现的,即切面为目标bean创建一个代理,通知发出的方法调用看似发往了目标bean,其实是被代理拦截了,即代理代替目标bean实现原有功能并添加了切面的新功能。 
    现在来说引入,什么是引入呢?就是在不改变切入的目标类的基础上为其添加新方法或属性。我们都知道Java不是动态语言,一旦类编译完成了,我们就很难再为该类添加新的功能了,那么所谓的“添加新方法或属性”是不是扯淡呢?是也不是,因为我们无法真的实现可以虚拟一个嘛,但你不说我不说谁知道呢,这就又用到了我们牛逼轰轰的代理。如下图所示:

    这里写图片描述

    这里,可以让代理发布一个新的接口,当引入接口的方法被调用时,代理就将此调用委托给了实现新接口的某个其他对象,也就是Bean的实现被拆分到了多个类,而不仅仅是原来的目标切点对应的那一个类。但是在外面看来,所有的功能都是通过切点对应的目标类的Bean实现的。

    5.2 XML配置引入

    还是通过一个实例来说明:前面的例子中表演类是一个切点,这里我们加入一个新的辅助表演类,来引入我们想要添加给表演类的方法。

    辅助表演接口

    package com.springinaction.aop;
    
    public interface AssistPerformer {
        void assist();
    }

    助人为乐者实现辅助表演接口

    package com.springinaction.aop;
    
    public class GraciousAssist implements AssistPerformer {
    
        @Override
        public void assist() {
            System.out.println("GraciousAssist来助演了——");
        }
    
    }

    XML配置 
    引入是通过<aop:declare-parents>元素来实现的,这个元素在<aop:aspect>元素的作用域内,它声明了此切面所通知的Bean在它的对象层次结构中拥有新的父类,也就是它的代理拥有了新的父类。具体配置如下:

                <aop:declare-parents 
                    types-matching="com.springinaction.aop.Performer+" 
                    implement-interface="com.springinaction.aop.AssistPerformer"
                    default-impl="com.springinaction.aop.GraciousAssist"/>

    具体说明一下<aop:declare-parents>的属性:

    • types-matching:Performer接口所实现的子类,也就是对应的目标切点类。
    • implement-interface:新加入的接口类。
    • default-impl:新加入的接口的默认实现类。

    除了使用default-impl来指定接口的实现外,还可以使用delegate-ref属性来标识:

                <aop:declare-parents 
                    types-matching="com.springinaction.aop.Performer+" 
                    implement-interface="com.springinaction.aop.AssistPerformer"
                    delegate-ref="gracious"/>

    其中“gracious”是新添加的接口的实现类对应Bean的ID:

        <bean id="gracious" class="com.springinaction.aop.GraciousAssist" />

    测试类

    package com.springinaction.aop;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class AudienceTest {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext(
            "com/springinaction/aop/aop.xml");
            Performer drama = (Performer)ctx.getBean("drama");
            drama.perform();
            AssistPerformer ap = (AssistPerformer) ctx.getBean("drama");
            ap.assist();
        }
    }

    注意一点,我们要实现的是新加入的AssistPerformer 接口的assist()方法,但是我们是通过切点的BeanID,也就是”drama“得到AssistPerformer 的实现的一个实例,这就仿佛是”drama“对应的切点Bean实现了AssistPerformer 接口一样,这就是AOP引入的神奇之处,可以为目标类添加新的方法。

    输出结果

    演出前——观众开始入座
    演出前——观众关机或静音
    话剧正在进行中——
    ......(重复1000次)
    演出很成功——观众鼓掌:啪啪啪
    演出持续了 42 milliseconds
    GraciousAssist来助演了——
  • 相关阅读:
    C 指针运算 指针访问数组
    C 字符串操作函数
    C putchar getchar
    C语言 指向函数的指针变量基础
    Openstack L2GW Plugin installation
    nova + ironic node
    cgroup
    ironic pxe tftp(二)Permission denied
    ironic bind port neutron port
    Failed to start OpenBSD Secure Shell server
  • 原文地址:https://www.cnblogs.com/danqiu/p/6179109.html
Copyright © 2020-2023  润新知