Spring(面试版)

IOC
控制反转,本质是将用户手动创建对象的控制权,交给程序来管理。主要目的是借助第三方实现具有依赖关系的对象之间的解耦
将对象之前的依赖关系交给IOC容器来管理,然后由IOC容器完成对象注入。可以很大程度上简化应用的开发。程序员可以从繁杂的对象依赖中挣脱出来,专心于业务代码的开发。
@Component
和@Bean
的区别
@Component
通过类路径扫描来自动侦测并自动装配到Spring容器中。@Bean
通常是在标有该注解的方法中自定义bean实例。同时,如果我们想要引入第三方库中的类,只能使用@Bean
。
DI
要想更透彻的理解IOC,还需要了解一下依赖倒置原则。
当我们进行设计时,往往会根据下层基础设计上层建筑,这样有一个问题就是上层建筑一旦发生改动,就要从上到下彻底更改,因为我们的上层建筑都是根据底层完成设计的。
依赖导致的思想就是将整个过程反转过来,我们先明确上层建筑需要底层提供什么服务,然后再去进一步设计底层,这样高层就不必关注底层实现,它只需要底层能完成它需要的功能就可以了。
这两种思想体现到代码里就是:
自下而上设计代表上层需要了解底层相关信息,例如如果我们要新建一个Car实例,首先要了解轮胎有多大,并将这个size传递给Car的构造器,Car构造器会在内部初始化轮胎实例。这样的问题在于,上下两层强耦合,一旦轮胎的初始化方式出现变动,就要修改Car的构造器。
自上而下设计可以避免上下层之间的耦合,因为它只需要知道自己有一个轮胎实例,并且这个轮胎实例提供了自己想要的功能,此时只需要给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)
解决方案有两个:
在Bean中尽量避免定义可变的成员变量
在类中定义一个
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支持两种事务:
编程式事务:在代码内部直接使用TransactionTemplate手动管理事务,维护困难,很少使用。
声明式事务:通过AOP实现,侵入性较小,如
@Transactional
事务管理接口
Spring中的事务管理接口包括:
- **
PlatformTransactionManager
**: (平台)事务管理器,Spring 事务策略的核心。 - **
TransactionDefinition
**: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。 - **
TransactionStatus
**: 事务运行状态。
PlatformTransactionManager
是事务的上层管理者,她可以获取事务,提交事务或回滚事务,而另外两个是对事务的描述。
事务属性:
- 隔离级别
- 传播行为
- 回滚规则
- 是否只读
- 事务超时
事务传播行为
事务传播行为用于解决业务层方法之间相互调用的问题。当一个事务方法被另一个事务方法调用时,必须指定事务如何传播
常用的事务传播行为:
TransactionDefinition.PROPAGATION_REQUIRED
Spring默认的事务传播策略,如果外围方法已经开启事务,就加入事务。否则新开启一个事务。这种传播行为,不管外围还是内部方法抛出异常都会将整个事务回滚。
TransactionDefinition.PROPAGATION_REQUIRES_NEW
无论如何都会创建一个新事务,如果外围方法已经开启了事务,就把外围事务挂起。如果外围事务回滚,不会影响内部方法的事务。而如果内部方法抛出异常回滚,并且这个异常被外围方法捕获到,也会导致外部事务回滚。
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 进行许可。