面试题分类总览
bean线程安全问题
单例/多例
单例(singleton):在每个spring ioc容器中都只有一个实例。
多例(prototype):在每个spring ioc容器中有多个实例。
默认情况下spring中的bean都是单例的。但是可以通过在类上添加@Scope(“prototype”)来切换成多例的作用域。
单例/多例的线程安全问题
多例一般不会有线程安全问题,因为栈中的对象分别指向堆中属于自己的成员变量。
但是单例不一定。如果单例没有成员变量,那么一般都是线程安全的。但是如果有可以改变状态的成员变量,那么就需要考虑线程安全问题,一般用多例或者加锁来解决。
譬如如下的代码
@Controller
@RequestMapping("/user")
public class UserController{private int count;@Autowiredprivate UserService userService;@GetMapping("/getById/{id}")public User getById(@PathVariable("id") Integer id){count++;return userService.getById(id);}
}
其中注入的UserService和getById方法都是线程安全的。因为他们都是无状态的,不可被改变的对象。(Service类和DAO类)
但是该类的成员变量count就是线程不安全的。
AOP
定义
AOP是一种软件开发的编程范式,又叫面向切面编程。它用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect)。这样可以减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性和复用性。像是常见的记录操作日志、缓存处理、还有Spring中内置的事务处理都是用AOP来实现的。
使用
使用AOP的一般步骤是:定义切面和切点,配置切面,织入切面,并在应用程序中触发切点。
我习惯的做法是
- 定义切面类,即Aspect类,该类包含了@Pointcut注解来实现切点的具体实现。还有具体的通知注解,譬如前置通知、后置通知、环绕通知、异常通知等来实现抽取的具体逻辑。
- 配置切面:譬如通过XML配置文件、注解等来完成切面的配置。
- 织入切面:通常使用动态代理的方式织入切面。一般就是在想要织入切面的类上加入自定义的AOP注解。
- 运行应用程序
事务管理
Spring支持编程式事务管理和声明式事务管理两种方式。
编程式事务管理
编程式事务管理:一般是基于底层的API,如TransactionDefinition 和 TransactionTemplate 等核心接口,使得开发者完全通过编程的方式来进行事务管理。现在项目比较少使用。
优点:提供了更精准的控制,相比于声明式事务管理粒度更小。
缺点:开发者需要在代码中手动实现事务的开启、提交、回滚等操作,较为繁琐。
声明式事务管理
声明式事务管理:其本质是通过AOP功能,在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务,现在项目基本都用声明式事务管理。
优点:
不再需要依赖底层API来硬编码,对业务代码没有侵入性。
适用于事务边界清晰、事务属性统一的场合,譬如最经典的CRUD业务。
缺点:
存在粒度问题。其最小粒度要作用在方法上。
存在一些事务失效的情况。
声明式事务失效
异常捕获提前处理
声明式事务通知只有捕捉到了目标抛出的异常,才能进行后续的回滚处理。如果目标使用try catch自己提前处理掉异常而且没有抛出,事务通知就无法捕获,也无法回滚。
解决方案:在catch中throw new RuntimeException(e)抛出异常。
抛出受检异常/检查异常
spring默认只会回滚非受检异常/非检查异常,所以也不会捕获到该异常。
解决方案:在注解上额外配置rollbackFor属性,@Transactional(rollbackFor=Exception.class)
方法非public
Spring 为方法创建代理、添加事务通知的前提条件都是该方法是 public 的
解决方案:改为public
数据库不支持事务
解决方案:手动编写回滚操作或者迁移到支持事务的数据库中。
多线程调用
解决方案:可以尝试以下方法:
1、调整事务的隔离级别到更高级别。
2、使用乐观锁/悲观锁
3、使用分布式事务管理器
自己调用自己的内部方法
导致类根本没被spring代理,从而失效。
解决方案:可以尝试以下方法:
1、检查事务传播行为。例如,可以使用Propagation.REQUIRED传播行为,使得内部方法加入到外部方法的事务中,保证事务的一致性。
2、考虑异步调用:如果内部方法可以异步执行,并且事务一致性的要求不高,可以将内部方法改为异步调用,让其在独立的线程中执行。通过异步调用,可以避免事务嵌套导致的死锁或其他并发问题。
3、使用编程式事务控制。