Spring基于注解版的 AOP

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_43687990/article/details/102640880

AOP简介
AOP(Aspect-Oriented Programming, 面向切面编程):是对面向对象编程的补充。其主要编程对象是切面。在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪个地方,以什么方式应用,并且不必修改受影响的类,这样一来,横切关注点就被模块化到特定的切面里了。
AOP中的术语
切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice): 切面必须要完成的工作
目标(Target): 被通知的对象
代理(Proxy): 向目标对象应用通知之后创建的对象
连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

Spirng 中AOP的使用

Spring中通过@AspectJ注解使用Java社区中最完整最流行的AOP框架

要在Spring中使用AspectJ注解,必须加入AspectJ类库:aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
在配置文件中启动Spring对@AspectJ注解的支持,配置如下

<!--使@AspectJ注解生效-->
<!--当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.-->
<aop:aspect-autoproxy></aop:aspect-autoproxy>

需要注意的是,配置文件中需要加入aop命名空间

xmlns:aop=“http://www.springframework.org/schema/aop”

用AspectJ注解声明切面

要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理
在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.

AspectJ支持的五种类型的通知

通知是标注有某种注解的简单的java方法。AspectJ支持一下五种类型的通知

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

示例代码

//准备一个接口

public interface CalculatorInterface {
	int add(int i , int j);
	int sub(int i , int j);
	int mul(int i , int j);
	int div(int i , int j);
	
}

//实现上面的接口
@Component
public class CalculatorImpl implements  CalculatorInterface{

	@Override
	public int add(int i, int j) {
		int resault = i + j;
		
		return resault;
	}

	@Override
	public int sub(int i, int j) {
		int resault = i - j;

		return resault;
	}

	@Override
	public int mul(int i, int j) {
		int resault = i * j;
	
		return resault;
	}

	@Override
	public int div(int i, int j) {
		int resault = i / j;
		
		return resault;
	}

//下面是实现aop的类

@Aspect
@Component
public class TestAspect(){
	
	/*
	前置通知:在方法执行前执行的通知,使用@Before注解,并将切入点表达式的值作为注解值
	execution (public int com.hist.aop.CalculatorInterface.add(int,int))为切点表达式,切点表达式表示执行 CalculatorInterface
接口的 add() 方法。可以用*号代替,表示执行CalculatorInterface下的所有符合参数类型为两个int的方法
		JoinPoint 可以获得方法的详细信息
	*/
	@Before("execution (public int com.hist.aop.CalculatorInterface.add(int,int))")
	public void beforeMethod(JoinPoint joinPoint){
	System.out.println(joinPoint.getSignature().getName() ); //获得所执行方法的名字
	Object [] args = joinPoint.getArgs();//获得方法的所有参数
	}

/*
后置通知:在返回结果或抛出异常的时候执行。一个切面可以包括多个通知
*/
@After("execution(public int com.hist.aop.CalculatorInterface.*(int,int))")
public void AfterMethod(JoinPoint joinPoint){
	System.out.println(joinPoint.getSingnature().getName()); 
}

/*
返回通知:无论连接点是正常返回还是抛出异常,后置通知都会执行,但返回通知只有在正常返回时才会执行
在返回通知中, 只要将 returning 属性添加到 @AfterReturning 注解中, 就可以访问连接点的返回值. 该属性的值即为用来传入返回值的参数名称. 
必须在通知方法的签名中添加一个同名参数. 在运行时, Spring AOP 会通过这个参数传递返回值
切入点表达式中,..匹配任意参数
*/
@AfterReturning(returning="result",value="execution(public int com.hist.aop.CalculatorInterface.*(..))")
public void returningMethod(JoinPoint joinPoint,Object result){
System.out.println("method = "+joinPoint.getSignature().getName());

System.out.println("The method result = "+ result);
}

	/*
	异常通知:只在连接点抛出异常时才执行异常通知
	将 throwing 属性添加到 @AfterThrowing 注解中, 也可以访问连接点抛出的异常. Throwable 是所有错误和异常类的超类. 所以在异常通知方法可以捕获到任何错误和异常.
	如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行
	*/
	@AfterThrowing(value="execution(public int com.hist.aop.CalculatorInterface.*(..))",throwing="ex")
	public void exectionMethod(JoinPoint joinPoint,NullPointerException ex) {
		System.out.println("method = "+joinPoint.getSignature().getName());
		System.out.println("Exception ="+ex);
	}
/*
环绕通知:环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.
对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.
在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
*/

@Around("execution(public int com.hist.aop.CalculatorInterface.*(..))")
public Objecta roundMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try{
//前置通知
			System.out.println("The method "+joinPoint.getSignature().getName()+" begain");

//执行方法
result = joinPoint.proceed();
//返回通知
			System.out.println("The method "+joinPoint.getSignature().getName()+" end with"+result);


}catch(Throwable e){
//异常通知
	System.out.println("The method throw Exection= "+e);
			throw new RuntimeException("find a Exception ");

}
//后置通知
System.out.println("这是后置通知");
}

}

利用方法签名可以编写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 类型的方法。
重用切入点,示例代码:

	/*
	 * 使用@Pointcut实现切点重用,在通知中通过方法名引用
	 */
	@Pointcut("execution (public int com.hist.aop.CalculatorInterface.*(int,int))")
	public void JoinPointExpression() {}

重用切入点的使用就是 将通知中的"execution (public int com.hist.aop.CalculatorInterface.*(int,int))"表达式换位方法名:"JoinPointExpression() "

多个切面的优先级
在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的.
切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定.
实现 Ordered 接口, getOrder() 方法的返回值越小, 优先级越高.
若使用 @Order 注解, 序号出现在注解中
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43687990/article/details/102640880