SpringAOP会导致的一些问题

前言

之前的系列文章已经把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中进行增强俩个方法(该俩方法在一个类里),如果其中一个方法里面调用了另一个方法,那么就会导致一个问题。该方法里调用的另一个方法并没有得到我们预想中的增强处理,这又是怎么回事呢?

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

推荐阅读更多精彩内容

  • 不足的地方请大家多多指正,如有其它没有想到的常问面试题请大家多多评论,一起成长,感谢!~ String可以被继承吗...
    启示录是真的阅读 2,047评论 3 3
  • 一 基础篇 1.1 Java基础 面向对象的特征抽象:将一类对象的共同特征总结出来构建类的过程。继承:对已有类的一...
    essential_note阅读 456评论 0 0
  • 1.垃圾回收算法 1.标记-回收算法 两个步骤:①标记需要回收的对象②同一回收被回收的对象 两个问题:①效率低下②...
    王杰涛阅读 300评论 0 1
  • 包含的重点内容:JAVA基础JVM 知识开源框架知识操作系统多线程TCP 与 HTTP架构设计与分布式算法数据库知...
    消失er阅读 3,074评论 1 10
  • 1、面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: -- 抽象:抽象是将一类对象的共同特征总结...
    ccc_74bd阅读 402评论 0 1