Java – SpringAOP – 基于XML的AOP
简介
Spring 支持通过XML配置AOP切面编程,可以事先创建切面类,和实体类,并通过SpringXML进行配置。
详细的通过注解配置AOP基础可查看下面文章了解。
实现AOP(使用JDK动态代理)
添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.24</version>
</dependency>
创建实体类接口
Spring一旦使用JDK动态代理的模式,在底层会自动把实体类从ioc中排除(即即使用在实体类中定义了 @Component
也不能通过 getBean
获得)
原因是:代理的目的就是在实体类的外面再封装一层类,ioc直接控制外层类。如果使用了代码,而ioc依然可以通过getBean
获取到实体类,那么用于代理的外层包装类就没有意义了,所以Spring在这方面进行了限制。如下图为代理模式下的实体类与外层类的关系。
// 创建一个运算的接口
public interface Calculator {
// 定义一个加法
int add(int x, int y);
// 定义一个减法
int min(int x, int y);
// 定义一个乘法
int sub(int x, int y);
// 定义一个除法
int div(int x, int y);
}
创建实现类
实现类为实现接口方法的类,也是实体类
// 使用注解定义实体类为 ioc Bean 类,归由ioc管理
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int x, int y) {
int result = x + y;
System.out.println("内部方法add");
return result;
}
@Override
public int min(int x, int y) {
int result = x - y;
System.out.println("内部方法min");
return result;
}
@Override
public int sub(int x, int y) {
int result = x * y;
System.out.println("内部方法sub");
return result;
}
@Override
public int div(int x, int y) {
int result = x / y;
System.out.println("内部方法div");
return result;
}
}
JDK代理模式只能通过接口来获取实体类,不能直接 new
实体类(上面说过,开启了代理就不能直接控制实体类,且Spring也不允许)。
创建切面类
切面类,就是用于存放那些在实体类中抽取出来的非核心业务代码的类,它会随着实体类的执行,按照通知的周期来跟随执行。
@Component
public class LoggerAspect {
/**
* 定义一个前置通知方法
* 使用 xml 定义前置通知方法不需要加注解,在xml中声明定义即可
*/
public void BeforeMethod(JoinPoint joinPoint) {
System.out.println("前置通知方法");
Signature signature = joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
System.out.println("方法名是" + signature.getName());
System.out.println("参数值为" + Arrays.toString(args));
}
/**
* 要使用复用切入点表达式,只要传入注解该表达式的方法名即可
*/
public void AfterMethod() {
}
public void After(){}
public void AfterReturningMethod(JoinPoint joinPoint, Object result) {
System.out.println(result);
}
public void AfterThrowingMethod(JoinPoint joinPoint, Exception e) {
System.out.println("异常抛出:" + e);
}
public Object AroundMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("此处相当于@Before注解中的执行通知方法");
/**
* ProceedingJoinPoint 是一个带执行的切入点对象,它可以控制实体类方法何时执行
* joinPoint.proceed() 则是执行实体类方法后得出的结果
* joinPoint.proceed() 可以看作是实体类方法全部代码的调用方法,并把执行结果返回
*/
result = joinPoint.proceed();
System.out.println("此处相当于@AfterReturning注解中的执行通知方法");
} catch (Throwable e) {
System.out.println("此处相当于@AfterThrowing注解中的执行通知方法");
}finally {
System.out.println("此处相当于@After注解中的执行通知方法");
}
return result;
}
}
注意:JoinPoint
接收的是来自实体类方法中的各种信息,包括方法名、接收参数等信息。
通知方法的接收参数
切面通知方法接收参数有三个,分别是【ProceedingJoinPoint】、【JoinPoint】、【Throwable】
1.【ProceedingJoinPoint】是用于使用 Around 环绕切面通知,Around 环绕切面通知相当于是【before】,【after-returning】,【after-throwing】,【after】四个切面方法。因此要在 Around 通知方法中判断什么时候调用什么切面方法,需要使用 ProceedingJoinPoint
ProceedingJoinPoint => proceed() 方法控制被代理的方法执行。
使用 try-catch-finally 定义【after-returning】、【after-throwing】、【after】方法情况
当 try 成功完成后,则是【after-returning】切面执行的方法
当 try 发生异常时,catch 则是【after-throwing】切面执行的方法
当 finally 执行时,则是【after】切面执行的方法。
2.【JoinPoint】仅提供被代理的方法的详细信息,如取得被代理的方法的参数、方法名、所在包名、切面表达式等。
3.【Throwable】是当被代理的方法发生异常时,所接收的异常信息。
注意:切面方法中如果要接收【Throwable】参数时,需要在xml中配置接收异常的参数名,如
<aop:after-throwing method="before" pointcut-ref="method1" throwing="e"></aop:after-throwing>
SpringXML切面类配置
<!-- 扫描包中的注解ioc管理实体类-->
<context:component-scan base-package="cn.unsoft.spring.aop.xml"></context:component-scan>
<!-- 定义AOP切面-->
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* cn.unsoft.spring.aop.xml.CalculatorImpl.*(..))"></aop:pointcut>
<!-- 定义一个切面类 ref 则为这个类的ioc ID,因为使用了注解+扫描,所以ioc自动创建bean了-->
<!-- @Aspect-->
<!-- @Order(99)-->
<aop:aspect ref="loggerAspect" order="99">
<!-- 定义 @before("pointCut") 通知-->
<aop:before method="AfterMethod" pointcut-ref="pointCut"></aop:before>
<!-- 定义 @AfterReturning(value = "pointCut", returning = "result") 通知-->
<aop:after-returning method="AfterReturningMethod" returning="result" pointcut-ref="pointCut"></aop:after-returning>
<!-- 定义 @AfterThrowing(value = "pointCut", throwing= "e") 通知-->
<aop:after-throwing method="AfterThrowingMethod" throwing="e" pointcut-ref="pointCut"></aop:after-throwing>
<!-- 定义 @After("pointCut") 通知-->
<aop:after method="After" pointcut-ref="pointCut"></aop:after>
<!-- 定义 @Around("pointCut") 通知-->
<aop:around method="AroundMethod" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
</aop:config>
Advisor 接口实现AOP
Spring 提供一种接口实现的方式配置AOP通知方法的方式。
如果用户想自定义AOP通知,可以使用Aspect 方式,来指定哪个方法哪个时机被代理,执的通知方法是哪个类中的切面方法。
Aspect 中,如果想配置切面方法,需要在xml中(或注解)中定义,如上一节【SpringXML切面类配置】
Advisor 中,切面类,可以通过实现Advisor的子接中,实现对应的切面方法,也可以实现。
在 Advisor -> Advice 的子接口中,有多个AOP切面接口,通过实现接口方法
// 把实现advisor接口的类放到Bean容器中管理
<bean id="advisor" class="cn.unsoft.advice.MyAdvisor"></bean>
<!-- aop切面配置 -->
<aop:config>
<!-- 定义需要被切面增强的类 -->
<aop:pointcut id="pointcut1" expression="execution(* cn.unsoft.service.impl.*(..))"/>
<aop:advisor advice-ref="advisor" pointcut-ref="pointcut1"></aop:advisor>
</aop:config>
通过IoC取得实体类
IoC不能直获取实体类,因为实体类已经被代理封装,因此只能通过它实现的接口进行获取。
// 获取ioc
ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-xml.xml");
Calculator calculator = ioc.getBean(Calculator.class);
calculator.add(1,1);
==>> 输出结果
前置通知方法
内部方法add
execution 表达式
execution 基础表达式格式如下:
execution([访问修饰符] 返回值类型 包名.类名.方法名 (参数))
其中,
1.访问修饰符可以省略不写;
2.返回值类型、某一级包名、类名、方法名 可以使用*表示任意;
3.包名与类名之间使用单点.表示该包下的类,使用双点.表示该包及其子包下的类;
4.参数列表可以使用两个点.表示任意参数。
//表示访问修饰符为public、无返回值、在cn.unsoft.aop包下的TargetImp1类的无参方法show
execution(public void cn.unsoft.aop.TargetImpl.show())
//表述cn.unsoft.aop包下的TargetImpl类的任意方法
execution(* cn.unsoft.aop.TargetImpl.*(..))
//表示cn.unsoft.aop包下的任意类的任意方法
execution(* cn.unsoft.aop.*.*(..))
//表示cn.unsoft.aop包及其子包下的任意类的任意方法
execution(* cn.unsoft.aop..*.*(..))
//表示任意包中的任意类的任意方法
execution(* *..*.*(..))
Aspect 的通知类型
通知名称 | 配置方式 | 执行时机 |
前置通知 | <aop:before > | 目标方法执行之前执行 |
后置通知 | <aop:after-returning > | 目标方法执行之后执行,目标方法异常时,不在执行 |
环绕通知 | <aop:around > | 目标方法执行前后执行,目标方法异常时,环绕后方法不在执行 |
异常通知 | <aop:after-throwing > | 目标方法抛出异常时执行 |
最终通知 | <aop:after > | 不管目标方法是否有异常,最终都会执行 |
简单实例
可下载查看对SpringAop的简单实例项目;
https://www.tzming.com/wp-content/uploads/2023/filesdown/SpringAopDemo.rar
共有 0 条评论