事务管理-AOP

spring事务管理

对员工管理系统删除员工的优化

Transactional注解:spring框架中已封装好,将注解加入到所用方法、类、接口(通常用在多次数据访问修改的方法上),spring则会对相应方法进行事务管理,遇到异常自动进行事务回滚

   @Transactional//将当前方法交给spring框架统一进行事务管理
    @Override
    public void delete(Integer id) {
        deptMapper.deleteByID(id);//删除员工

        empMapper.deleteByDeptId(id);//删除部门对应员工
    }

Transactinoal日志配置文件

    #spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

事务管理属性

rollbackFor属性:

默认情况下,只有运行时异常才会回滚事务

rollbackFor属性则规定了事务回滚的异常类型

@Transactional(rollbackFor = Exception.class)
//所有异常都会回滚事务

propagation属性(传播行为)

    public void delete(Integer id) {
        try {
            deptMapper.deleteByID(id);//删除员工
            int i = 1/0;
            empMapper.deleteByDeptId(id);//删除部门对应员工

        } finally {
            DeptLog deptLog = new DeptLog();
            deptLog.setCreateTime(LocalDateTime.now());
            deptLog.setDescription("执行了解散部门,解散部门为" + id + "号部门");
            deptLogService.insert(deptLog);//调用操作日志记录方法,与当前事务相互独立
        }
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)/* 当被事务调用时,该方法会创建一个新事务
     ,并将原事务挂起直到当前事务完成 */
    @Override
    public void insert(DeptLog deptLog) {
        deptLogMapper.insert(deptLog);
    }//记录操作日志,无论是否原事务是否抛出异常

AOP(面向切面编程/面向特定方法编程)⭐️⭐️

AOP(Aspect-Oriented Programming),即面向切面编程,是一种编程范式,它旨在解决软件开发中的横切关注点问题。

核心思想:分离关注点

想象一下,您的应用中有许多不同的业务逻辑(比如用户注册、订单处理、部门解散)。在这些业务逻辑中,有一些功能是重复出现的、与核心业务无关的,例如:

  • 日志记录(记录每个方法开始和结束)

  • 事务管理(确保数据库操作要么全成功要么全失败)

  • 权限检查(验证用户是否有权执行某个操作)

在传统编程中,您必须在每个业务方法中都重复编写这些代码,导致代码冗余和难以维护。

AOP 的解决方式

