Spring学习笔记——(5)AOP(二)

一、前置通知

(1)在方法执行之前执行的通知。前置通知使用 @Before 注解, 并将切入点表达式的值作为注解值。

/**
    * 前置通知,在目标方法开始之前执行。<br>
    * 1.@Before 标记的方法的方法体.<br>
    * 2.@Before 里面的是切入点表达式<br>
    * 3.JoinPoint 类型的参数:从中可以访问到方法的签名和方法的参数.
*/
@Before("execution(public int com.shaohe.spring.aop.helloword.ArithmeticCalculator.*(int, int))")
public void beforeMethod(JoinPoint joinPoint) {
	// 方法名称
	String methodName = joinPoint.getSignature().getName();
	// 参数数组
	Object[] args = joinPoint.getArgs();
	System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}

(2)利用方法签名编写 AspectJ 切入点表达式

  • execution * com.atguigu.spring.ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中声明的所有方法,第一个 * 代表任意修饰符及任意返回值. 第二个 * 代表任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 可以省略包名.
  • execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 接口的所有公有方法.
  • execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中返回 double 类型数值的方法
  • execution public double ArithmeticCalculator.*(double, ..): 匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数
  • execution public double ArithmeticCalculator.*(double, double): 匹配参数类型为 double, double 类型的方法.

(3)在 AspectJ 中, 切入点表达式可以通过操作符 &&, ||, ! 结合起来. 

二、后置通知

在目标方法执行后(无论是否发生异常),执行的通知。

后置通知是在连接点完成之后执行的, 即连接点返回结果或者抛出异常的时候

/**
 * 后置通知:@After,在方法执行之后执行的代码.
 */
@After("execution(* com.shaohe.spring.aop.helloword.*.*(..))")
public void afterMethod(JoinPoint joinPoint) {
	String methodName = joinPoint.getSignature().getName();
	System.out.println("The method " + methodName + " ends");
}

三、返回通知

在方法正常结束后执行

/**
 * 返回通知:在方法正常结束后执行的代码<br>
 * 返回通知是可以访问到方法的返回值的!<br>
 * value属性或者pointcut属性中填写切点表达式<br>
 * returning:和通知的参数名相同,都是result
 */
@AfterReturning(value = "execution(* com.shaohe.spring.aop.helloword.*.*(..))", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
	String methodName = joinPoint.getSignature().getName();
	System.out.println("返回通知:The method " + methodName + " ends with " + result);
}
  • 在返回通知中, 只要将 returning 属性添加到 @AfterReturning 注解中, 就可以访问连接点的返回值. 该属性的值即为用来传入返回值的参数名称. 
  • 必须在通知方法的签名中添加一个同名参数. 在运行时, Spring AOP 会通过这个参数传递返回值.

四、异常通知

只在连接点抛出异常时才执行

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

五、环绕通知

  • 环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.
  • 对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.
  • 在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
  • 注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
/**
 * 环绕通知:相当于动态代理的全过程<br>
 * ProceedingJoinPoint类型的参数可以决定是否执行目标方法<br>
 * 必须有返回值,即目标方法的返回值。
 */
@Around("execution(* com.shaohe.spring.aop.helloword.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd) {
	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 注解, 序号出现在注解中

七、重用切入点定义

  • 在编写 AspectJ 切面时, 可以直接在通知注解中书写切入点表达式. 但同一个切点表达式可能会在多个通知中重复出现.
  • 在 AspectJ 切面中, 可以通过 @Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的, 因为将切入点定义与应用程序逻辑混在一起是不合理的. 
  • 切入点方法的访问控制符同时也控制着这个切入点的可见性. 如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 在这种情况下, 它们必须被声明为 public. 在引入这个切入点时, 必须将类名也包括在内. 如果类没有与这个切面放在同一个包中, 还必须包含包名.
  • 其他通知可以通过方法名称引入该切入点.

八、基于 XML 的配置声明切面

  • 当使用 XML 声明切面时, 需要在 <beans> 根元素中导入 aop Schema
  • 在 Bean 配置文件中, 所有的 Spring AOP 配置都必须定义在 <aop:config> 元素内部. 对于每个切面而言, 都要创建一个 <aop:aspect> 元素来为具体的切面实现引用后端 Bean 实例. 
  • 切面 Bean 必须有一个标示符, 供 <aop:aspect> 元素引用

声明切面:


public class LoggingAspectXML {

	/**
	 * 前置通知,在目标方法开始之前执行
	 */
	public void beforeMethod(JoinPoint joinPoint) {
		// 方法名称
		String methodName = joinPoint.getSignature().getName();
		// 参数数组
		Object[] args = joinPoint.getArgs();
		System.out.println("前置通知:The method " + methodName + " begins with " + Arrays.asList(args));
	}

	/**
	 * 后置通知:,在方法执行之后执行的代码.
	 */
	public void afterMethod(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("后置通知:The method " + methodName + " ends");
	}

	/**
	 * 返回通知:在方法正常结束后执行的代码<br>
	 * 返回通知是可以访问到方法的返回值的!<br>
	 */
	public void afterReturning(JoinPoint joinPoint, Object result) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("返回通知:The method " + methodName + " ends with " + result);
	}

	/**
	 * 异常通知:只在连接点抛出异常时才执行
	 */
	public void afterThrowing(JoinPoint joinPoint, Exception e) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("异常通知:The method " + methodName + " occurs excetion:" + e);
	}

}

 

