AspectJ开发

【图书介绍】《Spring+Spring MVC+MyBatis从零开始学(视频教学版)(第3版)》-优快云博客

《Spring+Spring MVC+MyBatis从零开始学(视频教学版)(第3版)》(杨章伟,刘祥淼)【摘要 书评 试读】- 京东图书

【图书介绍】案例可借鉴的Spring应用开发书-优快云博客

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文件中配置切面、切入点和通知。常用元素的配置代码如下所示:

<!-- 定义切面Bean -->
<bean id="myAspect" class="com.smm. aspectj.xmI.MyAspect />
<aop:config>
	<!-- 1.配置切面 -->
	<aop:aspect id="aspect" ref="myAspect">
		<!-- 2.配置切入点 -->
		<aop:pointcut expression="execution(* com.ssm.aspectj.*.*(..))" 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两个属性,如表3.1所示。

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

属性名称

   

id

用于定义该切面的唯一标识名称

ref

用于引用普通的Spring Bean

2. 配置切入点

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

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

属性名称

 

id

用于定义切入点的唯一标识名称

expression

用于指定切入点关联的切入点表达式

在上述配置代码片段中,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?

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

  1. modifiers-pattern:表示定义的目标方法的访问修饰符,如public、private等。
  2. ret-type-pattern:表示定义的目标方法的返回值类型,如void、String等。
  3. declaring-type-pattern:表示定义的目标方法的类路径,如com.ssm.jdk.UserDaoImpl。
  4. name-pattern:表示具体需要被代理的目标方法,如add()方法。
  5. param-pattern:表示需要被代理的目标方法包含的参数,本章示例中的目标方法参数都为空。
  6. throws- pattern:表示需要被代理的目标方法抛出的异常类型。

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

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

3. 配置通知

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

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

 创建一个名为chapter03的动态Web项目,导入Spring构架所需求的JAR包到项目的lib目录中,并发布到类路径下。同时,导入AspectJ框架相关的JAR包,具体说明如下:

  1. spring- aspects-6.1.10.jar:Spring为AspectJ提供的实现,读者可通过https://repo1.maven.org/maven2/org/springframework/spring-aspects/6.1.10/spring-aspects-6.1.10.jar下载。
  2. aspectjweaver-1.9.22.1jar:是AspectJ框架所提供的规范,读者可以通过网址https://repo1.maven.org/maven2/org/aspectj/aspectjweaver/1.9.22.1/aspectjweaver-1.9.22.1.jar下载。

 在chapter03项目的src目录下创建一个com.ssm.aspectj包,在该包中创建接口UserDao,并在接口中编写添加和删除的方法,如文件3.1所示。

文件3.1  UserDao.java

01  package com.ssm.aspectj;

02  public interface UserDao {

03      //添加用户方法

04      public void addUser();

05      //删除用户方法

06      public void deleteUser();

07  }

 在com.ssm.aspectj包中创建UserDao接口的实现类UserDaoImpl,该类需要实现接口中的方法,如文件3.2所示。

文件3.2  UserDaoImpl.java

01  package com.ssm.aspectj;

02  public class UserDaoImpl implements UserDao {

03      public void addUser() {

04          System. out. println("添加用户");

05      }

06      public void deleteUser() {

07          System.out.println("删除用户");

08      }

09  }

本示例中将实现类UserDaoImpl作为目标类,对其中的方法进行增强处理。

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

文件3.3  MyAspect.java

01	package com.ssm.aspectj.xml;
02	import org.aspectj.lang.JoinPoint;
03	import org.aspectj.lang.ProceedingJoinPoint;
04	/**
05	 * 切面类,在此类中编写通知
06	 */
07	public class MyAspect {
08		//前置通知
09		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) throws Throwable{
29			//开始
30			System.out.println("环绕开始:执行目标方法之前,模拟开启事务...,");
31			//执行当前目标方法
32			Object obj=proceedingJoinPoint.proceed();
33			//结束
34			System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...,");
35			return obj;
36		}
37		//异常通知
38		public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
39			System.out.println("异常通知:出错了"+e.getMessage());
40		}
41		//最终通知
42		public void myAfter(){
43			System.out.println("最终通知:模拟方法结束后释放资源...");
44		}
45	}

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

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

 在com.ssm.aspectj.xml包中创建配置文件applicationContext.xml,并编写相关配置,如文件3.4所示。

