面经梳理-spring

题目

聊聊spring的IOC和AOP?其底层原理分别是什么?

IOC

没有使用spring,所有的对象均需要手动创建,相当于底层类控制了上层类,例如轮子类控制了汽车类,创建汽车对象的时候需要传入轮子的参数。
IOC意思就是控制反转,即上层类控制下层类,上层类的对象只需要指定需要什么类型的下层类对象,由外部容器提前创建好并注入,上层类对象本身不负责下层类对象的创建。提高了可维护性,下层类构造函数的变化不会影响上层类的构造函数。
spring基于反射原理扫描注解,将相关类生成对象注入到容器中,在需要的地方进行对象注入。

AOP

基于动态代理的方式增强原对象的方法。Spring AOP全称为Spring Aspect-Oriented Programming,即面向切面编程,是运行时织入的,那么运行时织入到底是怎么实现的呢?答案就是代理对象。代理对象又可以分为静态代理和动态代理。

静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。

Spring两种动态代理方式

  • JDK的动态代理

若目标对象实现了接口,spring默认使用JDK的动态代理。

优点:因为有接口,所以使系统更加松耦合;

缺点:为每一个目标类创建接口;

  • CGLib

若目标对象没有实现任何接口,spring使用CGLib进行动态代理

优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。

缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。

若目标对象实现了接口,但是强制cglib代理,则使用cglib代理

参考:
Spring IoC和AOP的实现原理解析

bean的生命周期,循环引用问题spring如何处理

bean的生命周期

Spring 容器只能管理 单例(singleton) 作用域的 Bean 的完整生命周期,对于 原型(prototype) 作用域的 Bean,Spring 容器只创建 bean 的实例后便会返回给用户,剩余的生命周期由用户控制。所以 Bean 的生命周期主要指的是 singleton 作用域 的 Bean。

具体生命周期参考:https://juejin.cn/post/7217693476494147641

循环引用问题spring如何处理

Spring 并不能解决所有 Bean 的循环依赖问题,Setter方法注入方式 在 Spring 中是不会产生循环依赖问题的,这主要是靠 三级缓存 机制。构造器注入无法解决循环依赖,因为 构造器注入 发生在 实例化阶段,而 Spring 解决循环依赖问题依靠的 三级缓存 在 属性注入阶段,也就是说调用构造函数时还未能放入三级缓存中,所以无法解决 构造器注入 的循环依赖问题。Spring 无法解决 原型作用域 出现的循环依赖问题,因为 Spring 不会缓存 原型 作用域的 Bean,而 Spring 依靠 缓存 来解决循环依赖问题,所以 Spring 无法解决 原型 作用域的 Bean。

首先会获取 AService 对应的 Bean 对象。
先是调用 doGetBean() 中的第一个 getSingleton(beanName) 判断是否有该 Bean 的实例,有就直接返回了。(显然这里没有)
然后调用 doGetBean() 中的第二个 getSingleton() 方法来执行 doCreateBean() 方法。
先进行实例化操作(也就是利用构造函数实例化),此时实例化后生成的是原始对象。
将原始对象通过 lambda表达式 进行封装成 ObjectFactory 对象,通过 addSingletonFactory 加入三级缓存中。
然后再进行属性注入,此时发现需要注入 BService 的 Bean,会通过 doGetBean() 去获取 BService 对应的 Bean。
同样调用 doGetBean() 中的第一个 getSingleton(beanName) 判断是否有该 Bean 的实例,显然这里也是不会有 BService 的 Bean 的。
然后只能调用 doGetBean() 中的第二个 getSingleton() 方法来执行 doCreateBean() 方法来创建一个 BService 的 Bean。
同样地先进行实例化操作,生成原始对象后封装成 ObjectFactory 对象放入三级缓存中。
然后进行属性注入,此时发现需要注入 AService 的 Bean,此时调用调用 doGetBean() 中的第一个 getSingleton(beanName) 查找是否有 AService 的 Bean。此时会触发三级缓存,也就是调用 singletonFactories.get(beanName)。
因为三级缓存中有 AService 的原始对象封装的 ObjectFactory 对象,所以可以获取到的代理对象或原始对象,并且上移到二级缓存中,提前暴露给 BService 调用。
所以 BService 可以完成属性注入,然后进行初始化后,将 Bean 放入一级缓存,这样 AService 也可以完成创建。

