Spring AOP失效的场景事务失效的场景

news2024/11/15 16:31:15

场景一:使用this调用被增强的方法

下面是一个类里面的一个增强方法

@Service
public class MyService  implements CommandLineRunner {
    private  MyService myService;

    public void performTask(int x) {
        System.out.println("Executing performTask method");

        if(x<5) this.performTask(1+x);
//        if(x<5) myService.performTask(1+x);
    }


    @Override
    public void run(String... args) throws Exception {
        myService = AopTestApplication.getBean(MyService.class);
    }
}

增强逻辑是在方法调用的前后输出各一行语句

@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(* com.example.aoptest.MyService.performTask(..))")
    public void performTaskPointcut() {
        // Pointcut for performTask method
    }

    @Before("performTaskPointcut()")
    public void beforePerformTask() {
        System.out.println("方法调用前");
    }

    @After("performTaskPointcut()")
    public void afterPerformTask() {
        System.out.println("方法调用后");
    }
}

运行项目并在Controller层调用方法得到的输出如下

可以看出只有第一次调用是执行了增强逻辑的,剩下那些this调用都没有。

原因

aop代理的原理:使用AOP对某一个Bean的方法进行增强之后,放进IOC容器的这个Bean不会是原本的类实例,而是专门创建的一个代理对象Bean.这个代理对象内部有对原始Bean的引用,在用原始Bean调用方法前会先执行增强逻辑。这里就是相当于加了一层。

用this.失效的原因: 用this.xxx相当于直接调用了原始Bean的方法,而不是外层的代理Bean的方法。示意图如下,左边的是用代理对象调用方法,右边的是在原始Bean的任意方法内部用this.调用增强方法。

解决方案

将this.调用改为Bean调用

代码如下。

必须从IOC容器里面直接获取到Bean对象,而不是使用@Autowried注解注入,否则会出现循环依赖的报错。

@SpringBootApplication
public class AopTestApplication implements ApplicationContextAware {
    private static ApplicationContext context;

    public static void main(String[] args) {
        SpringApplication.run(AopTestApplication.class, args);

    }
    public static <T> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
}
@Service
public class MyService  implements CommandLineRunner {
    private  MyService myService;

    public void performTask(int x) {
        System.out.println("Executing performTask method");

        if(x<5) myService.performTask(1+x);
    }


    @Override
    public void run(String... args) throws Exception {
        myService = AopTestApplication.getBean(MyService.class);
    }
}

这次的输出如下,可以看见每一次方法调用都有执行增强逻辑。

场景二:增强方法使用private修饰

@RestController
@Service
public class MyService   implements CommandLineRunner  {
    private  MyService myService;

    @PostMapping("test/{x}")
    private void performTask(@PathVariable("x")int x) {

        System.out.println("Executing performTask method");
        if(Objects.isNull(myService))
            System.out.println("222222");
        if(x<5) myService.performTask(1+x);
    }


    @Override
    public void run(String... args) throws Exception {
        myService = AopTestApplication.getBean(MyService.class);
        if(Objects.isNull(myService))
            System.out.println("1111111");
    }
}

上面代码的输出如下,可以看见并没有输出增强逻辑。并且在使用myService调用方法时还报错myService为空。明明run方法里面都已经注入了Bean.

原因

aop代理无法代理私有方法?代理对象无法通过原始Bean调用里面的私有方法?

同一个类里面存在一个私有和公有的两个增强方法时如下

@RestController
@Service
public class MyService   implements CommandLineRunner  {
    private  MyService myService;

    @PostMapping("test/{x}")
    private void performTask(@PathVariable("x")int x) {

        System.out.println("Executing performTask method");
        if(Objects.isNull(myService))
            System.out.println("222222");
        if(x<5) myService.performTask(1+x);
    }

    @PostMapping("test2/{x}")
    public void performTask2(@PathVariable("x")int x) {

        System.out.println("Executing performTask method2");
        if(Objects.isNull(myService))
            System.out.println("222222");
        if(x<5) myService.performTask2(1+x);
    }


    @Override
    public void run(String... args) throws Exception {
        myService = AopTestApplication.getBean(MyService.class);
        if(Objects.isNull(myService))
            System.out.println("1111111");
    }
}

先后调用方法2和方法1得到输出如下

结果是方法2一切正常,方法1还是有问题。

 奇怪的点是方法2里面测出局部变量myService是非空的,但是方法1里面却是空的????

