美文网首页
SpringAOP会导致的一些问题

SpringAOP会导致的一些问题

作者: 耐得千事烦 | 来源:发表于2019-08-17 23:08 被阅读0次

    前言

    之前的系列文章已经把AOP说的挺明白了,现在我们来说一些日常的可能在用到spring的时候会遇到的一些奇怪的问题,而这类问题的问题就出现在在AOP这块。


    \color{green}{问题1}
    在使用Spring事务的时候,如果在方法上加上synchronized锁依然会导致线程是非安全的问题。可能有点说的不明白,我们直接贴代码吧。

    • 测试代码:
    @RestController
    public class EmployeeController {
    
        @Autowired
        private EmployeeService employeeService;
    
        @RequestMapping("/add")
        public void addEmployee() {
            for (int i = 0; i < 1000; i++) {
                new Thread(() -> employeeService.addEmployee()).start();
            }
        }
    
    
    }
    
    @Service
    public class EmployeeService {
        @Autowired
        private EmployeeRepository employeeRepository;
        @Transactional
        public synchronized void addEmployee() {
            // 查出ID为8的记录,然后每次将年龄增加一
            Employee employee = employeeRepository.getOne(8);
            System.out.println(employee);
            Integer age = employee.getAge();
            employee.setAge(age + 1);
    
            employeeRepository.save(employee);
        }
    
    }
    

    描述一下代码业务吧:开启1000个线程,每个线程的工作是去查询一下员工的数据,然后把员工的年龄数据进行加1的操作,并且进行保存。事先申明在数据库层面并没有用到悲观锁或者乐观锁。当然根据上面的代码(已经在方法上加了锁),按理说最终的结果该员工的年龄应该是1000。

    代码贴完,并且也把业务说明清楚了,我们来看一下控制台打印情况:


    SQL打印情况.png

    看到什么了么?SQL执行的情况并非是我们想象中的串行进行的。这就会导致对同一个值进行重复修改,那么必然的最终的员工的年龄一定是小于1000的。那么问题来了,为什么我已经在方法上加了synchronized关键字了,结果方法依然没有出现串行呢?

    • 描述完毕,现在进行解答:
      首先先说一下,这个问题好像是事务与synchronized锁的问题,但是我们要知道一点,Spring实现事务的机制是通过SpringAOP来完成的(后面会发一篇文章来描述一下Spring事务的实现机制)。我说的这么明白,那么大家也应该明白了,问题的原因肯定是不会出现在synchronized锁上了,对,就是出现在springAOP的实现机制上。
      我们知道在IOC容器初始化bean的时候,在初始化完bean后,会通过BeanPostProcessor接口里的方法来将目标对象替换成代理类对象,而代理类是已经把目标对象里的方法进行了增强处理(可以去看一下之前写的关于AOP的文章)
      而如何增强的呢?我们以JDK动态代理为例子,通过实现InvocationHandler接口的invoke方法来实现增强的,我们来看一下Spring实现事务这块是如何实现相关invoke方法的:


      Spring事务实现invoke方法部分源码.png

    在多线程环境下,就可能会出现:方法执行完了(synchronized代码块执行完了),事务还没提交,别的线程可以进入被synchronized修饰的方法,再读取的时候,读到的是还没提交事务的数据,这个数据不是最新的,所以就出现了这个问题

    再聊的深一点:其实这个问题的本质是要对这个事务方法进行串行化处理(不知道大家能不能理解,如果可以理解,那就说明是真的懂了)

    处理方法有俩种:
    1.去掉synchronized关键字,直接在事务注解上加上配置,让数据库的隔离等级提升到serializable串行化。(数据层层面的串行化)

    1. 由上面的分析可以知道,看似synchronized关键字锁定了整个addEmployee方法,但是其实它只是锁了一部分代码的代码块而已,并没有整个锁住这个事务方法,我们可以在外层套个壳子,然后锁住这个壳子就行了:(代码层面的串行化)
      新建一个名叫SynchronizedService类,让其去调用addEmployee()方法,整个代码如下:
    @RestController
    public class EmployeeController {
        @Autowired
        private SynchronizedService synchronizedService ;
        @RequestMapping("/add")
        public void addEmployee() {
            for (int i = 0; i < 1000; i++) {
                new Thread(() -> synchronizedService.synchronizedAddEmployee()).start();
            }
        }
    }
    
    // 新建的Service类
    @Service
    public class SynchronizedService {
        @Autowired
        private EmployeeService employeeService ; 
        // 同步
        public synchronized void synchronizedAddEmployee() {
            employeeService.addEmployee();
        }
    }
    
    @Service
    public class EmployeeService {
        @Autowired
        private EmployeeRepository employeeRepository;
        @Transactional
        public void addEmployee() {
            // 查出ID为8的记录,然后每次将年龄增加一
            Employee employee = employeeRepository.getOne(8);
            System.out.println(Thread.currentThread().getName() + employee);
            Integer age = employee.getAge();
            employee.setAge(age + 1);
    
            employeeRepository.save(employee);
        }
    }
    

    \color{green}{问题2}
    在AOP中进行增强俩个方法(该俩方法在一个类里),如果其中一个方法里面调用了另一个方法,那么就会导致一个问题。该方法里调用的另一个方法并没有得到我们预想中的增强处理,这又是怎么回事呢?

    暂时先写到这,后面会把这块给补上的......

    相关文章

      网友评论

          本文标题:SpringAOP会导致的一些问题

          本文链接:https://www.haomeiwen.com/subject/vxwksctx.html