Spring面试

Springboot 启动的流程

Ioc注入方式

  • 接口注入 基本废弃,强制被注入对象实现不必要的接口,侵入性太大,
  • 构造方法注入

优点: 构造完成后,已进入就绪状态,可以马上使用
缺点: 依赖对象较多时,构造方法的参数列表会比较长。通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上比较麻烦。而且构造方法无法被继承,无法设置默认值

  • Setter方法注入

优点: 因为方法可以命名,描述性比构造方法注入好一些,setter方法还可以被继承,允许设置默认值,而且有良好的IDE支持。
缺点: 对象无法在构造完成后马上进入就绪状态

IoC 循环依赖

SpringIoC的循环依赖
spring对循环依赖的处理有三种情况:
①构造器的循环依赖:这种依赖spring是处理不了的,直 接抛出BeanCurrentlylnCreationException异常。
②单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
③非单例循环依赖:无法处理。
解决循环依赖原理:
通过提前暴露bean的引用来解决的
spring内部有三级缓存:

  • singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
  • earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
  • singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

image.png
参考: 循环依赖
三级缓存中的二级缓存是为了实现 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代理
  1. 获取增强器,例如被Aspect注解修饰的类
  2. 在实例化bean时,会检查是否有增强器能应用于这个bean,简单理解就是该bean是否在该增强器指定的execution表达式中。如果是,则将增强器作为拦截器参数,使用动态代理创建bean的代理对象实例。

补充: 处理时机: 在ioc实例化过程中实现的是BeanPostProcessor 接口,该接口是 Spring 初始化 bean 时对外暴露的扩展点,Spring IoC 容器允许 BeanPostProcessor 在容器实例化bean 的前后,添加自己的逻辑处理
todo: 如何判定在指定的execution表达式中

  1. 当我们调用被增强过的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按顺序加载

  1. 使用 @DependsOn、depends-on
  2. 让后加载的类依赖先加载的类

    1. @Component
    2. public class A {
    3. @Autowire
    4. private B b;
    5. }
  3. 使用扩展点提前加载,例如:BeanFactoryPostProcessor

    1. @Component
    2. public class TestBean implements BeanFactoryPostProcessor {
    3. @Override
    4. public void postProcessBeanFactory(ConfigurableListableBeanFactory
    5. configurableListableBeanFactory) throws BeansException {
    6. // 加载bean
    7. beanFactory.getBean("a");
    8. }

    事务

    事务的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,是根据业务逻辑处理是否抛出异常来决定
      1. // 伪代码
      2. public void invokeWithinTransaction() {
      3. // 1.事务资源准备
      4. try {
      5. // 2.业务逻辑处理,也就是调用被代理的方法
      6. } catch (Exception e) {
      7. // 3.出现异常,进行回滚并将异常抛出
      8. } finally {
      9. // 现场还原:还原旧的事务信息
      10. }
      11. // 4.正常执行,进行事务的提交
      12. // 返回业务逻辑处理结果
      13. }
      image.png

      Spring中使用ThreadLocal

  • 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
多数据源不建议在项目中使用,可采用服务间接口调用形式处理。数据回滚场景较少
,如果发生,应该排查错误数据原因,可手动发起数据请求。

Spring用到的设计模式