5.3 Spring5源码--Spring AOP使用接口方式实现

时间:2023-12-11 08:37:38

Spring 提供了很多的实现AOP的方式:Spring 接口方式,schema配置方式和注解.

本文重点介绍Spring使用接口方式实现AOP. 使用接口方式实现AOP以了解为目的. 更好地理解动态代理. 通常我们使用的更多的是使用注解的方式实现AOP

下面来看看如何实现接口方式的AOP

一. 环境准备

要在项目中使用Spring AOP 则需要在项目中导入除了spring jar包之外, 还需要引入aspectjrt.jar,aspectjweaver.jar,aopalliance.jar ,spring-aop-3.2.0.M2.jar和cglib.jar 

二、Spring接口方式实现AOP步骤

使用Spring aop接口方式实现aop, 可以通过自定义通知来供Spring AOP识别. 常见的自己定义通知有:前置通知, 后置通知, 返回通知, 异常通知, 环绕通知. 对应实现的接口是:

  • 前置通知: MethodBeforeAdvice
  • 后置通知: AfterAdvice
  • 返回通知:AfterReturningAdvice
  • 异常通知:ThrowsAdvice
  • 环绕通知:MethodInterceptor

实现步骤如下:

1. 业务接口实现

package com.lxl.www.aop.interfaceAop;

/**
* 使用接口方式实现AOP, 默认通过JDK的动态代理来实现. 非接口方式, 使用的是cglib实现动态代理
*
* 业务接口类-- 计算器接口类
*
* 定义三个业务逻辑方法
*/
public interface IBaseCalculate { int add(int numA, int numB); int sub(int numA, int numB); int div(int numA, int numB); int multi(int numA, int numB); int mod(int numA, int numB); }

2. 业务类

package com.lxl.www.aop.interfaceAop;//业务类,也是目标对象

import com.lxl.www.aop.Calculate;

import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service; /**
* 业务实现类 -- 基础计算器
*/
@Service
public class BaseCalculate implements IBaseCalculate { @Override
public int add(int numA, int numB) {
System.out.println("执行目标方法: add");
return numA + numB;
} @Override
public int sub(int numA, int numB) {
System.out.println("执行目标方法: sub");
return numA - numB;
} @Override
public int multi(int numA, int numB) {
System.out.println("执行目标方法: multi");
return numA * numB;
} @Override
public int div(int numA, int numB) {
System.out.println("执行目标方法: div");
return numA / numB;
} @Override
public int mod(int numA, int numB) {
System.out.println("执行目标方法: mod"); int retVal = ((Calculate) AopContext.currentProxy()).add(numA, numB);
return retVal % numA;
}
}

3. 通知类

前置通知

package com.lxl.www.aop.interfaceAop;

import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* 定义前置通知
*/
@Component
public class BaseBeforeAdvice implements MethodBeforeAdvice { /**
*
* @param method 切入的方法
* @param args 切入方法的参数
* @param target 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("===========进入beforeAdvice()============"); System.out.println("目标对象:" + target);
System.out.println("方法名: "+method); System.out.println("即将进入切入点方法");
} }

后置通知

package com.lxl.www.aop.interfaceAop;

import org.aspectj.lang.annotation.AfterReturning;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; public class BaseAfterReturnAdvice implements AfterReturningAdvice { /**
*
* @param returnValue 切入点执行完方法的返回值,但不能修改
* @param method 切入点方法
* @param args 切入点方法的参数数组
* @param target 目标对象
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("==========进入afterReturning()=========== \n");
System.out.println("切入点方法执行完成"); System.out.println("后置通知--目标对象:" + target);
System.out.println("后置通知--方法名: "+method);
System.out.println("后置通知--方法入参: "+ args.toString());
System.out.println("后置通知--方法返回值: "+ returnValue);
} }

异常通知

package com.lxl.www.aop.interfaceAop;

import org.springframework.aop.ThrowsAdvice;
import org.springframework.stereotype.Component; import java.lang.reflect.Method;
@Component
public class BaseAfterThrowsAdvice implements ThrowsAdvice { /**
* @param method 可选:切入的方法
* @param args 可选:切入的方法的参数
* @param target 可选:目标对象
* @param throwable 必填 : 异常子类,出现这个异常类的子类,则会进入这个通知。
*/
public void afterThrowing(Method method, Object[] args, Object target, Throwable throwable) {
System.out.println("出错啦");
}
}

环绕通知