文件3.4  applicationContext.xml

01	<?xml version="1.0" encoding="UTF-8"?>
02	<beans xmlns="http://www.springframework.org/schema/beans"
03		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04		xmlns:aop="http://www.springframework.org/schema/aop"
05		xsi:schemaLocation="http://www.springframework.org/schema/beans 
06	        http://www.springframework.org/schema/beans/spring-beans.xsd
07	        http://www.springframework.org/schema/aop
08	        http://www.springframework.org/schema/aop/spring-aop.xsd">
09		<!-- 1 目标类 -->
10		<bean id="userDao" class="com.ssm.aspectj.UserDaoImpl" />
11		<!-- 2 切面 -->
12		<bean id="myAspect" class="com.ssm.aspectj.xml.MyAspect" />
13		<!-- 3 aop编程 -->
14		<aop:config>
15			<!-- 1.配置切面 -->
16			<aop:aspect id="aspect" ref="myAspect">
17				<!-- 2.配置切入点 -->
18				<aop:pointcut expression="execution(* com.ssm.aspectj.*.*(..))" id="myPointCut" />
19				<!-- 3.配置通知 -->
20				<!-- 前置通知 -->
21				<aop:before method="myBefore" pointcut-ref="myPointCut" />
22				<!--后置通知--> 
23				<aop:after-returning method="myAfterReturning" pointcut-
24	         ref="myPointCut" returning="joinPoint"/> 
25				<!--环绕通知 -->
26				<aop:around method="myAround" pointcut-ref="myPointCut" />
27				<!--异常通知 -->
28				<aop:after-throwing method="myAfterThrowing" pointcut- 
29	         ref="myPointCut" throwing="e" />
30				<!--最终通知 -->
31				<aop:after method="myAfter" pointcut-ref="myPointCut" />
32			</aop:aspect>
33		</aop:config>
34	</beans>

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

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

文件3.5  TestXmlAspectJ.java

01	package com.ssm.aspectj.xml;
02	import org.springframework.context.ApplicationContext;
03	import org.springframework.context.support.ClassPathXmlApplicationContext;
04	import com.ssm.aspectj.UserDao;
05	public class TestXmlAspectJ {
06		public static void main(String[] args) {
07	     	// 定义配置文件路径
08			String xmlPath="com/ssm/aspectj/xml/applicationContext.xml";
09	     	// 初始化Spring容器,加载配置文件
10			ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
11			// 从容器中获得userDao实例
12			UserDao userDao=(UserDao)applicationContext.getBean("userDao");
13			// 执行添加用户方法
14			userDao.addUser();
15		}
16	}

执行程序后,控制台的输出结果如图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-2】为了使读者可以快速掌握这些注解,接下来重新使用注解的形式实现3.2.1节的示例,具体步骤如下:

 在chapter03项目的src目录下创建com.ssm.aspectj.annotation包,将文件3.3的切面类MyAspect复制到该包下,并对该文件进行修改,如文件3.6所示。

文件3.6  MyAspect.java

