Spring AOP(面向切面编程)

什么是AOP

  AOP的全称是 Aspect-Oriented Programming,即面向切面编程(也称面向方面编程,它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。

  AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充。

  AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。新版本的Spring框架也建议使用AspectJ来开发AOP。
使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ。

基于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(如上述代码中定义的my Aspect)。定义完成后,通过< aop: aspect>元素的ref属性即可引用该Bean。
  2. 在 Spring的配置文件中,切入点是通过<aop: pointcut>元素来定义的。当< aop: pointcut>元素作为< aop: config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当< aop: pointcut>元素作为< aop: aspect>元素的子元素时,表示该切入点只对当前切面有效。在定义< aop:pointcut>元素时,通常会指定id和 expression两个属性
  • 在上述配置代码片段中,execution(* com. ssm.jdk..(..))就是定义的切入点表达式,该切入点表达式的意思是匹配com. ssm.jdk包中任意类的任意方法的执行。其中 execution0是表达式的主体,第1个表示的是返回类型,使用代表所有类型;com. ssm.jdk表示的是需要拦截的包名,后面第2个表示的是类名,使用代表所有的类;第3个表示的是方法名,使用表示所有方法;后面()表示方法的参数,其中的“..”表示任意参数。需要注意的是,第1个与包名之间有一个空格。
  1. 在配置代码中,分别使用<aop: aspect>的子元素配置了5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性

  2. AspectJ框架相关的JAR包
    spring- aspects-4.3.6. RELEASE. Jar:Spring为AspectJ提供的实现, Spring的包中已经提供。
    aspectjweaver-1.8.10.jar:是 AspectJ框架所提供的规范

	package com.ssm.aspectj.xml;
	import org.aspectj.lang.JoinPoint;
	import org.aspectj.lang.ProceedingJoinPoint;
	/**
	 * 切面类,在此类中编写通知
	 */
	public class MyAspect {
		//前置通知
		public void myBefore(JoinPoint joinPoint){
			System.out.print("前置通知:模拟执行权限检查...,");
			System.out.print("目标类是:"+joinPoint.getTarget());
			System.out.println(",被植入增强处理的目标方法为:"+
	                          joinPoint.getSignature().getName());
		}
		//后置通知
		public void myAfterReturning(JoinPoint joinPoint) {
			System.out.print("后置通知:模拟记录日志...,");
			System.out.println("被植入增强处理的目标方法为:" +
	                           joinPoint.getSignature().getName());
		}
		/**
		 * 环绕通知
		 * ProceedingJoinPoint是JoinPoint的子接口,表示可执行目标方法
		 * 1.必须是Object类型的返回值
		 * 2.必须接收一个参数,类型为ProceedingJoinPoint
		 * 3.必须throws Throwable
		 */
		public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
			//开始
			System.out.println("环绕开始:执行目标方法之前,模拟开启事务...,");
			//执行当前目标方法
			Object obj=proceedingJoinPoint.proceed();
			//结束
			System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...,");
			return obj;
		}
		//异常通知
		public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
			System.out.println("异常通知:出错了"+e.getMessage());
		}
		//最终通知
		public void myAfter(){
			System.out.println("最终通知:模拟方法结束后释放资源...");
		}
	}

	<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-4.3.xsd
	        http://www.springframework.org/schema/aop
	        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
		<!-- 1 目标类 -->
		<bean id="userDao" class="com.ssm.aspectj.UserDaoImpl" />
		<!-- 2 切面 -->
		<bean id="myAspect" class="com.ssm.aspectj.xml.MyAspect" />
		<!-- 3 aop编程 -->
		<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>
	</beans>

基于注解的声明式 AspectJ

  基于XML的声明式AspectJ实现AOP编程虽然便捷,但是它也存在着一些缺点,那就是要在Spring文件中配置大量的代码信息。为了解决这个问题,AspectJ框架为AOP的实现提供了一套注解,用以取代 Spring配置文件中为实现AOP功能所配置的臃肿代码。

	package com.ssm.aspectj.annotation;
	import org.aspectj.lang.JoinPoint;
	import org.aspectj.lang.ProceedingJoinPoint;
	import org.aspectj.lang.annotation.After;
	import org.aspectj.lang.annotation.AfterReturning;
	import org.aspectj.lang.annotation.AfterThrowing;
	import org.aspectj.lang.annotation.Around;
	import org.aspectj.lang.annotation.Aspect;
	import org.aspectj.lang.annotation.Before;
	import org.aspectj.lang.annotation.Pointcut;
	import org.springframework.stereotype.Component;
	/**
	 * 切面类,在此类中编写通知
	 */
	@Aspect
	@Component
	public class MyAspect {
		//定义切入点表达式
		@Pointcut("execution(* com.ssm.aspectj.*.*(..))")
		//使用一个返回值为void、方法体为空的方法来命名切入点
		public void myPointCut(){}
		//前置通知
		@Before("myPointCut()")
		public void myBefore(JoinPoint joinPoint){
			System.out.print("前置通知:模拟执行权限检查..,");
			System.out.print("目标类是:"+joinPoint.getTarget());
			System.out.println(",被植入增强处理的目标方法为:"+
	             joinPoint.getSignature().getName());
		}
		//后置通知
		@AfterReturning(value="myPointCut()")
		public void myAfterReturning(JoinPoint joinPoint) {
			System.out.print("后置通知:模拟记录日志..,");
			System.out.println("被植入增强处理的目标方法为:" + 
	             joinPoint.getSignature().getName());
		}
		/**
		 * 环绕通知
		 * ProceedingJoinPoint是JoinPoint的子接口,表示可执行目标方法
		 * 1.必须是Object类型的返回值
		 * 2.必须接收一个参数,类型为ProceedingJoinPoint
		 * 3.必须throws Throwable
		 */
		@Around("myPointCut()")
		public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
			//开始
			System.out.println("环绕开始:执行目标方法之前,模拟开启事务..,");
			//执行当前目标方法
			Object obj=proceedingJoinPoint.proceed();
			//结束
			System.out.println("环绕结束:执行目标方法之后,模拟关闭事务..,");
			return obj;
		}
		//异常通知
		@AfterThrowing(value="myPointCut()",throwing="e")
		public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
			System.out.println("异常通知:出错了"+e.getMessage());
		}
		//最终通知
		@After("myPointCut()")
		public void myAfter(){
			System.out.println("最终通知:模拟方法结束后释放资源..");
		}
	}
	<?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"
		xmlns:context="http://www.springframework.org/schema/context"
		xsi:schemaLocation="http://www.springframework.org/schema/beans 
	        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
	        http://www.springframework.org/schema/aop
	        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
	        http://www.springframework.org/schema/context
	        http://www.springframework.org/schema/context/spring-context-4.3.xsd">
		<!-- 指定需要扫描的包,使注解生效 -->
		<context:component-scan base-package="com.ssm" />
		<!-- 启动基于注解的声明式AspectJ支持 -->
		<aop:aspectj-autoproxy />
	</beans>

posted @ 2020-06-23 16:13  曾强  阅读(42)  评论(0编辑  收藏