Spring(面试版)

Zephyr Lv3

IOC

控制反转,本质是将用户手动创建对象的控制权,交给程序来管理。主要目的是借助第三方实现具有依赖关系的对象之间的解耦

将对象之前的依赖关系交给IOC容器来管理,然后由IOC容器完成对象注入。可以很大程度上简化应用的开发。程序员可以从繁杂的对象依赖中挣脱出来,专心于业务代码的开发。

@Component@Bean的区别

@Component通过类路径扫描来自动侦测并自动装配到Spring容器中。@Bean通常是在标有该注解的方法中自定义bean实例。同时,如果我们想要引入第三方库中的类,只能使用@Bean

Spring IOC 容器源码分析

DI

要想更透彻的理解IOC,还需要了解一下依赖倒置原则。

当我们进行设计时,往往会根据下层基础设计上层建筑,这样有一个问题就是上层建筑一旦发生改动,就要从上到下彻底更改,因为我们的上层建筑都是根据底层完成设计的。

依赖导致的思想就是将整个过程反转过来,我们先明确上层建筑需要底层提供什么服务,然后再去进一步设计底层,这样高层就不必关注底层实现,它只需要底层能完成它需要的功能就可以了。

这两种思想体现到代码里就是:

  1. 自下而上设计代表上层需要了解底层相关信息,例如如果我们要新建一个Car实例,首先要了解轮胎有多大,并将这个size传递给Car的构造器,Car构造器会在内部初始化轮胎实例。这样的问题在于,上下两层强耦合,一旦轮胎的初始化方式出现变动,就要修改Car的构造器。

  2. 自上而下设计可以避免上下层之间的耦合,因为它只需要知道自己有一个轮胎实例,并且这个轮胎实例提供了自己想要的功能,此时只需要给Car构造器传递轮胎实例即可完成初始化,即使轮胎构造器发生变动,也不会影响到Car。

IOC就是实现依赖倒置的一种思路,它的实现手段就是通过DI(依赖注入)。

在过去的程序设计思路中,A要想使用B,就必须在自己的内部完成B的初始化,这就代表着A必须了解如何初始化B,而一旦引入了IOC容器,A只需要表明自己需要B,即可从IOC容器中获取到B的实例。

在Spring中,通过IOC容器完成DI,它会利用DFS查询某个实例初始化需要哪些依赖,然后将他们全部初始化出来。

Bean

作用域

scope description
singleton 单例
prototype 原型,容器内可以有任意数量的对象实例
request 每收到一个新请求就创建一个
session 每次会话创建一个
application 每个应用程序创建一个
websocket 在每个websocket的生存周期中存在一个

单例Bean的线程安全问题

多个线程操作同一个对象时是存在资源竞争的(eg:zl的PostBuilder)

解决方案有两个:

  1. 在Bean中尽量避免定义可变的成员变量

  2. 在类中定义一个ThreadLocal成员变量,将需要的成员变量放在里面

生命周期

  • Bean 容器找到配置文件中 Spring Bean 的定义。
  • Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
  • 如果涉及到一些属性值 利用 set()方法设置一些属性值。
  • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
  • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
  • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。
  • 与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
  • 如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。
  • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
  • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法
  • 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
  • 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

AOP

AOP的核心思想是将那些于业务无关的,但是被业务模块所共用的逻辑或责任封装起来。OOP是对纵向程序流的封装,而AOP则是对横向程序流的封装。

AOP的基础是动态代理,通过代理模式对原有的代码进行增强。

术语 含义
目标 被代理的对象
代理 代理对象
连接点 目标对象中所有定义的方法
切入点 被切面增强的连接点
通知 增强逻辑
切面 切入点+通知

自调用问题

Spring AOP借助代理实现,因此它的调用逻辑实际上是:

调用者 -> 代理 -> 实体

因此如果我们直接在实体内调用实体中的方法(自调用),就会导致AOP失效。下面通过一个例子来理解

