当前位置: 首页 > news >正文

Spring面试题总结

Spring如何设计容器的,BeanFactory 和 ApplicationContext 有什么区别?

二者都是IOC容器对象,是Spring对Bean做一个管理的容器,会维护这些bean

BeanFactory是Spring容器的基础接口,提供了基础的容器访问能力。

BeanFactory提供懒加载方式,只有通过getBean方法调用获取Bean才会进行实例化。

ApplicationContext继承自BeanFactory接口,ApplicationContext包含了BeanFactory中所有的功能。

ApplicationContext相较于BeanFactory的区别:

  • 支持 AOP(BeanPostProcessor、BeanFactoryPostProcessor)。
  • 事件发布机制(ApplicationEventPublisher)。
  • ApplicationContext采用的是预加载,每个Bean都在ApplicationContext启动后实例化。

说下FactoryBean和BeanFactory有什么区别?

  • BeanFactory:是 Bean 的工厂, ApplicationContext 的父类,IOC容器的核心,负责生产和管理 Bean 对象。
  • FactoryBean:是 Bean,可以通过实现, FactoryBean 接口定制实例化,Bean 的逻辑,通过代理一个Bean对象,对方法前后做一些操作(通常是用来创建比较复杂的bean)。

FactoryBean

  • FactoryBean 是一个特殊的 Bean 工厂接口,用于 创建其他 Bean 实例
  • 你可以通过实现 FactoryBean 接口来控制如何创建某个 Bean(例如,动态创建、延迟初始化等)。

功能

  • FactoryBean 用于自定义 Bean 的创建过程,使得 Bean 的实例化更加灵活。
  • 实现 FactoryBean 后,你可以在 Spring 容器中声明一个 FactoryBean 的 Bean,它的 getObject() 方法会返回真正的 Bean 实例

BeanFactory

  • BeanFactory 是 Spring 中最基础的接口之一,用于管理和获取 Bean 实例
  • BeanFactory 是 Spring IoC 容器的核心接口之一,负责 懒加载实例化 管理。

功能

  • BeanFactory 是用来获取 Bean 的容器,它负责管理 Spring 中的 Bean 定义,懒加载 Bean,只有在需要时才实例化 Bean。

了解事务传播行为吗?Spring事务中有几种事务传播行为?

事务传播行为是为了解决业务层方法之间互相调用的事务问题,简单的理解就是多个事务方法相互调用时,事务如何在这些方法间传播

举个例子,方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。

在Spring源码中这七种类型被定义为了枚举。(前两种最常用)

事务行为说明
PROPAGATION_REQUIRED支持当前事务。假设当前没有事务。就新建一个事务
PROPAGATION_SUPPORTS支持当前事务。假设当前没有事务,就以非事务方式运行
PROPAGATION_MANDATORY支持当前事务,假设当前没有事务,就抛出异常
PROPAGATION_REQUIRES NEW新建事务,假设当前存在事务。把当前事务挂起
PROPAGATION_NOT SUPPORTED以非事务方式运行操作。假设当前存在事务,就把当前事务挂起
PROPAGATION_NEVER以非事务方式运行,假设当前存在事务,则抛出异常
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操
作。

Spring的单例Bean是否有并发安全问题

当多个用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑,如果业务逻辑有对单例状态的修改(体现为此单例的成员属性),则必须考虑线程安全问题。

实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如view model对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton"变更为"prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。

无状态bean和有状态bean

  • 有实例成员变量的bean,可以保存数据,是非线程安全的
  • 没有实例成员变量的bean,不能保存数据,是线程安全的
public class TestManagerImpl implements TestManager{

    private User user;

    //有一个记录信息的实例
    public void deleteUser(user e)throws Exception {
        //1
        user =e:
        prepareData(e);
    }

    public void prepareData(user e)throws Exception {
        user =getuserByID(e.getId);
        ....
        //使用user.getId();
        :....
        //2
        .....
    }
    一个有状态的bean
}

如何解决有状态bean的线程安全问题

对于无状态的bean适合用单例模式

有状态的Bean在多线程环境下不安全,一般用Prototype模式或使用ThreadLocal解决线程安全问题