01	package com.ssm.aspectj.annotation;
02	import org.aspectj.lang.JoinPoint;
03	import org.aspectj.lang.ProceedingJoinPoint;
04	import org.aspectj.lang.annotation.After;
05	import org.aspectj.lang.annotation.AfterReturning;
06	import org.aspectj.lang.annotation.AfterThrowing;
07	import org.aspectj.lang.annotation.Around;
08	import org.aspectj.lang.annotation.Aspect;
09	import org.aspectj.lang.annotation.Before;
10	import org.aspectj.lang.annotation.Pointcut;
11	import org.springframework.stereotype.Component;
12	/**
13	 * 切面类,在此类中编写通知
14	 */
15	@Aspect
16	@Component
17	public class MyAspect {
18		//定义切入点表达式
19		@Pointcut("execution(* com.ssm.aspectj.*.*(..))")
20		//使用一个返回值为void、方法体为空的方法来命名切入点
21		public void myPointCut(){}
22		//前置通知
23		@Before("myPointCut()")
24		public void myBefore(JoinPoint joinPoint){
25			System.out.print("前置通知:模拟执行权限检查..,");
26			System.out.print("目标类是:"+joinPoint.getTarget());
27			System.out.println(",被植入增强处理的目标方法为:"+
28	             joinPoint.getSignature().getName());
29		}
30		//后置通知
31		@AfterReturning(value="myPointCut()")
32		public void myAfterReturning(JoinPoint joinPoint) {
33			System.out.print("后置通知:模拟记录日志..,");
34			System.out.println("被植入增强处理的目标方法为:" + 
35	             joinPoint.getSignature().getName());
36		}
37		/**
38		 * 环绕通知
39		 * ProceedingJoinPoint是JoinPoint的子接口,表示可执行目标方法
40		 * 1.必须是Object类型的返回值
41		 * 2.必须接收一个参数,类型为ProceedingJoinPoint
42		 * 3.必须是throws Throwable
43		 */
44		@Around("myPointCut()")
45		public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
46			//开始
47			System.out.println("环绕开始:执行目标方法之前,模拟开启事务..,");
48			//执行当前目标方法
49			Object obj=proceedingJoinPoint.proceed();
50			//结束
51			System.out.println("环绕结束:执行目标方法之后,模拟关闭事务..,");
52			return obj;
53		}
54		//异常通知
55		@AfterThrowing(value="myPointCut()",throwing="e")
56		public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
57			System.out.println("异常通知:出错了"+e.getMessage());
58		}
59		//最终通知
60		@After("myPointCut()")
61		public void myAfter(){
62			System.out.println("最终通知:模拟方法结束后释放资源..");
63		}
64	}

在文件3.6中,首先使用@Aspect注解定义了切面类,由于该类在Spring中是作为组件使用的,因此还需要添加@Component注解才能生效。然后使用@Pointcut注解来配置切入表达式,并通过定义方法来表示切入点名称。接下来在每个通知相应的方法上添加了相应的注解,并将切入点名称myPointcut作为参数传递给需要执行增强的通知方法。如果需要其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。

 在目标类com.ssm.aspectj.UserDaoImpl中添加注解@Repository("userDao")。

 在com.ssm.aspectj.annotation包下创建配置文件applicationContext.xml,并对该文件进行编辑,如文件3.7所示。

文件3.7  applicationContext.xml

01	package com.ssm.aspectj.annotation;
02	import org.springframework.context.ApplicationContext;
03	import org.springframework.context.support.ClassPathXmlApplicationContext;
04	import com.ssm.aspectj.UserDao;
05	public class TestAnnotation {
06		public static void main(String[] args) {
07			String xmlPath="com/ssm/aspectj/annotation/ applicationContext.xml";
08			ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
09			//从容器中获得userDao实例
10			UserDao userDao=(UserDao)applicationContext.getBean("userDao");
11			//执行添加用户的方法
12			userDao.addUser();
13		}
14	}

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

图3.3  运行结果

在UserDaoImpl类的addUser()方法中,加上出错代码来演示异常通知的执行,控制台的输出结果如图3.2所示。

从图3.1和图3.3可以看出,基于注解的方式与基于XML的方式执行结果相同,只是在目标方法前后通知的执行顺序发生了变化。相对来说,使用注解的方式更加简单、方便,所以在实际开发中推荐使用注解的方式进行AOP开发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值