@Service
public class MyService {

private void method1() {
     method2();
     //......
}
@Transactional
public void method2() {
     //......
  }
}

当我们通过method1()调用method2()时,实际上是this.method2(),这是一个内部调用。也就是说,这个方法调用不会被切面拦截到,这就意味着Spring提供的代理类不会出现在整个调用链里面,注解也就失效了。

而在类外部调用的话,Spring就会发现调用者是个自己要代理的类型,接着就会走代理的逻辑,但是对于this这种自调用,他会认为就是个普通的类内调用,不会进行代理。

要想修复这个问题,可以通过直接获取MyService的代理对象进行方法调用,或者改用AspectJ来实现。

事务

Spring支持两种事务:

  1. 编程式事务:在代码内部直接使用TransactionTemplate手动管理事务,维护困难,很少使用。

  2. 声明式事务:通过AOP实现,侵入性较小,如@Transactional

事务管理接口

Spring中的事务管理接口包括:

  • **PlatformTransactionManager**: (平台)事务管理器,Spring 事务策略的核心。
  • **TransactionDefinition**: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
  • **TransactionStatus**: 事务运行状态。

PlatformTransactionManager是事务的上层管理者,她可以获取事务,提交事务或回滚事务,而另外两个是对事务的描述。

事务属性:

  • 隔离级别
  • 传播行为
  • 回滚规则
  • 是否只读
  • 事务超时

事务传播行为

事务传播行为用于解决业务层方法之间相互调用的问题。当一个事务方法被另一个事务方法调用时,必须指定事务如何传播

常用的事务传播行为:

  1. TransactionDefinition.PROPAGATION_REQUIRED

    Spring默认的事务传播策略,如果外围方法已经开启事务,就加入事务。否则新开启一个事务。这种传播行为,不管外围还是内部方法抛出异常都会将整个事务回滚。

  2. TransactionDefinition.PROPAGATION_REQUIRES_NEW

    无论如何都会创建一个新事务,如果外围方法已经开启了事务,就把外围事务挂起。如果外围事务回滚,不会影响内部方法的事务。而如果内部方法抛出异常回滚,并且这个异常被外围方法捕获到,也会导致外部事务回滚。

  3. TransactionDefinition.PROPAGATION_NESTED

    如果当前没有事务,新建一个事务。如果已经有了,创建一个嵌套事务。如果内部事务回滚,并且异常逃逸到外部,也会导致外部事务回滚,但如果异常被成功捕获,则不会影响到外部事务。此外,由于内部事务嵌套在外部事务,因此,外部事务回滚也会导致内部事务回滚。

事务回滚规则

在默认情况下,事务只会在遇到运行时异常或Error时回归,在面对Checked异常时不会进行回滚。

SpringBoot自动装配

SpringBoot在SpringFramework的基础上借助SPI,进一步优化了自动装配。它会扫描外部引用jar包中的META-INF/spring.factories文件,将文件中的配置信息加载到Spring容器。

源码分析

首先看看SpringBoot中的核心注解SpringBootApplication

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication

我们重点要关注的是EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration

它里面的核心是AutoConfigurationImportSelector,这个类负责加载自动装配类。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

首先会判断是否启用了自动装配,如果启用了,就会去加载自动装配类

上面的代码是加载自动装配的过程,它首先需要加载所有的自动装配类,先获取所有自动装配类的全限定名,然后到spring.factories文件里读取要加载的类。

接下来进行去重以及筛选等操作

不过到这还有些问题,一个大型项目需要引入的jar包很多,其中会包含大量用不到的类,如果我们一股脑的全部加载,就会使项目启动非常缓慢,因此这里SpringBoot还会做进一步的筛选,将用不到的部分全部剔除。

  • 标题: Spring(面试版)
  • 作者: Zephyr
  • 创建于 : 2023-03-04 17:41:35
  • 更新于 : 2023-03-04 17:42:19
  • 链接: https://faustpromaxpx.github.io/2023/03/04/Spring/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论