Spring框架自学之路(七)

(07Day)

    今天我们来看看Spring中AOP的最流行最完整的AOP--->AspectJ

    我们要使用AspectJ就一定要先导入AspectJ 的Jar包,AspectJ要导入的jar包为aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar。所以加入AspectJ包后算上spring的基本jar包就一共有以下这些包。


    加完jar包后,还需要spring的配置文件中启用AspectJ注解支持。在spring中加入<aop:aspectj-autoproxy></aop:aspectj-autoproxy>这句话启用注解支持。这样当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理。

    要在spring中声明AspectJ切面,只需要在spring中将切面注册为Bean实例。在 Spring IOC 容器中初始化 AspectJ 切面之后,IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理。

    那么什么样的类是切面呢?其实在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类。注意前提是该类是一个已经在IOC容器中注册的Bean实例。那么在切面中每一个方法也就是每一个通知,是怎么知道这个方法要给哪一个类的哪些方法做代理呢?其实很简单就是在每个通知上加上某个通知类型的通知注解,并使用切入点表达式来表示(value的值=“execution(访问权限  返回值类型  全类名.方法名)”可以使用占位符)要匹配哪个类的哪个方法。下面是最典型的切入点表达式时根据方法的签名来匹配各种方法:

  • 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 类型的方法。

    那么AspectJ都有哪几种类型的通知注解呢?AspectJ一共提供了5种类型的通知注解。

  1. @Before: 前置通知, 在方法执行之前执行
  2. @After: 后置通知, 在方法执行之后执行 
  3. @AfterRunning: 返回通知, 在方法返回结果之后执行
  4. @AfterThrowing: 异常通知, 在方法抛出异常之后
  5. @Around: 环绕通知, 围绕着方法执行

    下面我就用一个例子来说明这几种通知的用法:

    1)创建一个接口

package com.aop.impl;

public interface ArithmeticCalculator {
	int add(int i, int j);
	int sub(int i, int j);
	int mul(int i, int j);
	int div(int i, int j);
}
    2)实现接口
package com.aop.impl;

import org.springframework.stereotype.Component;

@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

	public int add(int i, int j) {
		int result = i + j;
		return result;
	}

	public int sub(int i, int j) {
		int result = i - j;
		return result;
	}

	public int mul(int i, int j) {
		int result = i * j;
		return result;
	}

	public int div(int i, int j) {
		int result = i / j;
		return result;
	}

}
    3)声明一个切面
package com.aop.impl;

import java.util.Arrays;
import java.util.List;

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.springframework.stereotype.Component;
//把这个类声明为一个切面:需要把该类放入到IOC容器中,再声明为一个切面
@Component
@Aspect
public class LogginAspect {
	//声明该方法是一个前置通知:在目标方法开始执行之前
	/*execution执行括号中的是一个AspectJ的一个表达式:可以加入一些占位符,比如可以把add改成*
	 * 这样就变成了给ArithmeticCalculatorImpl类的所有方法(参数为int int的)注册前置通知。
	 * (..)可以表示参数任意
	*/
	@Before("execution(public int com.aop.impl.ArithmeticCalculatorImpl.add(int,int))")
	public void beforMethod(JoinPoint joinPoint){
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		System.out.println("方法"+methodName+"开始执行,参数为:"+args);
	}
	
	//后置通知:在目标方法执行后(无论是否有异常)执行该方法。
	@After("execution(public int com.aop.impl.ArithmeticCalculatorImpl.add(int,int))")
	public void afterMethod(JoinPoint joinPoint){
		String methodName = joinPoint.getSignature().getName();
		System.out.println("方法"+methodName+"执行结束");
	}
	
	/*返回通知:在方法正常结束后执行的代码,同时是可以获得返回值的,在returning = “result”
	 * 的意思是把返回值放在result中,然后在方法的参数中传入Object result参数,这样就可以
	 * 获得返回值了。
	 */
	@AfterReturning(value="execution(public int com.aop.impl.ArithmeticCalculatorImpl.*(..))",
			returning="result")
	public void afterReturning(JoinPoint joinPoint,Object result){
		String methodName = joinPoint.getSignature().getName();
		System.out.println("方法"+methodName+"无异常始执行结束,返回值为:"+result);
	}
	/*
	 *异常通知:在目标方法抛出异常的时候会执行的代码,同时可以访问异常对象;切可以指定在出现特定异常
	 *时在执行通知代码 
	 */
	@AfterThrowing(value="execution(public int com.aop.impl.ArithmeticCalculatorImpl.*(..))",
			throwing="ex")
	public void afterThrowing(JoinPoint joinPoint,Exception ex){
		String methodName = joinPoint.getSignature().getName();
		System.out.println("方法"+methodName+"抛出异常,异常为:"+ex);
	}
	
	/*
	 *环绕通知需要携带 ProceedingJoinPoint 类型的参数. 
	 *环绕通知类似于动态代理的全过程,ProceedingJoinPoint类型参数可以决定是否执行目标方法。
	 *且环绕通知必须要有返回值,返回值即为目标方法的返回值。
	 */
	@Around(value="execution(public int com.aop.impl.ArithmeticCalculatorImpl.*(..))")
	public Object aroundMethod(ProceedingJoinPoint pjd){
		Object result = null;
		String methodName = pjd.getSignature().getName();
		try {
			//前置通知
			System.out.println("方法"+methodName+"开始执行,参数为:"+Arrays.asList(pjd.getArgs()));
			//执行方法
			result = pjd.proceed();
			//返回通知
			System.out.println("方法"+methodName+"无异常始执行结束,返回值为:"+result);
		} catch (Throwable e) {
			//异常通知
			System.out.println("方法"+methodName+"抛出异常,异常为:"+e);
		}
		//后置通知
		System.out.println("方法"+methodName+"执行结束");
		return result;
	}
}
    4)在spring配置文件中配置扫描路径和启用AspectJ注解支持(注意需要加入aop,beans,context三个命名空间)
<?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.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
	<!-- 设置自动扫描路径 -->
	<context:component-scan base-package="com.aop.impl"></context:component-scan>
	<!-- 使Aspjectj 注解起作用:自动为匹配的类生成代理对象 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
    5)测试类
package com.aop.impl;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main02 {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);
		int result = arithmeticCalculator.add(3, 5);
		System.out.println(result);
		
	}

}

    讲完了通过注解来配置AOP,那么如何通过XML来配置呢?其实在正常情况下, 基于注解的声明要优先于基于 XML 的声明. 通过 AspectJ 注解, 切面可以与 AspectJ 兼容, 而基于 XML 的配置则是 Spring 专有的. 由于 AspectJ 得到越来越多的 AOP 框架支持, 所以以注解风格编写的切面将会有更多重用的机会.

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

    在 aop Schema 中, 每种通知类型都对应一个特定的 XML 元素. 通知元素需要使用 <pointcut-ref> 来引用切入点, 或用 <pointcut> 直接嵌入切入点表达式.  method 属性指定切面类中通知方法的名称.

    


猜你喜欢

转载自blog.csdn.net/qq_38166944/article/details/79856244