软件设计之SSM(5)

软件设计之SSM(5)

路线图推荐:
【Java学习路线-极速版】【Java架构师技术图谱】
尚硅谷新版SSM框架全套视频教程,Spring6+SpringBoot3最新SSM企业级开发
资料可以去尚硅谷官网免费领取

学习内容:

AOP面向切面编程

  1. 代理
  2. AOP面向切面编程
  3. 获取通知细节信息
  4. 切点表达式
  5. 环绕通知
  6. 切面优先级

1、代理

代理模式是一种结构型设计模式,它的核心思想是:在访问对象时,通过代理对象进行间接访问。

静态代理

静态代理是指在编译时,代理类就已经存在,并且程序运行前,代理类的字节码已经生成。代理类和被代理类实现相同的接口,代理类内部调用被代理类的方法,同时在方法前后添加额外的逻辑。
静态代理的结构:

  • 接口:定义代理类和被代理类都要实现的方法。
  • 被代理类:实现接口,包含实际的业务逻辑。
  • 代理类:也实现接口,在调用被代理类方法的前后加入额外逻辑。
  • 缺点: 静态代理的一个主要缺点是,代理类与被代理类实现了相同的接口,当业务类(被代理类)变多时,你需要为每个类创建一个代理类,代码量大,且难以维护。

动态代理

动态代理相比静态代理更加灵活,它允许在运行时动态生成代理对象,避免了手动编写代理类的繁琐。Java 提供了两种动态代理机制:

  • JDK 动态代理: 适用于代理实现了接口的类(需要被代理的目标类必须实现接口,他会根据目标类的接口动态生成一个代理对象)。
    • 这个代理类对象:是一个动态生成的类的实例,这个动态生成的类实现你提供的接口。
  • CGLIB 动态代理: 适用于代理没有实现接口的类,通过字节码技术生成子类实现代理。
JDK动态代理介绍
  • Proxy.newProxyInstance() 方法是 JDK 动态代理的关键,用来生成代理对象。它在运行时创建一个代理它接受三个参数:
    • ClassLoader: 代理类的类加载器,一般使用目标对象的类加载器。
    • Interfaces: 代理对象实现的接口列表,这些接口也必须是目标对象实现的接口。
    • InvocationHandler: 代理对象的方法调用处理器。[实现具体要执行的非核心动作]
  • InvocationHandler是 JDK 动态代理的核心。它用于拦截代理对象的方法调用,并在此基础上执行代理逻辑。通过实现 InvocationHandler 接口的 invoke() 方法,代理可以在方法调用之前、之后或替代原始方法逻辑。
  • invoke() 方法接收三个参数(invoke() 方法会通过反射调用目标对象的实际方法):
    • proxy: 代理对象本身
    • method: 被调用的方法对象(目标类的方法)
    • args: 调用该方法时传递的参数。

2、AOP面向切面编程

AOP与OOP思维差异

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过分离横切关注点(Cross-Cutting Concerns)来增强代码的模块化。而OOP(Object-Oriented Programming,面向对象编程)则是一种通过类和对象来封装数据和行为的编程范式。

思维OOP(面向对象编程)AOP(面向切面编程)
核心理念通过类和对象封装数据和行为,实现代码复用、封装和多态等特性。通过分离横切关注点,将与业务逻辑无关的代码(如日志、事务等)从主业务逻辑中剥离。
关注点主要关注对象及其行为,强调如何设计对象以及对象之间的交互关系。主要关注横切关注点,即那些分散在不同模块中的公共需求,如日志、权限、事务管理等。
代码组织OOP关注将相关的数据和行为放到同一个类中,使对象内部的实现细节对外部不可见。AOP关注如何将某些行为(如日志记录)提取到独立的模块(切面),通过切入点与业务逻辑无缝结合。
模块化强调使用类来组织模块。类是最基本的模块化单位。强调通过**切面(Aspect)**将横切关注点与业务逻辑分离,以增强模块化。
扩展方式使用继承和实现接口来扩展功能。使用切面切入点动态注入功能,无需修改现有代码。
代码侵入性如果需要引入日志、权限管理等,可能需要在类的多个方法中显式编写这些代码。通过声明式的方式(如注解),将横切关注点注入业务逻辑,减少代码的侵入性。
适用场景适合封装具有特定职责的对象或类,强调业务逻辑的实现。适合处理分散的、重复的横切关注点,如日志、事务、异常处理等。