package com.lxl.www.aop.interfaceAop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* 环绕通知
*/
@Component
public class BaseAroundAdvice implements MethodInterceptor { /**
* invocation :连接点
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("===========around环绕通知方法 开始==========="); // 调用目标方法之前执行的动作
System.out.println("环绕通知--调用方法之前: 执行"); // 调用方法的参数
Object[] args = invocation.getArguments();
// 调用的方法
Method method = invocation.getMethod();
// 获取目标对象
Object target = invocation.getThis();
System.out.println("输入参数:" + args[0] + ";" + method + ";" + target); // 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
Object returnValue = invocation.proceed(); System.out.println("环绕通知--调用方法之后: 执行"); System.out.println("输出参数:" + args[0] + ";" + method + ";" + target + ";" + returnValue); System.out.println("===========around环绕通知方法 结束==========="); return returnValue;
} }

4. 自定义切点

package com.lxl.www.aop.interfaceAop;

/**
* 切点
*
* 继承NameMatchMethodPointcut类,来用方法名匹配
*/
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; @Component
public class Pointcut extends NameMatchMethodPointcut { private static final long serialVersionUID = 3990456017285944475L; @SuppressWarnings("rawtypes")
@Override
public boolean matches(Method method, Class targetClass) {
// 设置单个方法匹配
this.setMappedName("add");
// 设置多个方法匹配
String[] methods = { "add", "div" }; //也可以用“ * ” 来做匹配符号
// this.setMappedName("get*"); this.setMappedNames(methods); return super.matches(method, targetClass);
} }

5. 配置xml文件

<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
> <!-- ==============================aop配置================================ -->
<!-- 声明一个业务类 -->
<bean id="baseCalculate" class="com.lxl.www.aop.interfaceAop.BaseCalculate"/> <!-- 声明通知类 -->
<bean id="baseBefore" class="com.lxl.www.aop.interfaceAop.BaseBeforeAdvice"/>
<bean id="baseAfterReturn" class="com.lxl.www.aop.interfaceAop.BaseAfterReturnAdvice"/>
<bean id="baseAfterThrows" class="com.lxl.www.aop.interfaceAop.BaseAfterThrowsAdvice"/>
<bean id="baseAround" class="com.lxl.www.aop.interfaceAop.BaseAroundAdvice"/> <!-- 指定切点匹配类 -->
<bean id="pointcut" class="com.lxl.www.aop.interfaceAop.Pointcut"/> <!-- 包装通知,指定切点 -->
<bean id="matchBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<ref bean="pointcut"/>
</property>
<property name="advice">
<ref bean="baseBefore"/>
</property>
</bean> <!-- 使用ProxyFactoryBean 产生代理对象 -->
<bean id="businessProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理对象所实现的接口 ,如果有接口可以这样设置 -->
<property name="proxyInterfaces">
<value>com.lxl.www.aop.interfaceAop.IBaseCalculate</value>
</property> <!-- 设置目标对象 -->
<property name="target">
<ref bean="baseCalculate"/>
</property>
<!-- 代理对象所使用的拦截器 -->
<property name="interceptorNames">
<list>
<value>matchBeforeAdvisor</value>
<value>baseAfterReturn</value>
<value>baseAround</value>
</list>
</property>
</bean>
</beans>

6. 方法入口

package com.lxl.www.aop.interfaceAop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class InterfaceMainClass{ public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/aop.xml");
BaseCalculate calculate = (BaseCalculate) context.getBean("baseCalculate");
calculate.add(1, 3);
} }

三. 分析

各种类型通知的执行顺序: 前置方法会在切入点方法之前执行,后置会在切入点方法执行之后执行,环绕则会在切入点方法执行前执行同事方法结束也会执行对应的部分。主要是调用proceed()方法来执行切入点方法。来作为环绕通知前后方法的分水岭

在xml 配置 businessProxy这个bean的时候,ProxyFactoryBean类中指定了,proxyInterfaces参数。这里把他配置了IBaseCalculate接口。因为在项目开发过程中,往往业务类都会有对应的接口,以方便利用IOC解耦。但Spring AOP却也能支持没有接口的代理。这就是为什么需要导入cglib.jar包了。看过spring的源码,知道在目标切入对象如果有实现接口,spring会默认使用jdk动态代理来实现代理类。如果没有接口,则会通过cglib来实现代理类。

  这个业务类现在有 前置通知,后置通知,环绕三个通知同时作用,可能以及更多的通知进行作用。那么这些通知的执行顺序是怎么样的?就这个例子而言,同时实现了三个通知。在例 子xml中,则显示执行before通知,然后执行around的前处理,执行切点方法,再执行return处理。最后执行around的后处理。经过测 试,知道spring 处理顺序是按照xml配置顺序依次处理通知,以队列的方式存放前通知,以压栈的方式存放后通知。所以是前通知依次执行,后通知到切入点执行完之后,从栈里 在后进先出的形式把后通知执行。
  在实现过程中发现通知执行对应目标对象的整个类中的方法,如何精确到某个方法,则需要定义一个切点匹配的方式:spring提供了方法名匹配或正则方式来匹配。然后通过DefaultPointcutAdvisor来包装通知,指定切点。

 使用接口方式配置起来,可见代码还是非常的厚重的,定义一个切面就要定义一个切面类,然而切面类中,就一个通知方法,着实没有必要。所以Spring提供了,依赖aspectj的schema配置和基于aspectj 注解方式。这两种方式非常简单方便使用,也是项目中普遍的使用方式。

as