JavaWeb 笔记之 Spring AOP

Spring AOP

  • AspectJ:Java 社区里最完整最流行的 AOP 框架
  • 在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP

基于 AspectJ 注解的 AOP

启用 AspectJ 注解支持

  1. 引入 Jar 包(aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar、spring-aop.jar)
  2. 将 aop Schema 添加到 <beans> 根元素中
  3. 要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy>
  4. 当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.

用 AspectJ 注解声明切面

  1. 要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.
  2. 在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.
  3. 通知是标注有某种注解的简单的 Java 方法.
  4. AspectJ 支持 5 种类型的通知注解:
  • @Before: 前置通知, 在方法执行之前执行
  • @After: 后置通知, 在方法执行之后执行
  • @AfterRunning: 返回通知, 在方法返回结果之后执行
  • @AfterThrowing: 异常通知, 在方法抛出异常之后
  • @Around: 环绕通知, 围绕着方法执行

1. 前置通知

  • 前置通知:在方法执行之前执行的通知
  • 前置通知使用 @Before 注解, 并将切入点表达式的值作为注解值.
 @Aspect
 @Component
 public class LoggingAspect {
	
	 @Before("execution(public int com.axon.spring5.aop.impl.ArithmeticCalculator.add(int, int))")
	 public void beforeMethod(JoinPoint joinPoint) {
	 String methodName = joinPoint.getSignature().getName();
	 List<Object> args = (List<Object>) Arrays.asList(joinPoint.getArgs());
	 System.out.println("The method " + methodName + "begins with "+args);
 }
}

  • 编写 AspectJ 切入点表达式
  • execution * com.axon.spring.ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中声明的所有方法,第一个 * 代表任意修饰符及任意返回值. 第二个 * 代表任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 可以省略包名.
  • execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 接口的 所有公有方法.
  • execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中 返回 double 类型数值的方法
  • execution public double ArithmeticCalculator.*(double, ..): 匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数
  • execution public double ArithmeticCalculator.*(doubledouble): 匹配参数类型为 double, double 类型的方法.
  • 在 AspectJ 中, 切入点表达式可以通过操作符 &&, ||, ! 结合起来
@Aspect
@Component
public class LoggingAspect {
	
	@Pointcut("execution(* *.add(int,..)) || execution(* *.sub(int,..))")
	private void loggingOperation(){}
	
	@Before("loggingOperation()")
	public void beforeMethod(JoinPoint joinPoint) {
	String methodName = joinPoint.getSignature().getName();
 	List<Object> args = (List<Object>) Arrays.asList(joinPoint.getArgs());
		System.out.println("The method " + methodName + "begins with "+args);
	}
}

2. 后置通知

  • 后置通知是在连接点完成之后执行的, 即连接点返回结果或者抛出异常的时候, 下面的后置通知记录了方法的终止.
  • 一个切面可以包括一个或者多个通知.
  • 后置通知还不能访问目标方法执行的结果
	 @After("execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))")
	 public void afterMethod(JoinPoint joinPoint) {
		 String methodName = joinPoint.getSignature().getName();
		 System.out.println("The method " + methodName + " ends");
	 }

3. 返回通知

  • 无论连接点是正常返回还是抛出异常, 后置通知都会执行. 如果只想在连接点返回的时候记录日志, 应使用返回通知代替后置通知.
  • 可以访问方法的返回值
	@AfterReturning(pointcut="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))",returning="result")
	public void afterReturning(JoinPoint joinPoint,Object result) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("The method " + methodName + " ends with " + result);
	}

4. 异常通知

  • 只在连接点抛出异常时才执行异常通知
  • 将 throwing 属性添加到 @AfterThrowing 注解中, 也可以访问连接点抛出的异常. Throwable 是所有错误和异常类的超类. 所以在异常通知方法可以捕获到任何错误和异常.
  • 如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行.
    @AfterThrowing(value="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))",throwing="ex")
	public void afterThrowing(JoinPoint joinPoint,Exception ex) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("The method " + methodName + " occurs exception: " + ex);
	}

5. 环绕通知

  • 环绕通知是所有通知类型中功能最为强大的,环绕通知类似于动态代理的全过程
  • ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.
  • 注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
    @Around("execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))")
	public Object aroundMethod(ProceedingJoinPoint pjd) throws Throwable {
		Object result = null;
		String methodName = pjd.getSignature().getName();
		//执行目标方法
		try{
			//前置通知
			System.out.println("The method " + methodName + " begins with "+ Arrays.asList(pjd.getArgs()));
			result = pjd.proceed();
			//返回通知
			System.out.println("The method " + methodName + " ends with " + result);
		} catch (Throwable e){
			//异常通知
			System.out.println("The method " + methodName + " occurs exception: " + e);
			throw new RuntimeException(e);
		}
		//后置通知
		System.out.println("The method " + methodName + " ends ");
		return result;
	}

