(二)AOP面向切面编程

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.10</version>
</dependency>

Spring整合了AspectJ框架的一部分。

核心概念

切面:AOP中的核心模块化单元。它是一个,这个类封装了一个或多个横切关注点(即通知)和确定这些通知执行位置的规则(即切点)。比如创建一个 LoggingAspect 类,专门负责所有与日志相关的逻辑。

连接点:在应用程序执行过程中,可以插入切面代码的特定点。这是AOP框架提供的一个“钩子”机会。如:

  • 方法调用
  • 方法执行
  • 异常抛出
  • 字段访问(部分AOP框架支持)

切点:一个谓词或表达式,用于匹配筛选出那些需要应用通知的特定连接点。它定义了“在哪里”。execution(* com.example.service.*.*(..)) 这个表达式匹配 com.example.service 包下所有类的所有方法执行连接点。

通知:切面在特定切点上要执行的动作。它是包含横切逻辑(如写日志)的代码块。它定义了“做什么”以及“何时做”。

  • 类型(根据执行时机划分):

    • 前置通知:在目标方法执行前执行。
    • 后置通知:在目标方法成功执行后执行(无论返回什么,只要不抛异常)。
    • 返回通知:在目标方法成功执行并返回结果后执行(可以访问返回值)。
    • 异常通知:在目标方法抛出异常后执行(可以访问异常对象)。
    • 环绕通知最强大的通知,包裹了目标方法的整个执行过程。可以在调用目标方法之前和之后执行自定义行为,甚至可以决定是否调用目标方法。

编织(织入):将切面应用到目标对象(被代理的对象)以创建代理对象的过程。这是实现AOP效果的关键步骤。

  • 编织时机

    • 编译时编织:在源代码编译成字节码时进行(如AspectJ编译器 ajc)。
    • 类加载时编织:在Java类被JVM加载时进行(需借助特殊的类加载器)。
    • 运行时编织:在应用程序运行时,通过动态代理技术实现(Spring AOP默认采用的方式)。

引入:一种特殊的通知,允许为现有的类动态地添加新的方法或属性(以及实现新的接口)。这改变了类的结构,而不仅仅是行为。例如,可以通过引入,让一个普通的业务类实现 Auditable 接口,并拥有对应的 setLastModifiedBy 方法,而无需修改其源代码。

一、使用配置实现AOP

目标类:

public class UserServiceImpl {
    public void saveUser() {
        System.out.println("正在保存用户信息...");
    }
}

切面类:

public class MyLogger {
    // 前置通知
    public void beforeLog() {
        System.out.println("[日志] 方法开始执行...");
    }
    
    // 后置通知
    public void afterLog() {
        System.out.println("[日志] 方法执行完毕。");
    }
}

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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.example.UserServiceImpl" />
    <bean id="myLogger" class="com.example.MyLogger" />

    <aop:config>
        <!--切入点-->
        <aop:pointcut id="logPointcut" expression="execution(* com.example.UserServiceImpl.saveUser(..))" />
        <!--切面-->
        <aop:aspect ref="myLogger">
            <aop:before method="beforeLog" pointcut-ref="logPointcut" />
            <aop:after method="afterLog" pointcut-ref="logPointcut" />
        </aop:aspect>
    </aop:config>

</beans>

<aop:aspect> 内部,我们可以根据需要选择不同的通知类型:

标签类型执行时机
<aop:before>前置通知目标方法运行之前
<aop:after-returning>后置通知目标方法正常结束之后
<aop:after-throwing>异常通知目标方法抛出异常时执行
<aop:after>最终通知无论是否异常,方法结束后都执行
<aop:around>环绕通知手动控制方法何时执行(功能最强)

不同切面之间的重合,使用 优先级(Order)控制顺序,数值越小,优先级越高。

<aop:aspect ref="securityAspect" order="1">
    <aop:before method="check" pointcut-ref="commonPointcut" />
</aop:aspect>

<aop:aspect ref="logAspect" order="2">
    <aop:before method="log" pointcut-ref="commonPointcut" />
</aop:aspect>

二、使用接口实现AOP

切面类实现MethodBeforeAdvice

public class Student {
    public String study(){
        System.out.println("学习!");
        return "study";
    }
}

例如:aop:before、aop:after-returning

public class StudentAOP implements MethodBeforeAdvice, AfterReturningAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("通过Advice实现AOP");
    }

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("我是方法执行之后的结果,方法返回值为:"+returnValue);
    }
}

或者 aop:around