<bean id="testManager" class="com.lcc.TestManagerImpl" scope="singleton">

<bean id="testManager" class="com.lcc.TestManagerImpl" scope="prototype">

如果说,配置成prototype那就是多例bean,每一个线程过来都会new一个实例

如果说保留Singleton,那么需要对某个实例成员变量,加上ThreadLocal,将这个成员变量给到我们的ThreadLocal里,这样对于每一个线程都有一份属于自己的变量

Spring有哪些拓展点(IOC流程)

换句话说,你是否知道一个Bean是如何被创建出来的

1.首先我们基于xml或者注解的方式,定义哪些需要类需要创建Bean,然后加载BeanDefination到Map中,接下来第一个拓展点是BeanFactoryPostProcessor,这个后置处理器会作用在所有Bean身上,常见的:

  • ConfigurationClassPostProcessor

    • 这是Spring内部使用的处理器,用于处理基于Java配置类的注解(如@Configuration@Bean等)。
    • 它负责扫描并注册所有带有@Configuration注解的类,并将它们作为bean定义添加到Spring容器中。

程序中可以有多个beanFactory后置处理器,那么优先执行谁呢?那就按照下面这个规则

  • 实现了优先级接口的
  • 实现了排序接口的
  • 按照配置文件顺序来执行

2.接下来就通过反射,将BeanDefinationMap中的对象进行实例化,实例化中存在什么内容呢,请看如下:

先填充属性,这一步会存在循环依赖问题,Spring通过三级缓存完成对象的提前暴露

接下来设置Aware接口的属性,再通过BeanPostProcessorbefore方法对Spring进行扩展

接下来如果说这个bean实现了initializingBean的话,那么会执行AfterPropertiesSet方法

然后再执行init-method方法(<bean init-method>可以指定初始化方法走哪一个),最后执行BeanPostProcessor的after方法(如果需要动态代理,那么就在这个方法中生成)

常见的BeanPostProcessor如下:

说说Spring框架中Bean的生命周期

对象的实例化 -> 对象的初始化 -> 使用完整对象 -> 对象的销毁

使用完整对象

什么是循环依赖,Spring如何解决循环依赖

循环依赖(Circular Dependency) 指的是 两个或多个 Bean 之间相互依赖,导致 Spring 在创建 Bean 时出现死循环。例如:

A 依赖 B,B 也依赖 A,Spring 在实例化时会陷入循环创建。

1️⃣ 三级缓存(Three-level Cache)机制

Spring 在 实例化 Bean 时,提前暴露引用,通过三级缓存避免循环依赖:

  • 一级缓存(singletonObjects):存放完全初始化的 Bean
  • 二级缓存(earlySingletonObjects):存放已经实例化但未填充属性的 Bean
  • 三级缓存(singletonFactories):存放 Bean 的 ObjectFactory,可以在必要时创建代理对象
2️⃣ 具体过程
  1. 实例化 A(但还没填充属性),将 A 的 ObjectFactory 放入 三级缓存
  2. 实例化 B,发现它依赖 A,于是去缓存找 A。
  3. 从三级缓存中获取 A 的引用,提前暴露给 B,B 依赖注入成功。
  4. 回过头来完成 A 的初始化,最终 A 和 B 都创建成功。

循环依赖连环拷打

(1)构造器注入产生的循环依赖能够解决吗?

答:不能解决,因为 Spring 在实例化时就需要构造函数的参数,但这些参数本身还未被创建,导致死循环。

(2)多例Bean对象 setter注入产生的循环依赖能够解决吗?

答:不能解决,因为 Spring 不会缓存 Prototype Bean,每次 getBean() 都会创建一个新实例,无法通过 三级缓存机制 解决循环依赖。

(3)只有一级缓存能够解决循环依赖吗?

单从解决循环依赖的角度,能解决的,但是使用过程会有问题,比如:如果 Bean A 依赖 Bean B,而 Bean B 还未完成初始化就被 A 使用,可能导致空指针或数据异常。

(4)只有一级缓存和二级缓存能够解决循环依赖吗?

