3.2 AspectJ开发
AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring 2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。新版本的Spring框架建议使用AspectJ来开发AOP。
使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ;另一种是基于注解的声明式AspectJ。本节将对这两种AspectJ的开发方式进行讲解。
3.2.1 基于XML的声明式AspectJ
基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。Spring配置文件中的<beans>元素下可以包含多个<aop:config>元素,一个<aop:config>元素中又可以包含属性和子元素,其子元素包括<aop:pointcut>、<aop:advisor>和<aop:aspect>。在配置时,这3个子元素必须按照此顺序来定义。在<aop:aspect>元素下,同样包含属性和多个子元素,通过使用<aop:aspect>元素及其子元素就可以在XML文件中配置切面、切入点和通知。常用元素的配置代码如下所示。
为了让读者能够清楚地掌握上述代码中的配置信息,下面对上述代码的配置内容进行详细讲解。
1.配置切面
在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean(如上述代码中定义的myAspect)。定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean。
配置<aop:aspect>元素时,通常会指定id和ref两个属性,如表3.1所示。
表3.1 <aop:aspect>元素的属性及其描述
2.配置切入点
在Spring的配置文件中,切入点是通过<aop:pointcut>元素来定义的。当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,可以被多个切面所共享;当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。在定义<aop:pointcut>元素时,通常会指定id和expression两个属性,如表3.2所示。
表3.2 <aop:pointcut>元素的属性及其描述
在上述配置代码片段中,execution(* com.ssm.jdk.*.*(..))就是定义的切入点表达式,该切入点表达式的意思是匹配com.ssm.jdk包中任意类的任意方法的执行。其中execution是表达式的主体,第1个*表示的是返回类型,使用*代表所有类型;com.ssm.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.ssm.jdk.UserDaoImpl。
- name-pattern:表示具体需要被代理的目标方法,如add()方法。
- param-pattern:表示需要被代理的目标方法包含的参数,本章示例中目标方法参数都为空。
- throws- pattern:表示需要被代理的目标方法抛出的异常类型。
提示
带有问号(?)的部分(如modifiers-pattern、declaring-type-pattern和throws-pattern)表示可选配置项,其他部分属于必须配置项。
想要了解更多切入点表达式的配置信息,读者可以参考Spring官方文档的切入点声明部分(Declaring a pointcut)。
3.配置通知
在配置代码中,分别使用<aop:aspect>的子元素配置了5种常用通知,这些子元素不支持再使用子元素,但在使用时可以指定一些属性,如表3.3所示。
表3.3 通知的常用属性及其描述
【示例3-1】了解了如何在XML中配置切面、切入点和通知后,接下来通过一个案例来演示如何在Spring中使用基于XML的声明式AspectJ,具体实现步骤如下。
(1)创建一个名为chapter03的动态Web项目,导入Spring构架所需求的JAR包到项目的lib目录中,并发布到类路径下。同时,导入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.ssm.aspectj包,在该包中创建接口UserDao,并在接口中编写添加和删除的方法,如文件3.1所示。
文件3.1 UserDao.java
(3)在com.ssm.aspectj包中创建UserDao接口的实现类UserDaoImpl,该类需要实现接口中的方法,如文件3.2所示。
文件3.2 UserDaoImpl.java
本案例中将实现类UserDaoImpl作为目标类,对其中的方法进行增强处理。
(4)在chapter03项目的src目录下创建一个com.ssm.aspectj.xml包,在该包中创建切面类MyAspect,并在类中分别定义不同类型的通知,如文件3.3所示。
文件3.3 MyAspect.java
在文件3.3中,分别定义了5种不同类型的通知,在通知中使用了JoinPoint接口及其子接口ProceedingJoinPoint作为参数来获得目标对象的类名、目标方法名和目标方法参数等。
注意
环绕通知必须接收一个类型为ProceedingJoinPoint的参数,返回值也必须是Object类型,且必须抛出异常。异常通知中可以传入Throwable类型的参数来输出异常信息。
(5)在com.ssm.aspectj.xml包中创建配置文件applicationContext.xml,并编写相关配置,如文件3.4所示。
文件3.4 applicationContext.xml
注意
在AOP的配置信息中,使用<aop:after-returning>配置的后置通知和使用<aop:after>配置的最终通知虽然都是在目标方法执行之后执行,但它们是有区别的。后置通知只有在目标方法成功执行后才会被植入,而最终通知不论目标方法如何结束(包括成功执行和异常中止两种情况),它都会被植入。另外,如果程序没有异常,异常通知将不会执行。
(6)在com.ssm.aspectj.xml包下创建测试类TestXmlAspectJ,在类中为了更加清晰地演示几种通知的执行情况,这里只对addUser()方法进行增强测试,如文件3.5所示。
文件3.5 TestXmlAspectJ.java
执行程序后,控制台的输出结果如图3.1所示。
图3.1 运行结果1
要查看异常通知的执行效果,可以在UserDaoImpl类的addUser()方法中添加出错代码,如“int i=10/0;”。重新运行测试类,将可以看到异常通知的执行,此时控制台的输出结果如图3.2所示。
图3.2 运行结果2
从图3.1和图3.2可以看出,使用基于XML的声明式AspectJ已经实现了AOP开发。
3.2.2 基于注解的声明式AspectJ
基于XML的声明式AspectJ实现AOP编程虽然便捷,但是存在一些缺点,那就是要在Spring文件中配置大量的代码信息。为了解决这个问题,AspectJ框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。
关于AspectJ注解的介绍如表3.4所示。
表3.4 AspectJ的注解及其描述
【示例3-2】为了使读者可以快速地掌握这些注解,接下来重新使用注解的形式实现3.2.1小节的案例,具体步骤如下。
(1)在chapter03项目的src目录下创建com.ssm.aspectj.annotation包,将文件3.3的切面类MyAspect复制到该包下,并对该文件进行修改,如文件3.6所示。
文件3.6 MyAspect.java
在文件3.6中,首先使用@Aspect注解定义了切面类,由于该类在Spring中是作为组件使用的,因此还需要添加@Component注解才能生效。然后使用@Pointcut注解来配置切入表达式,并通过定义方法来表示切入点名称。接下来在每个通知相应的方法上添加了相应的注解,并将切入点名称“myPointcut”作为参数传递给需要执行增强的通知方法。如果需要其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。
(2)在目标类com.ssm.aspectj.UserDaoImpl中添加注解@Repository("userDao")。
(3)在com.ssm.aspectj.annotation包下创建配置文件applicationContext.xml,并对该文件进行编辑,如文件3.7所示。
文件3.7 applicationContext.xml
在文件3.7中,首先引入了context约束信息,然后使用<context>元素设置了需要扫描的包,使注解生效。由于此案例中的目标类位于com.ssm.aspectj包中,因此这里设置base-package的值为“com.ssm"。最后,使用<aop.aspectj-autoproxy/>来启动Spring对基于注解的声明式Aspect的支持。
(4)在com.ssm.aspectj.annotation包中创建测试类TestAnnotation,该类与文件3.5基本一致,只是配置文件的路径有所不同,如文件3.8所示。
文件3.8 TestAnnotation.java
执行程序后,控制台的输出结果如图3.3所示。
图3.3 运行结果
在UserDaoImpl类的addUser()方法中加上出错代码来演示异常通知的执行,控制台的输出结果如图3.3所示。
从图3.2和图3.3可以看出,基于注解的方式与基于XML的方式执行结果相同,只是在目标方法前后通知的执行顺序发生了变化。相对来说,使用注解的方式更加简单、方便,所以在实际开发中推荐使用注解的方式进行AOP开发。
注意
如果在同一个连接点有多个通知需要执行,那么在同一切面中,目标方法之前的前置通知和环绕通知的执行顺序是未知的,目标方法之后的后置通知和环绕通知的执行顺序也是未知的。