学科分类
目录
SSM框架

基于XML的声明式AspectJ

基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。<aop:config>元素及其子元素如图1所示。

图1 <aop:config>元素及其子元素

在图1中,Spring配置文件中的<beans>元素下可以包含多个<aop:config>元素,一个<aop:config>元素中又可以包含属性和子元素,其子元素包括<aop:pointcut>、<aop:advisor>和<aop:aspect> 。在配置时,这3个子元素必须按照此顺序来定义。在<aop:aspect>元素下,同样包含了属性和多个子元素,通过使用<aop:aspect>元素及其子元素就可以在XML文件中配置切面、切入点和通知。图中灰色部分标注的元素即为常用的配置元素,这些常用元素的配置代码如下所示。

<!-- 定义切面Bean -->
<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
<aop:config>
    <!--1.配置切面 -->
    <aop:aspect  id="aspect"  ref="myAspect">
        <!--  2.配置切入点 -->
        <aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))"
                                                      id="myPointCut" />
        <!-- 3.配置通知 -->
        <!-- 前置通知 -->
        <aop:before method="myBefore" pointcut-ref="myPointCut" />
        <!-- 后置通知 -->
        <aop:after-returning method="myAfterReturning"
                  pointcut-ref="myPointCut" returning="returnVal" />
        <!-- 环绕通知 -->
        <aop:around method="myAround" pointcut-ref="myPointCut" />
        <!-- 异常通知 -->
        <aop:after-throwing method="myAfterThrowing"
                  pointcut-ref="myPointCut" throwing="e" />
        <!-- 最终通知 -->
        <aop:after method="myAfter" pointcut-ref="myPointCut" />
    </aop:aspect>
</aop:config>

为了让读者能够清楚的掌握上述代码中的配置信息,下面对上述代码的配置内容进行详细讲解。

1.配置切面

在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean(如上述代码中定义的myAspect)。定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean。

配置<aop:aspect>元素时,通常会指定id和ref两个属性,如表1所示。

表1 <aop:aspect>元素的属性及其描述

属性名称 描述
id 用于定义该切面的唯一标识名称
ref 用于引用普通的Spring Bean

2.配置切入点

在Spring的配置文件中,切入点是通过<aop:pointcut>元素来定义的。当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。

在定义<aop:pointcut>元素时,通常会指定id和expression两个属性,如表2所示。

表2 <aop:pointcut>元素的属性及其描述

属性名称 描述
id 用于指定切入点的唯一标识名称
expression 用于指定切入点关联的切入点表达式

在上述配置代码片段中,execution(* com.itheima.jdk..(..))就是定义的切入点表达式,该切入点表达式的意思是匹配com.itheima.jdk包中任意类的任意方法的执行。其中execution()是表达式的主体,第1个表示的是返回类型,使用代表所有类型;com.itheima.jdk表示的是需要拦截的包名,后面第2个表示的是类名,使用代表所有的类;第3个表示的是方法名,使用表示所有方法;后面(..)表示方法的参数,其中的“..”表示任意参数。需要注意的是,第1个*与包名之间有一个空格。

上面示例中定义的切入点表达式只是开发中常用的配置方式,而Spring AOP中切入点表达式的基本格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern) throws-pattern?)

上述格式中,各部分说明如下:

● modifiers-pattern:表示定义的目标方法的访问修饰符,如public、private等;

● ret-type-pattern:表示定义的目标方法的返回值类型,如void、String等;

● declaring-type-pattern:表示定义的目标方法的类路径,如com.itheima.jdk.UserDaoImpl;

● name-pattern:表示具体需要被代理的目标方法,如add()方法;

● param-pattern:表示需要被代理的目标方法包含的参数,本章示例中目标方法参数都为空;

● throws-pattern:表示需要被代理的目标方法抛出的异常类型;

其中带有问号(?)的部分,如modifiers-pattern、declaring-type-pattern和throws-pattern表示可配置项;而其他部分属于必须配置项。

想要了解更多切点表达式的配置信息,读者可以参考Spring官方文档的切入点声明部分(Declaring a pointcut)。

3.配置通知

在配置代码中,分别使用<aop:aspect>的子元素配置了5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性,如表3所示。

表3 通知的常用属性及其描述

属性名称 描述
pointcut 该属性用于指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入该通知。
pointcut-ref 该属性指定一个已经存在的切入点名称,如配置代码中的myPointCut。通常pointcut和pointcut-ref两个属性只需要使用其中之一。
method 该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理。
throwing 该属性只对<after-throwing>元素有效,它用于指定一个形参名,异常通知方法可以通过该形参访问目标方法所抛出的异常。
returning 该属性只对<after-returning>元素有效,它用于指定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值。

了解了如何在XML中配置切面、切入点和通知后,接下来通过一个案例来演示如何在Spring中使用基于XML的声明式AspectJ,具体实现步骤如下。

(1)导入AspectJ框架相关的JAR包,具体如下:

● spring-aspects-4.3.6.RELEASE.jar:Spring为AspectJ提供的实现,Spring的包中已经提供。

● aspectjweaver-1.8.10.jar:是AspectJ框架所提供的规范,读者可以通过网址“http://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.8.10”下载。

(2)在chapter03项目的src目录下,创建一个com.itheima.aspectj.xml包,在该包中创建切面类MyAspect,并在类中分别定义不同类型的通知,如文件1所示。