参考:
浅谈 Spring Bean 的生命周期
浅谈 Spring 如何解决 Bean 的循环依赖问题
第二次讲Spring循环依赖,时长16分钟,我保证每一秒都是精华

Bean的作用域有几种?

Spring框架支持以下五种bean的作用域:

1.singleton :bean在每个Spring ioc 容器中只有一个实例。

2.prototype:一个bean的定义可以有多个实例。

3.request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。

4.session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

5.global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

参考:
解释Spring支持的几种bean的作用域?

BeanFactory和ApplicationContext有什么区别?BeanFactory和FactoryBean的区别?

BeanFactory和ApplicationContext有什么区别

BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象,它是SpringIoC容器标准化超接口!

ApplicationContext 是 BeanFactory 的子接口。它扩展了以下功能:

更容易与 Spring 的 AOP 功能集成
消息资源处理(用于国际化)
特定于应用程序给予此接口实现,例如Web 应用程序的 WebApplicationContext
简而言之, BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加了更多特定于企业的功能。 ApplicationContext 是 BeanFactory 的完整超集!

FactoryBean和BeanFactory区别

**FactoryBean **是 Spring 中一种特殊的 bean,可以在 getObject() 工厂方法自定义的逻辑创建Bean!是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject() 方法来得到其所生产的 Bean。因此,FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean。

一般情况下,整合第三方框架,都是通过定义FactoryBean实现!!!

BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例如 getBean() 方法获取指定的 bean 实例。它可以从不同的来源(例如 Mysql 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如,ApplicationContext 接口)提供了额外的强大功能。

总的来说,FactoryBean 和 BeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能,而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。

参考:
赵伟风老师SSM框架实战精讲

Spring AOP和AspectJ AOP有什么区别?

Spring AOP是基于Spring IoC实现的,它解决大部分常见的需求,但它并不是一个完整的AOP解决方案。对于非Spring容器管理的对象,它更没有办法了。而AspectJ旨在提供完整的AOP方案,因此也会更复杂。
而Spring AOP是运行时织入的,主要使用了两种技术:JDK动态代理和CGLIB代理。对于接口使用JDK Proxy,而继承的使用CGLIB。AspectJ是在运行前织入的,分为三类:编译时织入、编译后织入、加载时织入。
Aspectj不受类的特殊限制,不管方法是private、或者static、或者final的,都可以代理,spring AOP只能代理实例方法。
AspectJ性能更好,功能更强,使用更复杂。

参考:
Spring AOP与AspectJ的对比及应用
AspectJ 静态代理实践
Java JDK代理、CGLIB、AspectJ代理分析比较

Spring的事务有了解?

编程式事务是指手动编写程序来管理事务,即通过编写代码的方式直接控制事务的提交和回滚。在 Java 中,通常使用事务管理器(如 Spring 中的 PlatformTransactionManager)来实现编程式事务。

编程式事务的主要优点是灵活性高,可以按照自己的需求来控制事务的粒度、模式等等。但是,编写大量的事务控制代码容易出现问题,对代码的可读性和可维护性有一定影响。

声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。

开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作!

使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。

区别:

编程式事务需要手动编写代码来管理事务
而声明式事务可以通过配置文件或注解来控制事务。

参考:
赵伟风老师SSM框架实战精讲

Spring的事件类型有几种?

Spring 提供了以下5中标准的事件:

上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
除了上面介绍的事件以外,还可以通过扩展ApplicationEvent 类来开发自定义的事件。

参考:
经典面试题-Spring框架中有哪些不同类型的事件?

Springmvc的流程?

Springmvc流程

参考:
赵伟风老师SSM框架实战精讲

SpringMvc的控制器是不是单例模式,如果是,有什么问题,怎么解决?

单例,实例变量的线程安全问题,可以用@Scope(“prototype”)改为多例的模式,或者实例变量用threadlocal变量。

参考:
SpringMVC控制器是不是单例模式

了解Springboot吗?那讲一下Springboot的启动流程吧