public class StudentAOP implements MethodInterceptor {   //实现MethodInterceptor接口
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {  //invoke方法就是代理方法
        Object value = invocation.proceed();   //需要手动proceed()才能调用原方法
        return value+"增强";
    }
}

xml 配置:

<bean id="student" class="org.example.entity.Student"/>
<bean id="studentAOP" class="org.example.entity.StudentAOP"/>
<aop:config>
    <aop:pointcut id="test" expression="execution(* org.example.entity.Student.study())"/>
    <aop:advisor advice-ref="studentAOP" pointcut-ref="test"/>
</aop:config>

三、使用注解实现AOP

在主类添加@EnableAspectJAutoProxy注解,开启AOP注解支持:

@EnableAspectJAutoProxy
@ComponentScan("org.example.entity")
@Configuration
public class MainConfiguration {
    
}

目标类:

@Component
public class Student {
    public void study(){
        System.out.println("我是学习方法!");
    }
}

使用 @Aspect注解实现切面

aop:before

@Aspect
@Component
public class StudentAOP {
    @Before("execution(* org.example.entity.Student.study())")
    public void before(){
        System.out.println("我是之前执行的内容!");
    }   
}
@Aspect
@Component
public class StudentAOP {
    @Before("execution(* org.example.entity.Student.study())")
    public void before(JoinPoint point){
        System.out.println("参数列表:"+ Arrays.toString(point.getArgs()));
        System.out.println("我是之前执行的内容!");
    }
}

命名绑定模式

@Aspect
@Component
public class StudentAOP {
    @Before(value = "execution(* org.example.entity.Student.study(..)) && args(str)", argNames = "str")
    //命名绑定模式就是根据下面的方法参数列表进行匹配
    //这里args指明参数,注意需要跟原方法保持一致,然后在argNames中指明
    public void before(String str){
        System.out.println(str);   //可以快速得到传入的参数
        System.out.println("我是之前执行的内容!");
    }
}

aop:after-returning

使用returning指定接收目标方法返回值的参数returnVal,以在切面方法内使用

@Aspect
@Component
public class StudentAOP {
    @AfterReturning(value = "execution(* org.example.entity.Student.study())", argNames = "returnVal", returning = "returnVal")
    //使用returning指定接收目标方法返回值的参数returnVal,以在切面方法内使用
    public void afterReturn(Object returnVal){
        System.out.println("返回值是:"+returnVal);
    }
}

aop:around

@Aspect
@Component
public class StudentAOP {
    @Around("execution(* com.test.bean.Student.test(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("方法执行之前!");
        Object val = point.proceed();
        System.out.println("方法执行之后!");
        return val;
    }
}

附:execution表达式的书写方法

基本语法结构

execution([修饰符] 返回类型 [包名.类名.]方法名(参数列表) [throws 异常类型])

注意[] 表示可选部分。实际书写时不包含 []

各部分详解

  1. 修饰符(可选)
  • 指定方法的访问权限:publicprivateprotected
  • 通常省略,表示匹配所有访问权限
  • 示例execution(public * *(..)) - 匹配所有 public 方法
  1. 返回类型(必需)
  • 指定方法的返回类型
  • * 表示匹配任何返回类型
  • 示例

    • execution(String *(..)) - 匹配返回 String 的方法
    • execution(void *(..)) - 匹配返回 void 的方法
  1. 包名.类名.(可选)
  • 指定完全限定类名
  • 支持通配符
  • 示例

    • execution(* com.example.service.*.*(..)) - service 包下的所有类
    • execution(* com.example.service..*.*(..)) - service 包及其子包下的所有类
  1. 方法名(必需)
  • 指定方法名称
  • 支持通配符
  • 示例

    • execution(* save*(..)) - 匹配所有以 save 开头的方法
    • execution(* *Service.*(..)) - 匹配所有以 Service 结尾的类中的所有方法
  1. 参数列表(必需)
  • () - 匹配无参数方法
  • (..) - 匹配任意数量、任意类型的参数(0个或多个)
  • (*) - 匹配一个任意类型的参数
  • (String, *) - 匹配两个参数,第一个是 String,第二个任意
  • (String, ..) - 匹配至少一个参数,第一个是 String
  1. throws 异常类型(可选)
  • 指定方法抛出的异常类型
  • 较少使用
  • 示例execution(* *(..) throws IOException)

常用通配符

通配符含义示例
*匹配任意数量字符(除了.com.*.service
..1)匹配任意数量子包 2)匹配任意数量参数com..service (..)
+匹配指定类型及其子类com.example.BaseService+
分类: Java-Backend 标签: JavaSpring

评论

暂无评论数据

暂无评论数据

目录