解决方案

将private改成public,现在可以看见增强逻辑都正常输出了。

AOP失效会导致的问题

估计所有依赖于AOP实现的功能都会有问题

首当其冲的就是事务注解@Transactional,事务注解里面也是依赖了AOp,在方法调用前开启事务,在方法调用后进行回滚或者提交事务。

但是像上面失效的场景下就事务注解就会失效,试想,在一个方法内用this调用了同一个类中的一个事务方法,那这个事务方法的事务就会失效,里面的事务方法报错回滚会无法回滚。

事务失效场景一:抛出检查异常checked

默认情况下,Spring事务管理器只在抛出未检查(unchecked)异常(即RuntimeException及其子类)时才会回滚事务。如果方法抛出的是检查(checked)异常,事务不会回滚,除非你显式配置了事务注解的rollbackFor属性。

如下所示,我在业务代码中手动抛出了一个自定义的检查异常,报错是报错了,但是事务并没有回滚。


@Service
public class UserServiceImpl implements UserService , CommandLineRunner {

    public class MyCheckedException extends Exception {
        public MyCheckedException(String message) {
            super(message);
        }
    }


    private UserMapper userMapper;
    private UserService userService;
    @Override
    @Transactional
    public void createUser(String name) throws MyCheckedException{
        user user = new user();
        user.setName(name);
        userMapper.insert(user);

        throw new MyCheckedException("This is a custom checked exception");
    }

    @Override
    public void run(String... args) throws Exception {
        userMapper = AopTestApplication.getBean(UserMapper.class);
        userService = AopTestApplication.getBean(UserService.class);
    }
}

原因

spring事务默认是只在抛出未检查异常时才会进行回滚。

解决方案

显式配置rollback属性

在上面代码的事务注解改成如下,这样子无论是什么异常都会自动回滚了

    @Transactional(rollbackFor = Exception.class)

当然,还有一个属性选择是Throwable, 这个是Exception和Error的父类,这样不管是抛出异常还是错误都会进行回滚了,正常来想,应该也是要这样设置的。

    @Transactional(rollbackFor = Throwable.class)

事务失效场景二:捕获异常之后不抛出

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createUser(String name) throws MyCheckedException{
        user user = new user();
        user.setName(name);
        userMapper.insert(user);
        try{
            throw new MyCheckedException("This is a custom checked exception");
        }
        catch(Exception ex){

        }

    }

如上所示,没有将异常抛出就不会触发回滚。

解决方案

记得抛出异常

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createUser(String name) throws MyCheckedException{
        user user = new user();
        user.setName(name);
        userMapper.insert(user);
        try{
            throw new MyCheckedException("This is a custom checked exception");
        }
        catch(Exception ex){
            throw  ex;
        }
    }

事务失效场景三:使用了非public方法

Spring的事务管理依赖于AOP代理,而AOP只能代理public方法。如果你在非public方法上使用了@Transactional注解,这个注解将不起作用,事务也不会生效。

原理和上面aop失效的原理一样。

事务失效场景四:内部方法调用

如果在同一个类中,一个方法调用了另一个带有@Transactional注解的方法,Spring的AOP代理将无法拦截这个内部调用,导致事务注解失效。解决方法之一是将被调用的方法提取到另一个bean中。

原理也和上面aop使用this调用失效的原理一样。

代码如下:

一个普通方法调用了一个事务方法,并且事务方法里面手动抛出异常,正常来说应该回滚的,但是因为是this.调用,aop失效就无法回滚了。

@Service
public class UserServiceImpl implements UserService , CommandLineRunner {

    public class MyCheckedException extends Exception {
        public MyCheckedException(String message) {
            super(message);
        }
    }


    private UserMapper userMapper;
    private UserService userService;
    @Override
    public void createUser(String name) throws MyCheckedException {
        user user = new user();
        user.setName(name);
//        userMapper.insert(user);
        this.test();
    }
    @Transactional(rollbackFor = Exception.class)
    public void test() throws MyCheckedException {
        user user = new user();
        user.setName(new Date().toString());
        userMapper.insert(user);
        try{
            throw new MyCheckedException("This is a custom checked exception");
        }
        catch(Exception ex){
            throw  ex;
        }
    }

    @Override
    public void run(String... args) throws Exception {
        userMapper = AopTestApplication.getBean(UserMapper.class);
        userService = AopTestApplication.getBean(UserService.class);
    }
}