xml配置:

<!-- 配置 bean -->
<bean id="arithmeticCalculator"
	class="com.shaohe.spring.aop.helloword.ArithmeticCalculatorImpl"></bean>

<!-- 配置切面的 bean. -->
<bean id="loggingAspectXML" class="com.shaohe.spring.aop.helloword.LoggingAspectXML"></bean>

<!-- 配置 AOP -->
<aop:config>
	<!-- 配置切点表达式 -->
	<aop:pointcut expression="execution(* com.shaohe.spring.aop.helloword.*.*(..))"
		id="pointcut" />
	<!-- 配置切面及通知 ,ref指向切面的id,order指定优先级 -->
	<aop:aspect ref="loggingAspectXML" order="2">
		<!-- 前置通知 -->
		<aop:before method="beforeMethod" pointcut-ref="pointcut" />
		<!-- 后置通知 -->
		<aop:after method="afterMethod" pointcut-ref="pointcut" />
		<!-- 异常通知 -->
		<aop:after-throwing method="afterThrowing"
			pointcut-ref="pointcut" throwing="e" />
		<!-- 返回通知 -->
		<aop:after-returning method="afterReturning"
			pointcut-ref="pointcut" returning="result" />
		<!-- 环绕通知 -->
		<aop:around method="aroundMethod" pointcut-ref="pointcut" />
	</aop:aspect>
</aop:config>

启动测试:

public static void main(String[] args) {

	ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-aop-xml.xml");
	// 从IOC容器中获取Bean的实例
	ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
	// bean的类型名称
	System.out.println(arithmeticCalculator.getClass().getName());
	// 调用bean的方法。
	int result = arithmeticCalculator.add(11, 12);
	System.out.println("result:" + result);

}

九、引入通知(不常用)

引入通知是一种特殊的通知类型. 它通过为接口提供实现类, 允许对象动态地实现接口, 就像对象已经在运行时扩展了实现类一样.

  • 引入通知可以使用两个实现类 MaxCalculatorImpl 和 MinCalculatorImpl, 让 ArithmeticCalculatorImpl 动态地实现 MaxCalculator 和 MinCalculator 接口. 而这与从 MaxCalculatorImpl 和 MinCalculatorImpl 中实现多继承的效果相同. 但却不需要修改 ArithmeticCalculatorImpl 的源代码
  • 引入通知也必须在切面中声明
  • 在切面中, 通过为任意字段添加@DeclareParents 注解来引入声明. 
  • 注解类型的 value 属性表示哪些类是当前引入通知的目标. value 属性值也可以是一个 AspectJ 类型的表达式, 以将一个即可引入到多个类中.  defaultImpl 属性中指定这个接口使用的实现类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOPSpring框架中的一个重要模块,它提供了面向切面编程(AOP)的支持。AOP是一种编程思想,它可以在不改变原有代码的情况下,通过在程序运行时动态地将代码“织入”到现有代码中,从而实现对原有代码的增强。 Spring AOP提供了基于注解的AOP实现,使得开发者可以通过注解的方式来定义切面、切点和通知等相关内容,从而简化了AOP的使用。 下面是一个基于注解的AOP实现的例子: 1. 定义切面类 ```java @Aspect @Component public class LogAspect { @Pointcut("@annotation(Log)") public void logPointcut() {} @Before("logPointcut()") public void beforeLog(JoinPoint joinPoint) { // 前置通知 System.out.println("执行方法:" + joinPoint.getSignature().getName()); } @AfterReturning("logPointcut()") public void afterLog(JoinPoint joinPoint) { // 后置通知 System.out.println("方法执行完成:" + joinPoint.getSignature().getName()); } @AfterThrowing(pointcut = "logPointcut()", throwing = "ex") public void afterThrowingLog(JoinPoint joinPoint, Exception ex) { // 异常通知 System.out.println("方法执行异常:" + joinPoint.getSignature().getName() + ",异常信息:" + ex.getMessage()); } } ``` 2. 定义业务逻辑类 ```java @Service public class UserService { @Log public void addUser(User user) { // 添加用户 System.out.println("添加用户:" + user.getName()); } @Log public void deleteUser(String userId) { // 删除用户 System.out.println("删除用户:" + userId); throw new RuntimeException("删除用户异常"); } } ``` 3. 在配置文件中开启AOP ```xml <aop:aspectj-autoproxy/> <context:component-scan base-package="com.example"/> ``` 在这个例子中,我们定义了一个切面类LogAspect,其中通过@Aspect注解定义了一个切面,通过@Pointcut注解定义了一个切点,通过@Before、@AfterReturning和@AfterThrowing注解分别定义了前置通知、后置通知和异常通知。 在业务逻辑类中,我们通过@Log注解标注了需要增强的方法。 最后,在配置文件中,我们通过<aop:aspectj-autoproxy/>开启了AOP功能,并通过<context:component-scan>扫描了指定包下的所有组件。 这样,当我们调用UserService中的方法时,就会触发LogAspect中定义的通知,从而实现对原有代码的增强。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值