AOP 将这些重复的、分散在各个业务逻辑中的功能(横切关注点抽象并集中起来,形成一个独立的模块,这个模块就叫做 “切面”(Aspect)

  • 切面 (Aspect): 封装了横切关注点的模块(如事务切面、日志切面)。

  • 切入点 (Pointcut): 决定切面在哪里执行(例如:在所有以 Service 结尾的类的方法执行时)。

  • 通知 (Advice): 切面在特定切入点要执行的动作(例如:在方法之前执行日志记录,在方法之后执行事务提交)。

总结

AOP 的目的就是让您只关注核心业务逻辑,而将那些重复的、通用的服务(如日志、事务、安全)通过 动态代理 等技术,自动地无侵入地织入到业务代码中。

简单记忆: AOP 就是把散落在各处的、非核心的通用功能,像一把刀一样切开业务代码,然后植入进去。

动态代理

动态代理(Dynamic Proxy)是程序设计中一种非常强大的设计模式,它属于 代理模式 的一种。它的核心思想是在 运行时 动态地创建一个代理对象,这个代理对象可以代替真实对象(目标对象)执行操作,并在不修改真实对象代码的前提下,增加额外的功能和控制。

概念解释作用
真实对象 (Real Subject)包含核心业务逻辑的对象。完成实际的任务。
代理对象 (Proxy)动态生成的一个对象,它实现了与真实对象相同的接口。接收外部的调用请求,并在将请求转发给真实对象之前或之后,插入额外的逻辑(增强)。
接口 (Interface)真实对象和代理对象共同实现的规范。保证代理对象和真实对象可以互换使用。
增强 (Enhancement)代理对象在调用真实方法前后添加的额外功能(例如:日志记录、权限检查、事务管理等)。实现了无侵入式的代码扩展。

AOP的使用

@Around:切入点表达式

AOP核心概念

1. 切面 (Aspect)

  • 定义: 封装横切关注点的模块。它是一个类,将**通知(Advice)切入点(Pointcut)**组合在一起。

  • 代码体现:@Aspect 注解标记的类(如你之前写的 TimeAspect)。

2. 连接点 (Join Point)

  • 定义: 程序执行过程中可以插入通知(Advice)的所有潜在位置

  • 例子: 方法的调用、方法的执行、设置字段值、抛出异常等。

  • Spring AOP 的限制: Spring AOP 只支持方法执行作为连接点。

3. 通知/增强 (Advice)

  • 定义: 真正被织入到连接点上的代码(动作)。它定义了**“何时(When)”“如何(How)”**执行额外的行为。

通知类型注解执行时机作用
前置通知@Before目标方法执行之前。校验权限、启动日志。
后置通知@AfterReturning目标方法正常返回之后。处理返回值、提交日志。
异常通知@AfterThrowing目标方法抛出异常之后。回滚事务、记录错误信息。
最终通知@After (或 AfterFinally)目标方法执行完毕(无论是否异常)。释放资源、关闭连接。
环绕通知@Around包围目标方法。计时、事务控制。最强大且常用

4. 切入点 (Pointcut)

  • 定义: 一个表达式,用来精确匹配或筛选出真正要执行通知的连接点。它是一个规则过滤器

  • 代码体现: execution(...) 表达式,如你在 @Around 中定义的 execution(* com.itheima.service.*.*(..))

5. 目标对象 (Target Object)

  • 定义: 被一个或多个切面(Aspect)所通知的对象。就是那个原始的、等待被增强的类

6. 织入 (Weaving)

  • 定义: 将切面(Aspect)应用到目标对象(Target Object)上,创建出被通知的新对象的过程。

  • Spring AOP 实现: 主要在运行时(Runtime)通过动态代理(JDK Proxy 或 CGLIB)实现织入。

环绕通知

返回值必须为Object接受原始方法的返回值

通知类型@Around 环绕通知其他通知 (@Before, @After 等)
控制权完全控制(Full Control)。旁观和依附(Passive Attachment)。
是否调用 proceed()必须调用 joinPoint.proceed()不能调用 joinPoint.proceed()
工作方式“取代” 原始方法。环绕方法是原始方法的替代品。你必须手动决定原始方法是否运行、何时运行、以及运行后如何处理结果。“附加” 到原始方法上。框架会自动处理原始方法的运行,你的通知只是在某个特定时刻被回调执行。
返回值必须返回一个 Object,作为最终返回给调用方的值。返回值通常是 void 或无意义的(除了 @AfterReturning 可以接收返回值)。
AOP 参数必须使用 ProceedingJoinPoint通常使用 JoinPoint 即可。

切入点表达式(@Pointcut注解)

@Pointcut("execution(* com.itheima.service.*.*(..))")
    public void pt(){};//抽取替换切入点表达式

    @Around("pt()")//切入点表达式

Execution切入点表达式

*
位置示例匹配说明
返回值类型execution(public * *.*(..))匹配 所有 返回类型的公有方法。
包名execution(* *.*.Service.*(..))匹配任意包路径下的 Service 类。
类名/方法名execution(* com.test.User*.*(..))匹配 com.test 包下所有以 User 开头的类中的所有方法。
..
位置示例匹配说明
包路径execution(* com.app..*.*(..))匹配 com.app及其所有子包 下的 所有类 中的所有方法。
方法参数execution(* com.app.Service.method(..))匹配 method 方法,不论它有没有参数,或者参数是什么类型。 (..) 是最常用的参数匹配方式。

annotation

自定义注解

import java.lang.annotation.*; // 引入元注解

@Target(ElementType.METHOD) // 只能用于方法
@Retention(RetentionPolicy.RUNTIME) // 在运行时保留,可以通过反射读取
public @interface Mylog {
    // 元素(Element):注解的参数,定义为方法形式
    String value() default "Default Log"; // 默认值为 "Default Log"
    int level() default 1; 
}

//可被带有annotation的通知识别

元注解名称作用/目的必须使用的位置
@Target指定目标:定义注解可以应用在哪些程序元素上(如类、方法、字段等)。自定义注解定义
@Retention指定生命周期:定义注解的保留级别(编译期、类加载时、运行时)。最常用的是 RUNTIME自定义注解定义
@Documented生成文档:标记注解会被 Javadoc 等工具包含在生成的文档中。自定义注解定义
@Inherited允许继承:标记注解可以被子类继承。自定义注解定义
@Repeatable可重复注解:允许在同一个元素上多次使用同一个注解。自定义注解定义

通知顺序(同一切面类内部)

一、正常执行顺序 (无异常)

当目标方法正常执行,没有抛出异常时,通知的执行顺序如下:

  1. @Around (环绕通知)前置部分 (即 joinPoint.proceed() 之前)

  2. @Before (前置通知)

  3. 目标方法执行

  4. @Around (环绕通知)后置部分 (即 joinPoint.proceed() 之后)

  5. @After (最终通知/AfterFinally)

  6. @AfterReturning (返回通知)

二、异常执行顺序

当目标方法执行时抛出异常,或在 @Before 中抛出异常时,顺序会发生变化:

  1. @Around (环绕通知)前置部分

  2. @Before (前置通知)

  3. 目标方法执行 (💥 抛出异常)

  4. @Around (环绕通知)异常处理部分 (通常通过 try-catch 捕获异常)

  5. @After (最终通知/AfterFinally)

  6. @AfterThrowing (异常通知)

  7. (异常向上抛出)

通知顺序(不同切面类)

默认以类名排序

通过@Order注解(数字)规定执行顺序,数字越小越先执行(栈模式)

员工管理系统完善(日志记录)

//日志记录接口
@Mapper
public interface OperateLogMapper {

    //插入日志数据
    @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
    public void insert(OperateLog log);

}

//annotation注解

@Retention(RetentionPolicy.RUNTIME)//运行指示生效
@Target(ElementType.METHOD)//当前注解作用在方法上
public @interface Log {
}

//切面类
@Slf4j
@Component
@Aspect
public class LogAspect {
    @Autowired
    private OperateLogMapper operateLogMapper;

    @Autowired
    private HttpServletRequest request;

    @Around("@annotation(com.itheima.anno.Log)")
    public Object record(ProceedingJoinPoint joinPoint) throws Throwable{
        //操作人ID,获取请求头中jwt令牌解析
        String jwt = request.getHeader("token");
        Claims claims = JwtUtils.parseJWT(jwt);
        Integer id = (Integer) claims.get("id");

        //操作时间
        LocalDateTime operateTime = LocalDateTime.now();

        //操作类名
        String className = joinPoint.getClass().getName();

        //操作方法名
        String methodName = joinPoint.getSignature().getName();

        //操作方法参数
        Object[] args = joinPoint.getArgs();
        String params = Arrays.toString(args);

        Long startTime = System.currentTimeMillis();
        //执行原始方法
        Object result = joinPoint.proceed();
        Long endTime = System.currentTimeMillis();

        //方法返回值
        String returnValue = JSONObject.toJSONString(result);

        //操作耗时
        Long costTime = endTime - startTime;
        //日志记录
        OperateLog operateLog = new OperateLog(null,id,operateTime,className,methodName,params,returnValue,costTime);
        operateLogMapper.insert(operateLog);

        log.info("AOP日志记录{}",operateLog);
        return result;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值