指定切面的优先级

  • 同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的
  • 切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定.
  • 实现 Ordered 接口, getOrder() 方法的返回值越小, 优先级越高.
  • 若使用 @Order 注解, 序号出现在注解中
@Aspect
@Order(1)
@Component
public class LoggingAspect {
}

@Aspect
@Order(2)
@Component
public class ValidationAspect {
} 


基于 XML 的配置的 AOP

  • 除了使用 AspectJ 注解声明切面, Spring 也支持在 Bean 配置文件中声明切面. 这种声明是通过 aop schema 中的 XML 元素完成的.
  • 正常情况下, 基于注解的声明要优先于基于 XML 的声明. 通过 AspectJ 注解, 切面可以与 AspectJ 兼容, 而基于 XML 的配置则是 Spring 专有的. 由于 AspectJ 得到越来越多的 AOP 框架支持, 所以以注解风格编写的切面将会有更多重用的机会.

声明切面步骤

  • 首先 需要在 <beans> 根元素中导入 aop Schema
  • 在 Bean 配置文件中, 所有的 Spring AOP 配置都必须定义在 <aop:config> 元素内部. 对于每个切面而言, 都要创建一个 <aop:aspect> 元素来为具体的切面实现引用后端 Bean 实例.
  • 切面 Bean 必须有一个标示符, 供 <aop:aspect> 元素引用
  • 切入点使用 <aop:pointcut> 元素声明
  • 切入点必须定义在 <aop:aspect> 元素下, 或者直接定义在 <aop:config> 元素下.
  • 定义在 <aop:aspect> 元素下: 只对当前切面有效
<!-- 配置 aop  -->
<aop:config>
	<!-- 配置切面以及通知 -->
	<aop:aspect  ref="xxxx" order="1">
	    <aop:around method="aroundMethod"  pointcut="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))" />
	</aop:aspect>
</aop:config>
  • 定义在 <aop:config> 元素下: 对所有切面都有效
<aop:config>
	<!-- 配置切入点表达式 -->
	<aop:pointcut expression="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))" id="pointcut" />
    <aop:aspect  ref="xxxx" order="1">
		<aop:before method="beforeMethod"  pointcut-ref="pointcut" />
		<aop:after method="afterMethod"  pointcut-ref="pointcut" />
		<aop:after-returning method="afterReturning"  pointcut-ref="pointcut" returning="result"/>
		<aop:after-throwing method="afterThrowing"  pointcut-ref="pointcut" throwing="ex"/>
	</aop:aspect>
	<aop:aspect  ref="xxxx" order="0">
		<aop:before method="validateArgs" pointcut-ref="pointcut"/>
	</aop:aspect>
</aop:config>
  • 基于 XML 的 AOP 配置不允许在切入点表达式中用名称引用其他切入点.
  • 详细示例:
 
<!-- 配置切面的 bean -->
<bean id="loggingAspect" class="com.axon.spring5.aop.impl.LoggingAspect"></bean>
<bean id="validationAspect" class="com.axon.spring5.aop.impl.ValidationAspect"></bean>
<!-- 配置 aop  -->
<aop:config>
	<!-- 配置切入点表达式 -->
	<aop:pointcut expression="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))" id="pointcut" />
	<!-- 配置切面以及通知 -->
	<aop:aspect  ref="loggingAspect" order="1">
	    <aop:before method="beforeMethod"  pointcut-ref="pointcut" />
	    <aop:after method="afterMethod"  pointcut-ref="pointcut" />
	    <aop:after-returning method="afterReturning"  pointcut-ref="pointcut" returning="result"/>
		<aop:after-throwing method="afterThrowing"  pointcut-ref="pointcut" throwing="ex"/>
		<!-- <aop:around method="aroundMethod"  pointcut-ref="pointcut" /> -->
		<!-- <aop:around method="aroundMethod"  pointcut="execution(* com.axon.spring5.aop.impl.ArithmeticCalculator.*(int, int))" /> -->
	</aop:aspect>
	<aop:aspect  ref="validationAspect" order="0">
		<aop:before method="validateArgs" pointcut-ref="pointcut"/>
	</aop:aspect>
</aop:config>

猜你喜欢

转载自blog.csdn.net/wm1203618455/article/details/82998454
今日推荐