Spring常见面试题
# AOP&IOC
IOC:控制反转,它是一种思想,将手动创建对象的权力交给Spring,由容器来控制对象的生命周期和对象之间的关系。以前对象是手动创建,现在是向容器索要;
为什么叫控制反转?
控制对象生命周期的不再是引用它的对象,而是容器;
Spring的IOC负责管理bean的生命周期,在底层,这个过程通过BeanPostProcessor接口和BeanFactoryPostProcessor接口来实现;
- BeanFactoryPostProcessor:在容器加载配置文件时,对配置信息进行修改或扩展;
- BeanPostProcessor:负责在bean的初始化前后进行一些处理操作,如AOP代理、属性注入等;
AOP:面向切面编程,将一些业务逻辑中相同代码抽取到一个独立的模块中,降低模块耦合,让业务逻辑更加清爽。在使用时,首先自定义一个注解作为切点,再定义一个切面类,并为需要增强的方法添加注解,Spring回将切面逻辑自动注入到这个方法中,从而实现AOP功能;
原理:基于动态代理,如果代理对象实现了某个接口,使用jdk动态代理创建对象;如果没有实现接口,会用cglib生成一个被代理对象的子类作为代理;
# 如何通过注解实现AOP?
定义切面类;
创建一个类,添加@Aspect注解,定义具体的通知方法:
@Before
、@After
、@Around
;定义切点;
使用@Pointcut注解定义切点,切点是一个表达式,用于匹配需要应用切面的连接点(方法调用、字段访问等);
编写通知方法时指定切点表达式来确定在哪些连接点应用通知;
启用自动代理;
在配置类(通常是一个带有
@Configuration
注解的类)中,添加@EnableAspectJAutoProxy
注解来启用 Spring 对 AOP 的支持,并自动创建代理对象;
# 依赖注入
@Autowired:根据类型匹配;
如果一个接口有多个实现类,再用@Qualifier根据名称注入;
@Resource:根据名称匹配;
# Bean的生命周期
实例化
Spring容器根据Bean的定义创建Bean的实例,相当于执行构造方法,也就是 new 一个对象;
属性填充
相当于执行 setter 方法为字段赋值;
初始化
回调各种Aware接口,调用对象的各种初始化方法;
Bean使用
销毁
如果Bean实现了DisposableBean接口,执行destroy()方法;相当于执行了=null,释放资源;
# Spring循环依赖问题
循环依赖:两个Bean互相持有对方引用;
AB 循环依赖,A 实例化的时候,发现依赖 B,创建 B 实例,创建 B 的时候发现需要 A,创建 A1 实例……
Spring只解决了单例模式下,通过setter方法进行依赖注入的情况;
解决方式:三级缓存
一级缓存只保存初始化完成的Bean对象;
三级缓存保存Bean工厂对象,解决Bean早期引用问题,通过Bean工厂创建出Bean的代理对象;当检测到循环依赖时,可以通过这个工厂得到Bean的代理对象,这个代理对象放入二级缓存中;
三级缓存为什么存放ObjectFactory,不直接保存实例?
ObjectFactory对象可以对实例对象进行增强;
二级缓存保存实例化完成但没有初始化好的Bean对象;
为什么需要二级缓存?
ObjectFactory每次创建的实例对象不一样,通过二级缓存拿到相同对象实例;
案例:
假设两个 Bean A 和 B 存在循环依赖:
- 实例化 A 的时候把 A 的对象⼯⼚放⼊三级缓存,表示 A 开始实例化了。
- 实例化 B,发现依赖 A,就从一级缓存里找 A,但 A 还未完全初始化好,因此 B 转而从二级缓存中获取 A,如果二级缓存也没有,则从三级缓存中获取 A 的工厂对象,通过这个工厂对象获取 A 的早期引用(可能是 A 的代理对象),并将这个早期引用放入二级缓存,同时删除三级缓存中的 A。
- 使用 A 的早期引用完成 B 的创建和初始化,然后将 B 放入一级缓存。
- 在创建 B 的过程中,B 需要 A 的实例。此时,B 尝试从一级缓存获取 A,但 A 还未完全初始化好,因此 B 转而从二级缓存中获取 A,如果二级缓存也没有,则从三级缓存中获取 A 的工厂对象,通过这个工厂对象获取 A 的早期引用(可能是 A 的代理对象),并将这个早期引用放入二级缓存。
- 使用 A 的早期引用完成 B 的创建和初始化,然后将 B 放入一级缓存。
- 使用完全初始化好的 B 实例完成 A 的创建和初始化,最后将 A 也放入一级缓存。
# Spring如何实现优雅停机?
Spring也是依托于JVM实现的,它通过JVM的shutdownHook感知到Java进程关闭,然后执行doClose方法:
JVM优雅停机方式是通过
Runtime.getRuntime().addShutdownHook(shutdownTask);
doClose方法步骤:
- 发布一个容器关闭事件;
- 调用Bean生命周期关闭方法;
- 销毁所有Bean;
- 关闭Bean工厂;
- 调用子类关闭函数;
// org.springframework.context.support.AbstractApplicationContext
@Deprecated //Spring 5 即将废弃
public void destroy() {
close();
}
@Override
public void close() {
synchronized (this.startupShutdownMonitor) {
doClose();
// If we registered a JVM shutdown hook, we don't need it anymore now:
// We've already explicitly closed the context.
if (this.shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
}
catch (IllegalStateException ex) {
// ignore - VM is already shutting down
}
}
}
}
protected void doClose() {
LiveBeansView.unregisterApplicationContext(this);
try {
// Publish shutdown event.
publishEvent(new ContextClosedEvent(this)); ➀
}
// Stop all Lifecycle beans, to avoid delays during individual destruction.
if (this.lifecycleProcessor != null) {
try {
this.lifecycleProcessor.onClose(); ➁
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
}
// Destroy all cached singletons in the context's BeanFactory.
destroyBeans(); ➂
// Close the state of this context itself.
closeBeanFactory(); ➃
// Let subclasses do some final clean-up if they wish...
onClose(); ➄
// Reset local application listeners to pre-refresh state.
if (this.earlyApplicationListeners != null) {
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Switch to inactive.
this.active.set(false);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# IOC初始化过程
在Spring中,IoC容器的初始化过程可以分为以下几个步骤:
- 读取配置文件:容器根据配置文件(如XML、注解等)读取bean的定义和其他相关信息。这些配置文件通常包含了bean的名称、类路径、依赖关系等。
- 创建BeanDefinition对象:根据读取到的配置信息,容器会创建对应的BeanDefinition对象。BeanDefinition对象保存了每个bean的元数据,包括类型、作用域、属性等。
- 解析依赖关系:容器会解析各个bean之间的依赖关系。通过查找BeanDefinition对象的属性或构造函数参数,容器可以确定bean之间的依赖关系。
- 实例化Bean对象:根据BeanDefinition的信息,容器会实例化各个bean对象。这通常是通过Java反射机制来创建bean实例。
- 属性注入:容器会将配置文件中定义的属性值注入到相应的bean实例中。这可以通过setter方法、字段注入或构造函数参数注入来完成。
- Aware接口回调:如果bean实现了Aware接口,容器会调用相应的回调方法,将一些特殊的资源(如ApplicationContext)注入到bean中。
- 初始化回调:如果bean实现了InitializingBean接口或定义了init-method回调方法,容器会在bean实例化完成后调用这些方法,完成bean的初始化工作。
- BeanPostProcessor处理:在bean初始化过程中,容器会调用注册的BeanPostProcessor实现类的方法,对bean进行一些额外的处理操作。
- 容器就绪:所有的bean都经过了初始化和处理后,容器就处于就绪状态,可以通过getBean()方法获取bean实例,并开始使用它们。
- 销毁回调:当容器关闭时,会调用bean实现了DisposableBean接口或定义了destroy-method回调方法的销毁方法,完成bean的清理工作。