单从解决循环依赖的角度,能解决的,但仍然有 AOP 代理失效的问题。比如:A需要生成代理对象,但是判断生成代理对象的逻辑是在BeanPostProcessor的after,也就是生命周期最后阶段,但是A依赖于B的属性填充是在早期阶段,当使用二级缓存,A的原始半成品对象赋值给了B,这样B在调用相关的A方法,走的就不是AOP代理了

而三级缓存中,存的ObjectFactory中调用getObject会判断当前Bean是否需要生成代理对象,如果需要,那我会提前生成,而后的BeanPostProcessor就不会重复生成了

(5)只有一级缓存和三级缓存能够解决循环依赖吗?

如果省略了二级缓存,Spring 需要从三级缓存获取 Bean 时,每次都会重新调用 ObjectFactory 创建对象,导致多个不同实例被使用,进而影响应用逻辑。

具体问题

  1. A 依赖 BB 依赖 A,Spring 先创建 A 的“半成品”放入三级缓存singletonFactories)。
  2. B 需要 A 时,Spring 从三级缓存取出 AObjectFactory,然后创建 A 并返回给 B
  3. A 继续初始化完成,放入一级缓存singletonObjects)。
  4. 问题:如果 A 还没有完成初始化,其他线程再次请求 A,会再次触发 ObjectFactory,创建一个新的 A,导致多个实例出现!

这样就违背了A的单例原则

Spring事务在哪几种情况下会失效?

1.数据库引擎不支持事务

以MySQL为例,5.5之前存储引擎默认是MyISAM,这是不支持事务的

2.没有被Spring管理

如果此时@Service注解注释掉,这个类就不会被加载成一个Bean,那么这个类就不会被Spring管理,事务自然就失效了

3.方法不是public

Spring事务也是通过动态代理来实现的,在对一个Bean进行初始化的过程中,在执行到第八个后置处理器AbstractAutoProxyCreator后,其中有个方法的判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务

if(allowPublicMethodsOnly() && !Modifier,isPublic(method.getModifiers())){
    return null;
}

4.方法用final修饰

Spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能

但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,从而实现事务功能

5.方法内部调用

Spring的事务是基于动态代理对象实现的,但是,this.updateStatus中的this是不是代理对象,所以transactional不会生效,可以通过以下方式解决(自己注入自己):

@Service
public class UserService{

    @Autowired
    private UserService userService

    userSearvice.update...
}

6.异常被吃了

就是方法内部发生异常,但是被try...catch了

7.事务的传播行为

由于不同的事务传播行为,一些事务失效了

Bean注入容器有哪些方式

1.@Configuration+@Bean

2.包扫描

@ComponentScan扫描指定路径下标注了@Controller,@Service,@Repository,@Component的类

3.@Import注解导入

4.实现BeanDefinitionRegistryPostProcessor进行后置处理

在Spring容器启动的时候会执行BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,就是等beanDefinition加载完成之后,对beanDefinition进行后置处理,可以在此进行调整IOC容器中的beanDefition,从而干扰到后面进行初始化bean。

比如我们可以手动向beanDefinitionRegistry中注册Person的BeanDefinition。那么后续Spring就会扫描到这个BeanDefinition并进行注册

5.可以通过注入FactoryBean

FactoryBean可以包装Person,那么我们注入了FactoryBean,也是可以通过这个FactoryBean从容器中获取Person的

说说AOP的实现原理

Spring的AOP实现原理其实很简单,就是通过动态代理实现的。如果我们为Spring的某个bean配置了切面,那么Spring在创建这个bean的时候,实际上创建的是这个bean的一个代理对象,我们后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。而Spring的AOP使用了两种动态代理,分别是JDK的和CGLIB的动态代理

目标类实现了接口,采用JDK动态代理,否则采用CGLIB

Spring的AOP中有哪些Advice类型

说说Spring中@EnableAspectJAutoProxy的原理

@EnableAspectJAutoProxy 本质上 导入了 AspectJAutoProxyRegistrar,并 注册 AnnotationAwareAspectJAutoProxyCreator 这个 BeanPostProcessor。