初始化信息
发布启动等事件,方便外部拓展,读取环境配置信息(把几个配置文件都扫一遍)
实例化一个上下文
准备上下文
ioc容器加载
解析启动类,加载自动配置类
创建内嵌服务器

参考:
小米二面:你知道SpringBoot项目是如何启动的吗?

讲一讲Spring和Springboot的区别

springboot 自动导依赖,自动版本仲裁,自动扫包(主程序所在包及其子包),自动配置,自动导入组件

自动配置流程细节梳理:
1、导入starter-web:导入了web开发场景
● 1、场景启动器导入了相关场景的所有依赖:starter-json、starter-tomcat、springmvc
● 2、每个场景启动器都引入了一个spring-boot-starter,核心场景启动器。
● 3、核心场景启动器引入了spring-boot-autoconfigure包。
● 4、spring-boot-autoconfigure里面囊括了所有场景的所有配置。
● 5、只要这个包下的所有类都能生效,那么相当于SpringBoot官方写好的整合功能就生效了。
● 6、SpringBoot默认却扫描不到 spring-boot-autoconfigure下写好的所有配置类。(这些配置类给我们做了整合操作),默认只扫描主程序所在的包。
2、主程序:@SpringBootApplication
● 1、@SpringBootApplication由三个注解组成@SpringBootConfiguration、@EnableAutoConfiguratio、@ComponentScan
● 2、SpringBoot默认只能扫描自己主程序所在的包及其下面的子包,扫描不到 spring-boot-autoconfigure包中官方写好的配置类
● 3、@EnableAutoConfiguration:SpringBoot 开启自动配置的核心。
○ 1. 是由@Import(AutoConfigurationImportSelector.class)提供功能:批量给容器中导入组件。
○ 2. SpringBoot启动会默认加载 142个配置类。
○ 3. 这142个配置类来自于spring-boot-autoconfigure下 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件指定的
○ 项目启动的时候利用 @Import 批量导入组件机制把 autoconfigure 包下的142 xxxxAutoConfiguration类导入进来(自动配置类)
○ 虽然导入了142个自动配置类
● 4、按需生效:
○ 并不是这142个自动配置类都能生效
○ 每一个自动配置类,都有条件注解@ConditionalOnxxx,只有条件成立,才能生效
3、xxxxAutoConfiguration自动配置类
● 1、给容器中使用@Bean 放一堆组件。
● 2、每个自动配置类都可能有这个注解@EnableConfigurationProperties(ServerProperties.class),用来把配置文件中配的指定前缀的属性值封装到 xxxProperties属性类中
● 3、以Tomcat为例:把服务器的所有配置都是以server开头的。配置都封装到了属性类中。
● 4、给容器中放的所有组件的一些核心参数,都来自于xxxProperties。xxxProperties都是和配置文件绑定。
● 只需要改配置文件的值,核心组件的底层参数都能修改
4、写业务,全程无需关心各种整合(底层这些整合写好了,而且也生效了)

核心流程总结:
1、导入starter,就会导入autoconfigure包。
2、autoconfigure 包里面 有一个文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面指定的所有启动要加载的自动配置类
3、@EnableAutoConfiguration 会自动的把上面文件里面写的所有自动配置类都导入进来。xxxAutoConfiguration 是有条件注解进行按需加载
4、xxxAutoConfiguration给容器中导入一堆组件,组件都是从 xxxProperties中提取属性值
5、xxxProperties又是和配置文件进行了绑定

效果:导入starter、修改配置文件,就能修改底层行为。

参考:
尚硅谷SpringBoot3全栈指南

实现一个springbootstarter

具体不展开实践了,原理如同上述的自动配置流程,具体案例可以参考:手把手教你手写一个最简单的 Spring Boot Starter

参考:
手把手教你手写一个最简单的 Spring Boot Starter

Springboot的如何去掉不用的配置,springboot不是开箱即用么?怎么把不要的配置去掉呢?

1
2
3
4
5
6
7
8
@SpringBootApplication(excludeName = {"org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration"})
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

}

在yml配置文件中添加排除配置

1
2
3
spring:
autoconfigure:
exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

参考:
排除不需要的自动配置类


面经梳理-spring
https://fattree.cn/2023/10/22/面经梳理-spring/
作者
fattree
发布于
2023年10月22日
许可协议