Spring面试
Springboot 启动的流程
Ioc注入方式
- 接口注入 基本废弃,强制被注入对象实现不必要的接口,侵入性太大,
- 构造方法注入
优点: 构造完成后,已进入就绪状态,可以马上使用
缺点: 依赖对象较多时,构造方法的参数列表会比较长。通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上比较麻烦。而且构造方法无法被继承,无法设置默认值
- Setter方法注入
优点: 因为方法可以命名,描述性比构造方法注入好一些,setter方法还可以被继承,允许设置默认值,而且有良好的IDE支持。
缺点: 对象无法在构造完成后马上进入就绪状态
IoC 循环依赖
SpringIoC的循环依赖
spring对循环依赖的处理有三种情况:
①构造器的循环依赖:这种依赖spring是处理不了的,直 接抛出BeanCurrentlylnCreationException异常。
②单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
③非单例循环依赖:无法处理。
解决循环依赖原理:
通过提前暴露bean的引用来解决的
spring内部有三级缓存:
- singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
- earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
- singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。
参考: 循环依赖
三级缓存中的二级缓存是为了实现 aop中代理bean的时候用的,因为aop的bean是Spring生成了代理类,注入到容器中。
AOP动态代理时机: 其中最后也解释了二级缓存的意义: 在实例化对象属性注入(populateBean方法)的时候,防止代理bean和被代理bean的地址指向不一。
二级缓存也能解决问题,为什么不使用二级缓存解决
AOP的基本概念
- Joinpoint(连接点)
- Pointcut(切点): 用于指定一组Jointcut,表达式 execution可以圈定一组
- Advice(增强/通知): 实现具体的逻辑,常用@Before,@After,@Around等
- Aspect(切面): 定义Pointcut 和 Advice 的地方,注解@Aspect
- Weaving(织入): 将Advice植入到Pointcut指定的Jointpoint
- Target(目标对象): 符合Pointcut所指定的条件,被织入Advice的对象
AOP的实现原理
本质上是通过动态代理来实现的,默认Cglib代理
- 获取增强器,例如被Aspect注解修饰的类
- 在实例化bean时,会检查是否有增强器能应用于这个bean,简单理解就是该bean是否在该增强器指定的execution表达式中。如果是,则将增强器作为拦截器参数,使用动态代理创建bean的代理对象实例。
补充: 处理时机: 在ioc实例化过程中实现的是BeanPostProcessor 接口,该接口是 Spring 初始化 bean 时对外暴露的扩展点,Spring IoC 容器允许 BeanPostProcessor 在容器实例化bean 的前后,添加自己的逻辑处理
todo: 如何判定在指定的execution表达式中
当我们调用被增强过的bean时,就会走到代理类中,从而可以出发增强器,本质跟拦截器类似。
被增强过的类,注入到容器中的是代理类
todo: 怎么判断 bean是否在该增强器指定的execution表达式中
**
Spring 的 AOP 有哪几种创建代理的方式
Spring 中的 AOP 目前支持 JDK 动态代理和 Cglib 代理。
通常来说:如果被代理对象实现了接口,则使用 JDK 动态代理,否则使用 Cglib 代理。另外,也可以通过指定 proxyTargetClass=true 来实现强制走 Cglib 代理。
AOP使用失效场景
同一个Class中,方法A,方法B都被aop拦截,方法A调用方法B,B的拦截会失效。因为被AOP管理的bean,其实是代理bean,调用方法A的时候,是代理类调用的方法A,当真正执行到方法A中,此时的this就指向了被代理bean,this.B()方法,就正常调用B方法,而不会被拦截。
多个AOP的顺序怎么定
通过 Ordered 和 PriorityOrdered 接口进行排序。
PriorityOrdered 接口的优先级比 Ordered 更高,如果同时实现 PriorityOrdered 或 Ordered 接口,则再按 order 值排序,值越小的优先级越高。
@Resource 和 @Autowired 的区别
- @Resource和@Autowired都可以用来装配bean
- @Autowired默认按照类型匹配,默认情况下必须要求依赖对象存在,如果要允许为null值,可以设置它的required属性为false。 @Autowired配合@Qualifier使用,可以按照名称来注入。
- @Resource如果制定了name或type,则按指定的进行装配;如果都不指定,则优先按照名称转配,当找不到与名称匹配的bean时才按照类型进行装配
@PostConstruct 修饰的方法里用到了其他 bean 实例,会有问题吗
todo:
Spring中如何让两个bean按顺序加载
- 使用 @DependsOn、depends-on
让后加载的类依赖先加载的类
@Component
public class A {
@Autowire
private B b;
}
使用扩展点提前加载,例如:BeanFactoryPostProcessor
@Component
public class TestBean implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory
configurableListableBeanFactory) throws BeansException {
// 加载bean
beanFactory.getBean("a");
}
}
事务
事务的ACID特性
- 原子性(Atomicity): 一个事务必须被视为一个不可分割的最小工作单元,整个事务找那个的所有操作要么全部提交成功,要么全部失败回滚。 (主要涉及InnoDB事务相关特性:事务的提交,回滚,信息表)
- 一致性(Consistency): 数据库总是从一个一致性的状态转换到另一个一致性的状态。在事务开始前后,数据库的完整性约束没有被破坏。例如违反了唯一性,必须撤销事务,返回初始状态。(主要涉及内部InnoDB处理,以保护数据不受崩溃,相关特性:双写缓冲、崩溃恢复。)
- 隔离性(isolation): 每个读写事务的对象对其他事务的操作对象能互相分离,即: 事务提交前对其他事务是不可见的,通常内部加锁实现。(主要涉及事务,尤其是事务隔离级别,相关特性:隔离级别、innodb锁的底层实现细节。)
- 持久性(durability): 一旦事务提交,则起所做的修改会永久保存到数据库。(涉及到MySQL软件特性与特定硬件配置的相互影响,相关特性:4个配置项:双写缓冲开关、事务提交刷新log的级别、binlog同步频率、表文件;写缓存、操作系统对于fsync()的支持、备份策略等)
事务的三种现象:
脏读(读取未提交的事务数据)
不可重复读(事务A读取一次,此时事务B对数据进行了修改或删除操作,事务A再次查询数据不一致)
幻读(事务A读取一次,此时事务B插入一条数据,事务A再次查询,记录多了)
事务的隔离级别
Read Uncommitted(读取未提交的) 出现问题: 脏读,不可重复读,幻读
Read Commited(读取提交内容),默认 出现问题: 不可重复读,幻读
Repeatable Read(可重复读) 出现问题: 幻读
Serializable(可串行化)
事务的传播机制
事务嵌套才会用到事务的传播机制
事务的传播机制
- REQUIRED: 支持当前事务,如果不存在,创建一个新的
- SUPPORTS: 当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
- MANDATORY : 当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常(编译时期就不会通过)
- REQUEST_NEW: 创建一个新事物,如果存在当前事务,则挂起该事务
- NOT_SUPPORTED: 始终以非事务方式执行,如果当前存在事务,则挂起当前事务,A.serivce()有事务,B.service()读取不到A修改的数据,除非隔离级别是Read Uncommitted
- NEVER: 不使用事务,如果当前事务存在,则抛出异常
- NESTED: 如果当前事务存在,则嵌套事务中执行,否则和REQUIRED的操作一样,开启一个新的事务
- todo: 对应场景列举
Spring 事务的实现原理
底层实现主要使用的技术:AOP(动态代理) + ThreadLocal + try/catch
动态代理: 基本所有要进行逻辑增强的地方都会用到动态代理AOP底层也是通过动态代理实现。
ThreadLocal: 主要用于线程间的资源隔离,以此山西不同线程可以使用不同的数据源、隔离级别等
try/catch: 最终是执行commit 还是rollback,是根据业务逻辑处理是否抛出异常来决定// 伪代码
public void invokeWithinTransaction() {
// 1.事务资源准备
try {
// 2.业务逻辑处理,也就是调用被代理的方法
} catch (Exception e) {
// 3.出现异常,进行回滚并将异常抛出
} finally {
// 现场还原:还原旧的事务信息
}
// 4.正常执行,进行事务的提交
// 返回业务逻辑处理结果
}
Spring中使用ThreadLocal
- todo: 对应场景列举
- ThreadLocal为每个线程分配了一个它们各自持有的Connection,从而避免了对单一Connection资源的征用,毕竟在JDBC中是一个Connection对应一个事务,如果所有的线程共用一个Connection的话,那么整个事务管理就有点儿失控的感觉了。
- 通过ThreadLocal来保证Connection多线程环境下的正确使用,也是原因之一。
ThreadLocal应用场景
事务失效的使用方式和原因
A类,methodA 调用methodB,methodB事务失效,还是和AOP生成的代理类有关系
事务嵌套和传播方式的设定有关,比如SUPPORTS,MANDATORY ,NOT_SUPPORTED,NEVER
多数据源事务若干
多数据源事务生效配置 https://blog.csdn.net/weixin_45922369/article/details/107924401
多数据源不建议在项目中使用,可采用服务间接口调用形式处理。数据回滚场景较少
,如果发生,应该排查错误数据原因,可手动发起数据请求。