这个后置处理器做以下事情:

  • 在 Spring Bean 初始化之前,它会扫描所有 @Aspect 注解的 Bean。
  • 识别切面方法(@Before, @After, @Around 并自动创建代理对象。
  • 根据 proxyTargetClass = true 选择 JDK 动态代理CGLIB 代理

说说Spring AOP自动动态代理的实现过程

在我们有了 Join point(连接点)、Pointcut(切点)、Advice(通知)以及 Aspecy)(切面)后,我们应该如何将他们"织入"我们的应用呢?在 Sping AOP 中提供了自动代理的实现,底层借助JDK 动态代理和 CGLIB 动态代理创建对象。

在 Spring IOC 中 Bean 的加载过程,在整个过程中,Bean 的实例化前和初始化后等生命周期阶段都提供了扩展点,会调用相应的BeanPostProcessor 处理器对 Bean 进行处理。当我们开启了 Aspecy 自动代理(例如通过 @EnableAspecyAutoProxy注解),则会往IOC容器中注册一个 AbstractAutoProxyCreator 自动代理对象,该对象实现了几种 BeanPostProcessor,例如在每个 Bean 初始化后会被调用,解析出当前 Spring上下文中所有的 Advisor(会缓存),如果这个 Bean 需要进行代理,则会通过JDK动态代理或者 CGLIB 动态代理创建一个代理对象并返回,所以得到的这个 Bean 实际上是一个代理对象。这样一来,开发人员只需要配置好 Aspect 相关信息,Spring 则会进行自动代理,和 Spring IOC 完美地整合在一起。

那再谈谈JDK动态代理和CGLIB动态代理的区别

JDK动态代理

如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是 InvocationHandler接口和 proxy 类。

特点:目标类必须有实现的接口。如果某个类没有实现接口,那么这个类就不能用JDK动态代理。

CGLIB动态代理

通过继承实现,如果目标类没有实现接口,那么SpringAOP会选择使用CGLB来动态代理目标类。CGLlB可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。

CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用CGLIB做动态代理的。

优点:目标类不需要实现特定的接口,更加灵活。

总结

何时采用哪种动态代理?
1.如果目标对象实现了接口,默认情况下会采用IDK的动态代理实现AOP
2.如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3.如果目标对象没有实现了接口,必须采用CGLIB库

两者的区别

  • 1.jdk动态代理使用jdk中的类Proxy来创建代理对象,它使用反射技术来实现,不需要导入其他依赖。cglib需要引入相关依赖:asm.jar,它使用字节码增强技术来实现。
  • 2.当目标类实现了接口的时候Spring Aop默认使用jdk动态代理方式来增强方法,没有实现接口的时候使用cglib动态代理方式增强方法。

相关文章:

  • 剪映5.9版本——免费字幕识别功能的全能解决方案
  • 2025 聚合易支付完整版PHP网站源码
  • 【算法】acwing算法基础875. 快速幂
  • ArcGIS Pro应用指南:如何为栅格图精确添加坐标信息
  • Rocky Linux 系统安装 typecho 个人博客系统(Docker 方式)
  • 基于SpringBoot的校园二手交易平台(源码+论文+部署教程)
  • TCP的四次挥⼿为什么是四次?为什么不能是三 次
  • Tailwind CSS 问题:npm error could not determine executable to run
  • FFmpeg-chapter3和chapter4-读取视频流(原理篇和实战篇)
  • 点大商城V2-2.6.6.1全能版源码+最新排队免单插件功能
  • Error:java: 错误: 不支持发行版本 14
  • 千峰React:外部库引用
  • SSH监控
  • 深度学习模型与前端结合
  • SimPro - 轻量级协议模拟器
  • [Git]克隆仓库报错warning: remote HEAD refers to nonexistent ref, unable to checkout
  • CAP定理和BASE理论 趣学!
  • Kafka Connect连接器的全生命周期:
  • Nat Mach Intell | AI分子对接算法评测
  • 魔改switch样式
  • 有哪些网站可以做h5/免费发布推广的网站有哪些
  • 网站模板被抄袭怎么办/seo销售话术开场白
  • 宝安区 疫情/网站关键词怎么优化排名
  • 西安网站建设陕icp/百度竞价推广有哪些优势
  • 零基础学习网站建设/搜索引擎优化的含义
  • 湖南省住房和城乡建设厅网站/什么软件可以排名次