解决方案一

将this.调用换成Bean调用

解决方案二

使用事务方法调用事务方法,这样子即使是this调用也会回滚了。

代码如下所示,但是这个的本质是外层事务方法进行的回滚,里面调用的事务方法并没有执行回滚。

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createUser(String name) throws MyCheckedException {
        user user = new user();
        user.setName(name);
//        userMapper.insert(user);
        this.test();
    }

    @Transactional(rollbackFor = Exception.class)
    public void test() throws MyCheckedException {
        user user = new user();
        user.setName(new Date().toString());
        userMapper.insert(user);
        try{
            throw new MyCheckedException("This is a custom checked exception");
        }
        catch(Exception ex){
            throw  ex;
        }
    }

事务失效场景五:事务传播属性设置不当

事务失效场景六:不同的事务管理器

如果应用中有多个数据源,可能会有多个事务管理器。如果在配置事务管理器时没有指定正确的事务管理器,可能会导致事务失效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1702311.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode题练习与总结:将有序数组转换为二叉搜索树--108

一、题目描述 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵平衡二叉搜索树。 示例 1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#xff1a;[0,-10,5,null,-3,null,9] 也将…

流程引擎之compileflow idea 2024.*插件支持

之前有使用过多种类型工作流&#xff0c;但最近研究工作流引擎对比各有优劣&#xff0c;compileflow内存支持性能不错&#xff0c;但在idea新版本使用的时候发现插件不支持&#xff0c;干脆自己修改源码手撸一个&#xff08;当前版本2024.1验证可用&#xff0c;如果有其他版本不…

Vue框架动态引入省份个性化代码

项目需求有产品的功能&#xff0c;但是功能下部分小功能每个省份有不同的控制&#xff0c;所以需要引入省份个性化代码。 思路是&#xff0c;页面一开始加载产品化的代码&#xff0c;有个性化的代码就加载个性化的逻辑&#xff0c;个性化代码是产品化代码的重写&#xff0c;所…

QT如何将生成的exe文件打包成安装包

一、生成exe文件 1、生成exe文件 QT编译模式选择release&#xff0c;然后点击编译&#xff1a; 2、找到exe文件 在开发文件夹下找到build-xxxxxxx-Release文件夹里面找到exe文件 3、相关依赖dll文件查找 新建个空文件夹将exe文件拷贝进去&#xff1a; 然后找到如下程序…

数据结构-二叉树系统性学习(四万字精讲拿捏)

前言 这里我会从二叉树的概念开始讲解&#xff0c;其次涉及到概念结构&#xff0c;以及堆的实现和堆排序。 目的是&#xff0c;堆比二叉树简单&#xff0c;同时堆本质上是二叉树的其中一种情况&#xff0c;堆属于二叉树顺序结构的实现 最后完善二叉树的讲解&#xff0c;也就是…

SpringBoot 结合 WebSocket 实现聊天功能

目录 一、WebSocket 介绍 二、源码 2.1 pom.xml 2.2 WebSocket配置类&#xff0c;用于配置WebSocket的相关设置 2.3 自定义WebSocket处理器类&#xff0c;用于处理WebSocket的生命周期事件 2.4 自定义WebSocket握手拦截器&#xff0c;用于增强WebSocket的握手过程 2.5 Ses…

网络编程-TCP

一、TCP的相关IP 1.1 SeverSocket 这是Socket类,对应到网卡,但是这个类只能给服务器使用. 1.2 Socket 对应到网卡,既可以给服务器使用,又可以给客户端使用. TCP是面向字节流的,传输的基本单位是字节. TCP是有连接的,和打电话一样,需要客户端拨号,服务器来听. 服务器的内核…

夏日将至,给手机装个“液冷”降温可行吗?

夏天出门在外&#xff0c;手机总是更容易发热&#xff0c;尤其是顶着大太阳用手机的时候&#xff0c;更是考验手机的散热能力。如果你也是一个对手机体验有追求的人&#xff0c;比较在意手机的温度&#xff0c;那么可以考虑入手一个微泵液冷手机壳。 【什么是微泵液冷壳&#…