AOP核心名词理解

名词含义说明
横切关注点系统中跨多个模块的需求,如日志或事务(非核心代码处理场景)。
通知(增强)在特定连接点执行的增强代码,如前置通知或后置通知。
连接点程序执行中的特定点,如方法调用。
切入点匹配连接点的表达式,用于定义哪些方法/类需要增强。
切面横切关注点的具体实现,包含通知和切入点。
目标对象被增强(要被切入)的对象。
代理实际执行增强逻辑的对象,代替目标对象执行方法。
织入将切面与目标对象绑定的过程,通常在运行时完成。

快速实现

spring-aop: 用于引入Spring的AOP功能,支持基于代理的AOP(JDK动态代理和CGLIB代理)。
spring-aspects: 用于引入对AspectJ的支持,提供编译时、加载时和注解式的AspectJ功能。
@Aspect: 表示这个类是一个切面类
@Before: 是 Spring AOP 中的一个通知类型,表示在目标方法执行之前要执行的增强逻辑。
切入点表达式: 用于定义在哪些连接点上应用增强逻辑。

步骤关键点描述
1. 引入依赖pom.xml中加入spring-aopspring-aspects依赖,确保项目支持AOP。
2. 编写目标对象创建业务类或接口,定义需要被增强的业务逻辑。
3. 定义切面类使用@Aspect注解定义切面类,利用@Before@After等通知注解添加增强逻辑,使用@Pointcut定义切入点。
4. 启用AspectJ支持在配置类中使用@EnableAspectJAutoProxy注解,开启Spring AOP的注解支持。
5. 编写控制器或测试创建控制器或其他测试类,调用目标类的方法,验证切面的增强逻辑。
6. 测试并验证运行项目,通过访问控制器或测试类,检查AOP增强是否按预期工作。

3、获取通知细节信息

获取通知细节信息指的是在通知方法(如@Before、@After等)中获取有关被增强的目标方法的信息,例如方法名、参数、目标对象、类名等。

JointPoint接口

需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参

// @Before注解标记前置通知方法
// value属性:切入点表达式,告诉Spring当前通知方法要套用到哪个目标方法上
// 在前置通知方法形参位置声明一个JoinPoint类型的参数,Spring就会将这个对象传入
// 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
@Before(value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))")
public void printLogBeforeCore(JoinPoint joinPoint) {
    
    // 1.通过JoinPoint对象获取目标方法签名对象
    // 方法的签名:一个方法的全部声明信息
    Signature signature = joinPoint.getSignature();
    
    // 2.通过方法的签名对象获取目标方法的详细信息
    String methodName = signature.getName();
    System.out.println("methodName = " + methodName);
    
    int modifiers = signature.getModifiers();
    System.out.println("modifiers = " + modifiers);
    
    String declaringTypeName = signature.getDeclaringTypeName();
    System.out.println("declaringTypeName = " + declaringTypeName);
    
    // 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表
    Object[] args = joinPoint.getArgs();
    
    // 4.由于数组直接打印看不到具体数据,所以转换为List集合
    List<Object> argList = Arrays.asList(args);
    
    System.out.println("[AOP前置通知] " + methodName + "方法开始了,参数列表:" + argList);
}

获取返回值

在返回通知中,通过@AfterReturning 注解的returning属性获取目标方法的返回值!

// @AfterReturning注解标记返回通知方法
// 在返回通知中获取目标方法返回值分两步:
// 第一步:在@AfterReturning注解中通过returning属性设置一个名称
// 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
@AfterReturning(
        value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))",
        returning = "targetMethodReturnValue"
)
public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) {
    
    String methodName = joinPoint.getSignature().getName();
    
    System.out.println("[AOP返回通知] "+methodName+"方法成功结束了,返回值是:" + targetMethodReturnValue);
}

异常对象捕捉

在异常通知中,通过@AfterThrowing注解的throwing属性获取目标方法抛出的异常对象

