文章目录
Spring面试底层原理的那些问题,你是不是真的懂Spring?
说一下Spring中Bean的生命周期
实例化、属性赋值、初始化、销毁。
1、实例化: 根据配置中的 BeanDefinition 实例化 Bean。调用了Bean的构造函数。
2、属性赋值: Spring 使用依赖注入(构造器或者setter)填充所有属性。
3、初始化之前: 调用 BeanPostProcessors 实现类并注入到容器中的Bean的初始化之前方法。
4、初始化: 调用初始化方法,如果指定了初始化方法。
5、初始化之后: 调用 BeanPostProcessors 实现类并注入到容器中的Bean的初始化之后方法。
6、销毁: 当容器关闭时,调用指定的销毁方法。
Spring怎么解决循环依赖
https://blog.csdn.net/weixin_42390873/article/details/113581473
1、实例化A,A添加到三级缓存。
2、A属性赋值,赋值B对象。
3、从一级缓存中获取 B,获取不到,会实例化 B。
4、B添加到三级缓存。
5、B属性赋值,赋值A对象,依次从一级缓存到三级缓存获取B,最终从三级缓存获取到A,然后将A放到二级缓存。
6、B初始化完毕,将B放入一级缓存,并且从二级三级缓存中移除。B创建完毕,赋值给A。
7、A初始化完毕,将A放入一级缓存,并且从二级三级缓存中移除。
8、A创建完毕。
流程图:
为什么通过构造器无法解决循环依赖?
因为Spring解决循环依赖是通过Bean的中间态完成的,这个中间态是已经实例化但尚未初始化的状态,也就是一个半成品。
如果实例化的过程中通过构造器创建,无法将一个对象提前暴露出来,因为这时还未创建好,所以构造器无法解决循环依赖。
二级缓存能否解决循环依赖?
通过上面的分析,其实把二级缓存拿掉,在 B 尝试获取 A 的时候直接返回 A 的实例,其实也是可以的。
但是为什么还是用三级缓存呢?
主要为了解决动态代理场景下的循环依赖。
如果去掉三级缓存:在创建代理对象的时候对Bean生命周期有影响。
如果去掉二级缓存:如果A依赖B和C,切B和C也依赖A,三级缓存的getObject()返回的是代理对象,会导致B和C依赖了不同的A。
如果只保留一级缓存呢:如果A依赖B,在实例化B的时候,从一级缓存是获取不到A的,因为这时A还未初始化完毕。
什么是IOC
草率的回答: IOC 是控制反转,Inverse of Control 。
亲,你在做名词翻译吗?作为面试官不应该只想听到这么一点点吧。
IOC是一种编程原则,它的设计和架构可以实现组件间的解耦,核心思想是将控制权转移出去。
对象间的依赖关系的维护权利交给了 Spring ,程序本身不再维护。
IOC 为了实现解耦,将原有的对象间的主动依赖改为被动接收型依赖(由直接 new 变为 set )。
IOC与DI、DL的区别
DI:依赖注入,依赖上下文被动接收,暴露setter方法即可,public void setXXX() { ... }
DL:依赖查找,使用上下文(容器)主动获取,applicationContext.getBean(beanName)
IOC 是一种思想、编程原则,DI、DL 是 IOC 思想的一种实现方式。
IOC 的实现方式有依赖查找( Dependency lookup )和依赖注入( Dependency Injection )。
SpringFramework中实现的IOC有什么
BeanFactory 接口提供了一个抽象的配置和对象的管理机制。
ApplicationContext 是 BeanFactory 的子接口,它提供了 AOP 的支持、Web 环境的扩展( WebApplicationContext)、事件发布机制(ApplicationEvent)、Bean后置处理器的支持(BeanPostProcessor )。
BeanFactory与FactoryBean的区别
BeanFactory: IOC 的最底层容器接口。(ApplicationContext
在最底层组合了 BeanFactory
)
FactoryBean: 创建对象的工厂 Bean ,可以使用它来直接创建一些初始化流程比较复杂的对象。
BeanFactory 和 ApplicationContext 有什么区别?
BeanFactory
是Spring中最底层的IOC
容器接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。
ApplicationContext
是Spring的一个更高级的容器,是 BeanFactory
子接口,提供了更多的有用的功能。
注意: ApplicationContext
是基于 BeanFactory
的扩展而不是继承!因为在底层,无论是 AbstractRefreshableApplicationContext
还是 GenericApplicationContext
,底层都是组合了一个 DefaultListableBeanFactory
。
ApplicationContext 主要扩展了以下功能: AOP 的支持、配置元信息(BeanDefinition)、资源管理、事件驱动机制、消息与国际化。
ApplicationContext的类型
ApplicationContext
分为两种:
- 基于 xml 配置文件(
ClassPathXmlApplicationContext
、FileSystemXmlApplicationContext
)区别是加载 xml 配置文件的基准路径不同。 - 基于注解驱动配置类,只有
AnnotationConfigApplicationContext
对比BeanPostProcessor与BeanFactoryPostProcessor
依赖注入的注入方式
- 构造器注入:xml、编程式注入,不依赖Spring框架的API
- setter注入:xml、编程式注入,不依赖Spring框架的API
- 参数注入:只能通过标注注解来侵入式注入,依赖Spring框架的API
你觉得哪种方式好?为什么?
如果是业务类,推荐使用参数注入,因为可能会随时增加依赖其他类。
如果是工具类,推荐使用setter注入,因为属性一般不会变动。
构造器注入目前没有用过,最大的弊端是如果属性过多,构造器的参数列表会很长。
自动注入的注解对比
@Autowired:根据类型注入,Spring原生注解,可指定 required=false 来避免注入失败,配置 @Qualifier 指定名称。
@Resource:根据名称注入,JSR250规范,可通过type参数指定类型。
@Inject:根据类型注入,JSR330规范,需要导jar包,基本不用。
依赖注入4连问
依赖注入的目的和优点?
解耦,我们不再需要直接去 new 那些依赖的类对象,直接依赖会导致对象的创建机制、初始化过程难以统一控制。
依赖对象可配置,通过 xml 或者注解声明,可以指定和调整组件注入的对象,借助 Java 的多态特性,可以不需要大批量的修改就完成依赖注入的对象替换。
谁把什么注入给谁了?
IOC 容器把需要依赖的对象注入给待注入的组件。
依赖注入具体是如何注入的?
使用setter注入还是构造器注入?
推荐 setter 注入,理由是如果一个 Bean 有多个依赖时,构造器的参数列表会很长;而且如果 Bean 中依赖的属性不都是必需的话,注入会变得更麻烦。
Spring 框架中都用到了哪些设计模式
- 代理模式:AOP 思想的底层实现技术,Spring 中采用 JDK Proxy 和 CgLib 类库。
- 单例模式:在 Spring配置文件中定义的 bean 默认为单例模式。
- 模板模式:用来解决代码重复的问题。比如:RestTemplate, JdbcTemplate。
- 委派模式:Spring 提供了 DispatcherServlet 来对请求进行分发。
- 工厂模式:BeanFactory 用来创建对象的实例,贯穿于 BeanFactory / ApplicationContext。
Bean的作用域
作用域类型 | 概述 |
---|---|
singleton | 一个 IOC 容器中只有一个【默认值】 |
prototype | 每次获取创建一个 |
request | 一次请求创建一个(仅Web应用可用) |
session | 一个会话创建一个(仅Web应用可用) |
application | 一个 Web 应用创建一个(仅Web应用可用) |
websocket | 一个 WebSocket 会话创建一个(仅Web应用可用) |
AOP
AOP思想的实现是基于代理模式 ,在JAVA中一般采用JDK动态代理模式,但是JDK动态代理模式
只能代理接口而不能代理类。所以Spring AOP 同时支持 CGLIB、JDK
动态代理。
如果目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类。
如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类。
概述BeanDefinition
BeanDefinition: Spring容器创建对象的图纸,一个对象对应一个图纸。
BeanFactoryPostProcessor: Spring容器创建对象,修改图纸。
BeanDefinition
描述了 IOC
容器中 bean
的元信息,它包含 bean
的类信息、属性、行为、依赖关系、配置信息等。BeanDefinition
具有层次性,并且可以在 IOC
容器初始化阶段被 BeanDefinitionRegistryPostProcessor
构造和注册,被 BeanFactoryPostProcessor
拦截修改等。
Spring使用注解注入属性为null的问题排查
保证Bean能够交付给Spring容器进行统一的管理:
1、先检查一下自己的Spring配置
2、组件上面是否加入了合适的注解。例如:@Controller,@Service, @Repository,@Component等
3、检查一下你context-scan扫描器配置的路径是否正确,即你的包扫描器是否能够扫描到你的注解组件
检查实例化的方式:
可能是获取对象的方式出错了。
当我们想要从中获取一个Bean的实例时,应该从Spring容器当中获取。
而如果我们自己new一个类的实例,便绕过了容器的依赖注入过程,因此也可能出现获取不到应有的属性这种情况。
检查注入的位置:
如果把@Autowired注解加在了一个静态属性上,也无法注入属性值。
Spring帮助我们做属性的自动注入,是在帮助我们实例化Bean之后完成的。而static属性标示该属性是归属于类而不是实例的。换句话说,当我们想给static属性注入值的时候,对象还没创建的。因此一定会出现注入为空的情况。
由于Bean初始化顺序导致的问题:
这种情况是在业务当中真实可能出现,并且并不好思考的地方。楼主遇到的场景是实现了一个Filter,在Filter当中需要注入业务层组件实现一些过滤逻辑。代码写完之后我发现ServerBean一直为空,我把上面三种方式可能出现问题的地方都做了检查也没有解决。后来Google了一下让我茅塞顿开。原来web容器启动是按照一定顺序的,即:Listener --> Filter -->Servlet。而我们的Service组件依赖与Servlet的Controller组件而创建,因此在创建Filter实例时,由于Servlet实例还没有初始化也会导致依赖无法注入的情况。