mybatisplus填充公共字段MetaObjectHandler后不生效解决方式

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component;import java.util.Date;/*** 拦截处理公共字…

高清短视频:四川京之华锦信息技术公司

高清短视频&#xff1a;视觉盛宴与时代的脉搏 在数字化时代&#xff0c;短视频已成为人们生活中不可或缺的一部分。其中&#xff0c;高清短视频以其卓越的画质和精彩的内容&#xff0c;成为了人们追求视觉享受的首选。四川京之华锦信息技术公司将从高清短视频的定义、特点、影…

简单微信企业群消息推送接口

群管理 群发送接口 POST: JSONURL http://localhost:65029/m/wxapi/sendwxmsg{ "nr":"试", --消息 "at":"wxid_y0k4dv0xcav622,wxid_y0k4dv0xcav622",--群wxid "key":"F98F354F1671A2D21BC78C76B95E96EB",--群k…

设计软件有哪些?建模和造型工具篇(2),渲染100邀请码1a12

之前介绍了一批建模工具&#xff0c;这次我们继续介绍。 1、Forest Pack Forest Pack是由iToo Software公司开发的3ds Max插件&#xff0c;专门用于创建大规模自然环境。它提供了丰富的植被和物体库&#xff0c;用户可以快速创建树木、植物、岩石等元素&#xff0c;并将它们分…

迭代器模式(行为型)

目录 一、前言 二、迭代器模式 三、总结 一、前言 迭代器模式(Iterator Pattern&#xff09;是一种行为型设计模式&#xff0c;提供一种方法顺序访问一个聚合对象中各个元素&#xff0c;而又不暴露该对象的内部表示。总的来说就是分离了集合对象的遍历行为&#xff0c;抽象出…

AutoCAD Electrical电气版 2018 2019 2020 20212022远程安装服务

购买流程&#xff1a; ①进店沟通需求 ②协商服务内容 ③拍下订单 ④远程安装服务 常用版本2015&#xff0c;2016&#xff0c;2018&#xff0c;2019&#xff0c;2020&#xff0c;2021&#xff0c;2022。 适用&#xff1a;WIN 7/8/10/11系统用户购买 系统支持推荐 WIN7&…

【数据分析面试】54.员工信息(HR)数据库搭建

题目 由于发展需求&#xff0c;进一步提高公司人员统筹管理的能力&#xff0c;公司决定要重新升级人力数据管理系统。 现在&#xff0c;你的任务是为公司重新设计和搭建一个员工信息数据库。 提示&#xff1a;考虑HR管理系统的功能&#xff0c;比如人员信息、入职时间、离职…

shell脚本【免交互】

目录 1. Here Document 免交互 1.1 Here Document概述 1.2 实验操练 1.3 tee命令 ​1.4 变量替换 ​ 2. expect 免交互 2.2 实验操练 1. Here Document 免交互 1.1 Here Document概述 *使用I/O重定向的方式将命令列表提供给交互式程序或命令&#xff0c;比如 ftp、…

clickhouse——clickhouse单节点部署及基础命令介绍

clickhouse支持运行在主流的64位CPU架构的linux操作系统之上&#xff0c;可以通过源码编译&#xff0c;预编译压缩包&#xff0c;docker镜像和rpm等多种方式进行安装。 一、单节点部署 1、安装curl工具 yum install -y curl 2、添加clickhouse的yum镜像 curl -s https://pack…

5V降3.3V或3V恒压1A芯片WT6015

5V降3.3V或3V恒压1A芯片WT6015 WT6015 是一款采用恒定频率、电流模式架构的高效单片同步降压稳压器。该设备有可调节版本。无负 载时的电源电流为 40uA&#xff0c;关断时降至 <1uA。 2.5V 至 5.5V 输入电压范围使 WT6015 非常适合单节锂离 子电池供电的应用。 100% 占空比…

第八课,分支语句嵌套、随机数函数、初识while循环

一&#xff0c;分支结构的嵌套语法 在 Python 中&#xff0c;分支结构可以嵌套&#xff0c;这意味着你可以在一个条件语句中包含另一个条件语句。嵌套的分支结构可以让你更灵活地控制程序的逻辑流程。 怎么理解呢&#xff1f;打个比方&#xff1a;放学后&#xff0c;请三年级…

macOS上编译android的ffmpeg及ffmpeg.c

1 前言 前段时间介绍过使用xcode和qt creator编译调试ffmepg.c&#xff0c;运行平台是在macOS上&#xff0c;本文拟介绍下android平台如何用NDK编译链编译ffmepg库并使用。 macOS上使用qt creator编译调试ffmpeg.c macOS上将ffmpeg.c编译成Framework 大体思路&#xff1a; 其…