// @AfterThrowing注解标记异常通知方法
// 在异常通知中获取目标方法抛出的异常分两步:
// 第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称
// 第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们
@AfterThrowing(
        value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))",
        throwing = "targetMethodException"
)
public void printLogAfterCoreException(JoinPoint joinPoint, Throwable targetMethodException) {
    
    String methodName = joinPoint.getSignature().getName();
    
    System.out.println("[AOP异常通知] "+methodName+"方法抛异常了,异常类型是:" + targetMethodException.getClass().getName());
}

4、切点表达式

在这里插入图片描述

  1. 权限修饰符:public private 直接描述对应修饰符即可
  2. 方法返回值 int String void 直接描述返回值类型,其中execution(*) 表示 不考虑权限修饰符与方法返回值
  3. 指定包的地址
 固定的包: com.atguigu.api | service | dao
 单层的任意命名: com.atguigu.*  = com.atguigu.api  com.atguigu.dao  * = 任意一层的任意命名
 任意层任意命名: com.. = com.atguigu.api.erdaye com.a.a.a.a.a.a.a  ..任意层,任意命名 用在包上!
 注意: ..不能用作包开头   public int .. 错误语法  com..
 找到任何包下: *..
  1. 指定类名称
固定名称: UserService
任意类名: *
部分任意: com..service.impl.*Impl
任意包任意类: *..*
  1. 指定方法名称:语法和类名一致
  2. 方法参数
       具体值: (String,int) != (int,String) 没有参数 ()
       模糊值: 任意参数 有 或者 没有 (..)  ..任意参数的意识
       部分具体和模糊:
         第一个参数是字符串的方法 (String..)
         最后一个参数是字符串 (..String)
         字符串开头,int结尾 (String..int)
         包含int类型(..int..)

切点表达式的复用

增强方法的切点表达式容易冗余,也不方便统一维护。我们可以将切点提取,在增强上进行引用即可!
切点统一管理:将需要被引用的切点表达式方法统一放在类中管理

  • 在需要使用表达式的地方调用即可
@Before(value = "com.atguigu.AtguiguPointCut.atguiguGlobalPointCut()")
@Component
public class AtguiguPointCut {
    
    @Pointcut(value = "execution(public int *..Calculator.sub(int,int))")
    public void atguiguGlobalPointCut(){}
    
    @Pointcut(value = "execution(public int *..Calculator.add(int,int))")
    public void atguiguSecondPointCut(){}
    
    @Pointcut(value = "execution(* *..*Service.*(..))")
    public void transactionPointCut(){}
}

5、环绕通知

环绕通知(@Around)是 Spring AOP 中的一种通知类型,允许你在目标方法执行的前后插入自定义的逻辑。它是最强大和灵活的通知类型,因为你可以控制目标方法的执行,也可以决定是否执行该方法,并可以修改方法的参数和返回值。

  • userServiceMethods() 方法定义了切入点,匹配 UserService 中的所有方法。
  • logAround(ProceedingJoinPoint joinPoint) 方法是环绕通知。
  • 调用 joinPoint.proceed() 方法执行目标方法
@Aspect
@Component
public class LoggingAspect {

    // 定义切入点,匹配 UserService 中的所有方法
    @Pointcut("execution(* com.example.service.UserService.*(..))")
    public void userServiceMethods() {}

    // 环绕通知
    @Around("userServiceMethods()")
    public Object logAround(ProceedingJoinPoint joinPoint) {
        Object result = null; // 用于存储目标方法的返回值
        try {
            // 获取方法名
            String methodName = joinPoint.getSignature().getName();
            // 获取方法参数
            Object[] methodArgs = joinPoint.getArgs();
            
            // 在方法执行前执行的逻辑
            System.out.println("Logging: Before method: " + methodName);
            System.out.println("Arguments: " + Arrays.toString(methodArgs));

            // 调用目标方法
            result = joinPoint.proceed(); // 执行目标方法

            // 在方法执行后执行的逻辑
            System.out.println("Logging: After method: " + methodName);
            System.out.println("Result: " + result);
        } catch (Throwable throwable) {
            // 处理目标方法抛出的异常
            System.err.println("Logging: Exception in method: " + joinPoint.getSignature().getName());
            System.err.println("Exception message: " + throwable.getMessage());
            // 重新抛出异常,便于后续处理
            throw throwable;
        }
        return result; // 返回目标方法的返回值
    }
}

6、切面优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

  • 优先级高的切面:外面
  • 优先级低的切面:里面

使用 @Order 注解可以控制切面的优先级:

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值