文件1 MyAspect.java

 1    package com.itheima.aspectj.xml;
 2    import org.aspectj.lang.JoinPoint;
 3    import org.aspectj.lang.ProceedingJoinPoint;
 4    /**
 5     *切面类,在此类中编写通知
 6     */
 7    public class MyAspect {
 8        // 前置通知
 9        public void myBefore(JoinPoint joinPoint) {
 10            System.out.print("前置通知 :模拟执行权限检查...,");
 11            System.out.print("目标类是:"+joinPoint.getTarget() );
 12            System.out.println(",被织入增强处理的目标方法为:"
 13                                +joinPoint.getSignature().getName());
 14        }
 15        // 后置通知
 16        public void myAfterReturning(JoinPoint joinPoint) {
 17            System.out.print("后置通知:模拟记录日志...," );
 18            System.out.println("被织入增强处理的目标方法为:"
 19                              + joinPoint.getSignature().getName());
 20        }
 21        /**
 22         * 环绕通知
 23         * ProceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法
 24         * 1.必须是Object类型的返回值
 25         * 2.必须接收一个参数,类型为ProceedingJoinPoint
 26         * 3.必须throws Throwable
 27         */
 28        public Object myAround(ProceedingJoinPoint proceedingJoinPoint) 
 29                 throws Throwable {
 30            // 开始
 31            System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
 32            // 执行当前目标方法
 33            Object obj = proceedingJoinPoint.proceed();
 34            // 结束
 35            System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
 36            return obj;
 37        }
 38        // 异常通知
 39        public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
 40            System.out.println("异常通知:" + "出错了" + e.getMessage());
 41        }
 42        // 最终通知
 43        public void myAfter() {
 44            System.out.println("最终通知:模拟方法结束后的释放资源...");
 45        }
 46    }

在文件1中,分别定义了5种不同类型的通知,在通知中使用了JoinPoint接口及其子接口ProceedingJoinPoint作为参数来获得目标对象的类名、目标方法名和目标方法参数等。

需要注意的是,环绕通知必须接收一个类型为ProceedingJoinPoint的参数,返回值也必须是Object类型,且必须抛出异常。异常通知中可以传入Throwable类型的参数来输出异常信息。

(3)在com.itheima.aspectj.xml包中,创建配置文件applicationContext.xml,并编写相关配置,如文件2所示。

文件2 applicationContext.xml

 1    <?xml version="1.0" encoding="UTF-8"?>
 2    <beans xmlns="http://www.springframework.org/schema/beans"
 3            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 4            xmlns:aop="http://www.springframework.org/schema/aop"
 5            xsi:schemaLocation="http://www.springframework.org/schema/beans 
 6            http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
 7            http://www.springframework.org/schema/aop 
 8            http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
 9        <!-- 1 目标类 -->
 10        <bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
 11        <!-- 2 切面 -->
 12        <bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
 13        <!-- 3 aop编程 -->
 14        <aop:config>
 15            <!-- 配置切面 -->
 16            <aop:aspect ref="myAspect">
 17              <!-- 3.1 配置切入点,通知最后增强哪些方法 -->
 18              <aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))"
 19                                                          id="myPointCut" />
 20                <!-- 3.2 关联通知Advice和切入点pointCut -->
 21                <!-- 3.2.1 前置通知 -->
 22                <aop:before method="myBefore" pointcut-ref="myPointCut" />
 23                <!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值
 24                 returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
 25                <aop:after-returning method="myAfterReturning"
 26                    pointcut-ref="myPointCut" returning="returnVal" />
 27                <!-- 3.2.3 环绕通知 -->
 28                <aop:around method="myAround" pointcut-ref="myPointCut" />
 29                <!-- 3.2.4 抛出通知:用于处理程序发生异常-->
 30                <!-- * 注意:如果程序没有异常,将不会执行增强 -->
 31                <!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
 32                <aop:after-throwing method="myAfterThrowing"
 33                    pointcut-ref="myPointCut" throwing="e" />
 34                <!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
 35                <aop:after method="myAfter" pointcut-ref="myPointCut" />
 36            </aop:aspect>
 37        </aop:config>
 38    </beans>

小提示:

在AOP的配置信息中,使用<aop:after-returning>配置的后置通知和使用<aop:after>配置的最终通知虽然都是在目标方法执行之后执行,但它们也是有所区别的。后置通知只有在目标方法成功执行后才会被织入,而最终通知不论目标方法如何结束(包括成功执行和异常中止两种情况),它都会被织入。

(4)在com.itheima.aspectj.xml包下,创建测试类TestXmlAspectj,在类中为了更加清晰的演示几种通知的执行情况,这里只对addUser()方法进行增强测试,如文件2所示。

文件2 TestXmlAspectj.java

 1    package com.itheima.aspectj.xml;
 2    import org.springframework.context.ApplicationContext;
 3    import 
 4        org.springframework.context.support.ClassPathXmlApplicationContext;
 5    import com.itheima.jdk.UserDao;
 6    // 测试类
 7    public class TestXmlAspectj {
 8        public static void main(String args[]) {
 9            String xmlPath = 
 10                             "com/itheima/aspectj/xml/applicationContext.xml";
 11            ApplicationContext applicationContext = 
 12                              new ClassPathXmlApplicationContext(xmlPath);
 13            // 1 从spring容器获得内容
 14            UserDao userDao = (UserDao) applicationContext.getBean("userDao");
 15            // 2 执行方法
 16            userDao.addUser();
 17        }
 18    }

执行程序后,控制台的输出结果如图1所示。

图1 运行结果

要查看异常通知的执行效果,可以在UserDaoImpl类的addUser()方法中添加错误代码,如“int i = 10/0;”,重新运行测试类,将可以看到异常通知的执行,此时控制台的输出结果如图2所示。

图2 运行结果

从图1和图2可以看出,使用基于XML的声明式AspectJ已经实现了AOP开发。

点击此处
隐藏目录