大厂面试攻略:Spring框架核心要点精讲

本文涉及的产品
实时数仓Hologres,5000CU*H 100GB 3个月
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
实时计算 Flink 版,5000CU*H 3个月
简介: Java SPI (Service Provider Interface) 是一种服务发现机制,允许在运行时动态加载和发现服务提供者。在数据库驱动加载中,SPI使得数据库驱动能够自动识别和注册,而无需显式加载。Spring 是一个广泛应用的轻量级框架,核心功能包括依赖注入(DI)和面向切面编程(AOP)。不使用Spring时,开发人员需要手动管理对象的创建和依赖关系,使用Servlet等基础组件完成Web开发,以及手动处理JDBC操作。Spring通过管理Bean的生命周期和依赖关系,简化了企业级应用的开发,降低了代码的侵入性。

1.Java SPI

Java SPI(Service Provider Interface)是一种服务提供机制,它允许在运行时动态地加载和发现服务提供者。在数据库驱动加载中,SPI机制使得数据库驱动能够在不需要显式加载的情况下被自动识别和注册。
image.png

1.Spring

1.0 如果不使用 Spring,我们将如何开发?

曾经有一道面试题掀起了劲爆的浪潮,说如果不使用 Spring,我们将如何开发?好多家公司都模仿提问了这么一道面试题,而且好多人也都在各个社区给出了自己的答案。接下来看看网友们是怎么说的。有说手写 IoC 的,有说用 Servlet 完成 Web 开发的,有说用纯 JDBC 完成数据库操作的等等,大家回答各有千秋。其实,想想我们上一代的程序员,没有 Spring 不也照样开发出非常多优秀的系统。
image.png
那我们使用 Spring 到底能给我们带来哪些好处呢?
根据我对 Spring 10 多年的研究经验,给大家做以下总结。
首先,Spring 是一个轻量级的应用框架。当然,众所周知,它提供了 IoC 和 AOP 这两个核心的功能。

它的核心目的是简化企业级应用程序的开发,使得开发者只需要关心业务需求,不需要关心 Bean 的管理,以及通过切面增强功能减少代码的侵入性。

当然,Spring 发展到现在俨然不只是一个框架,而是一个生态,我们习惯性地称之为 Spring 全家桶。 Spring 大约有 20 个模块,由 1300 多个不同的文件构成。这些模块可以分为核心容器、AOP 和设备支持、数据访问与集成、Web 组件、通信报文和集成测试、集成兼容等类。如图所示:
组成 Spring 框架的每个模块都可以单独存在,也可以将一个或多个模块联合实现。
2、使用 Spring 的好处
image.png
任何一个技术框架的出现,一定为了实现某些业务场景或者是某一类技术问题的解决方案。当大家有了开发经验之后,对一些技术框架,要有自己的见解和思考。我自己在面试的时候也喜欢问这一类的问题。

1.1能回答一下 Spring Bean 生命周期的执行流程吗

Spring 生命周期全过程大致分为五个阶段:创建前准备阶段、创建实例阶段、依赖注入阶段、
容器缓存阶段和销毁实例阶段。
这张是 Spring Bean 生命周期完整流程图,其中对每个阶段的具体操作做了详细介绍:

  1. 创建前准备阶段: 在这个阶段,Spring容器会对Bean进行加载前的准备工作。这包括解析Bean的定义、查找扩展配置(如init-method和destroy-method)、以及执行BeanFactoryPostProcessor接口的实现类。BeanFactoryPostProcessor可以在Bean实例化之前对Bean的定义进行修改。
  2. 创建实例阶段: Spring容器使用反射机制根据Bean的定义创建Bean的实例对象。同时,Spring会扫描和解析Bean声明的属性。
  3. 依赖注入阶段: 如果Bean实例需要依赖其他Bean对象,则Spring会进行依赖注入。依赖注入可以通过构造函数注入、Setter方法注入或字段注入等方式实现。在这个阶段,还会触发一些扩展点的调用,如BeanPostProcessor接口的实现类。BeanPostProcessor可以在Bean初始化前后进行一些额外的处理。
  4. 容器缓存阶段: 在这个阶段,Spring会将已创建的Bean实例保存到容器中,以及Spring的缓存中。这意味着Bean已经可以被应用程序使用了。在这个阶段,会执行init-method配置的方法,以及BeanPostProcessor接口的后置处理方法,如postProcessAfterInitialization。
  5. 销毁实例阶段: 当Spring应用上下文关闭时,容器中的所有Bean都会被销毁。如果Bean实现了DisposableBean接口,或者在配置中指定了destroy-method属性,则会调用相应的销毁方法。这个阶段确保资源的释放和清理工作得以进行。

以上是Spring Bean生命周期的主要阶段,理解这些阶段可以帮助开发者更好地管理Bean的生命周期,确保Bean在应用程序中的正确创建、初始化和销毁。

1.2 Spring 中 Bean 的作用域有哪些?

“嗯,Spring中Bean的作用域有单例(singleton)、原型(prototype)、会话(session)和请求(request)这几种。”
好的,这个问题可以从几个方面来回答。

首先呢,Spring 框架里面的 IOC 容器,可以非常方便地去帮助我们管理应用里面的 Bean 对象实例。我们只需要按照 Spring 里面提供的 xml 或者注解等方式去告诉 IOC 容器,哪些 Bean 需要被 IOC 容器管理就行了。

其次呢,既然是 Bean 对象实例的管理,那意味着这些实例,是存在生命周期,也就是所谓的作用域。理论上来说,常规的生命周期只有两种:

  • singleton, 也就是单例,意味着在整个 Spring 容器中只会存在一个 Bean 实例。

  • prototype,翻译成原型,意味着每次从 IOC 容器去获取指定 Bean 的时候,都会返回一个新的实

    例对象。
    

但是在基于 Spring 框架下的 Web 应用里面,增加了一个会话维度来控制 Bean 的生命周期,主要有三个选择

  • request, 针对每一次 http 请求,都会创建一个新的 Bean

  • session,以 sesssion 会话为纬度,同一个 session 共享同一个 Bean 实例,不同的 session 产生不同的 Bean 实例

  • globalSession,针对全局 session 纬度,共享同一个 Bean 实例

    以上就是我对这个问题的理解。

技术框架的本质是去解决特定问题的,所以如果能够站在技术的角度去思考 Spring”
当遇到这种问题的时候,就可以像这个高手的回答一样,能够基于场景来推断出答案。
就像我们现在写 CRUD 代码,它已经变成了一种基本能力去让我们完成复杂业务逻辑的开发。
image.png

1.3 Spring 中 BeanFactory 和 FactoryBean 的区别

关于这个问题,我从几个方面来回答。
首先,Spring 里面的核心功能是 IOC 容器,所谓 IOC 容器呢,本质上就是一个 Bean 的容器或者是一个 Bean 的工厂。
它能够根据 xml 里面声明的 Bean 配置进行 bean 的加载和初始化,然后 BeanFactory 来生产我们需要的各种各样的 Bean。

  • BeanFactory 是所有 Spring Bean 容器的顶级接口,它为 Spring 的容器定义了一套规范,并提供

    像 getBean 这样的方法从容器中获取指定的 Bean 实例。
    
  • BeanFactory 在产生 Bean 的同时,还提供了解决 Bean 之间的依赖注入的能力,也就是所谓的

    DI。
    

FactoryBean 是一个工厂 Bean,它是一个接口,主要的功能是动态生成某一个类型的 Bean 的实例,
也就是说,我们可以自定义一个 Bean 并且加载到 IOC 容器里面。
它里面有一个重要的方法叫 getObject(),这个方法里面就是用来实现动态构建 Bean 的过程。 Spring Cloud 里面的 OpenFeign 组件,客户端的代理类,就是使用了 FactoryBean 来实现的。

面试点评:
这个问题,只要稍微看过 Spring 框架的源码,怎么都能回答出来。
关键在于你是否愿意逼自己去学习一些工作中不常使用的技术来提升自己。

1.4 Spring 是如何解决循环依赖问题的?

我们都知道,如果在代码中,将两个或多个 Bean 互相之间持有对方的引用就会发生循环依赖。循环的依赖将会导致陷入死循环。这是 Spring 发生循环依赖的原因。
循环依赖有三种形态:

  • 第一种互相依赖:A 依赖 B,B 又依赖 A,它们之间形成了循环依赖

image.png

  • 第二种三者间依赖:A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖。

image.png

  • 第三种是自我依赖:A 依赖 A 形成了循环依赖。

image.png
而 Spring 中设计了三级缓存来解决循环依赖问题,当我们去调用 getBean()方法的时候,Spring 会先从一级缓存中去找到目标 Bean,如果发现一级缓存中没有便会去二级缓存中去找,而如果一、二级缓存中都没有找到,意味着该目标 Bean 还没有实例化。于是,Spring 容器会实例化目标 Bean(PS:刚初始化的 Bean 称为早期 Bean) 。然后,将目标 Bean 放入二级缓存中,同时,加上标记是否存在循环依赖。如果不存在循环依赖便会将目标 Bean 存入到二级缓存,否则,便会标记该 Bean 存在循环依赖,然后将等待下一次轮询赋值,也就是解析@Autowired 注解。等@Autowired 注解赋值完成后 (PS:完成赋值的 Bean 称为成熟 Bean) ,会将目标 Bean 存入到一级缓存。

这里我可以做个总结,我们来看这张图
Spring 一级缓存中存放所有的成熟 Bean,
二级缓存中存放所有的早期 Bean,先取一级缓存,再去二级缓存。
image.png

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 模拟一个服务接口
interface MyService {
   
   
    void doSomething();
}

// 实际服务类
class MyServiceImpl implements MyService {
   
   
    @Override
    public void doSomething() {
   
   
        System.out.println("Doing something...");
    }
}

// 代理工厂类,用于创建代理对象
class MyServiceProxyFactory implements FactoryBean<MyService> {
   
   
    @Autowired
    private MyServiceImpl myService;

    @Override
    public MyService getObject() throws Exception {
   
   
        // 创建代理对象,这里简单地返回原始对象,实际场景中可能会创建 AOP 代理等
        return myService;
    }

    @Override
    public Class<?> getObjectType() {
   
   
        return MyService.class;
    }

    @Override
    public boolean isSingleton() {
   
   
        return true;
    }
}

@Configuration
class AppConfig {
   
   
    @Bean
    public MyServiceImpl myService() {
   
   
        return new MyServiceImpl();
    }

    @Bean
    public MyServiceProxyFactory myServiceProxyFactory() {
   
   
        return new MyServiceProxyFactory();
    }
}

public class Main {
   
   
    public static void main(String[] args) {
   
   
        // 初始化 Spring 应用上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // 获取 MyService 实例
        MyService myService = context.getBean(MyService.class);
        myService.doSomething(); // 执行方法

        // 关闭应用上下文
        context.close();
    }
}

在这个示例中,MyService 是一个简单的服务接口,MyServiceImpl 是其实际实现类。MyServiceProxyFactory 是一个代理工厂类,用于创建代理对象。在 AppConfig 配置类中,我们定义了 myService() 和 myServiceProxyFactory() 方法,分别用于创建 MyServiceImpl 和 MyServiceProxyFactory 的 Bean。
当我们调用 getBean() 方法获取 MyService Bean 时,Spring 发现 MyService Bean 需要通过 MyServiceProxyFactory 来创建,因此会先创建 MyServiceProxyFactory Bean。在这个过程中,Spring 会将创建好的代理实例存储到三级缓存中。最终,创建好的 MyServiceImpl 实例会被同步到一级缓存中,以便下次获取该 Bean 时能够直接从一级缓存中获取。

1.5 代理对象是什么?三级缓存的作用?

代理对象是在软件设计模式中常见的一种对象,代表另一个对象,并控制对原始对象的访问。代理对象通常在客户端和实际对象之间充当中介,可以在访问实际对象时添加额外的逻辑、控制访问权限或者实现延迟加载等功能。

在 Spring 框架中,创建代理对象的主要目的是为了实现 AOP(面向切面编程)和事务管理等功能。代理对象可以用来在方法调用前后执行额外的逻辑,比如日志记录、性能监控、安全检查等。它还可以用来实现事务管理,比如在方法调用前开启事务,在方法调用后提交或回滚事务。

创建代理对象的具体原因包括但不限于:

  1. AOP功能:在 AOP 中,代理对象用于织入切面逻辑,实现横切关注点的代码重用。
  2. 事务管理:代理对象可以用来管理事务的生命周期,比如在方法调用前后开启、提交或回滚事务。
  3. 延迟加载:代理对象可以延迟加载实际对象,只有在需要时才真正创建和初始化实际对象。
  4. 权限控制:代理对象可以用来实现访问权限控制,根据用户的权限动态决定是否允许访问某些资源。
  5. 性能监控:代理对象可以用来监控方法的执行时间、调用次数等性能指标,以便进行性能优化。

总的来说,创建代理对象的目的是为了在访问实际对象时添加额外的逻辑或者控制访问权限,从而实现更加灵活和可扩展的系统功能。

扩展(了解):
三级缓存是用来存储代理 Bean,当调用 getBean()方法时,发现目标 Bean 需要通过代理工厂来创建, 此时会将创建好的实例保存到三级缓存,最终也会将赋值好的 Bean 同步到一级缓存中。

在 Spring 框架中,创建代理对象的主要原理涉及到两种方式:JDK 动态代理和 CGLIB(Code Generation Library)代理。下面我将详细介绍这两种代理方式的原理:

1. JDK 动态代理
JDK 动态代理是基于接口的代理,它利用了 Java 的反射机制来创建代理对象。当一个类实现了一个接口,并且要在方法执行前后添加额外的逻辑时,可以使用 JDK 动态代理。

创建接口:首先需要创建一个接口,定义需要被代理的方法。

创建目标类:目标类是实现了接口的类,它包含了具体的业务逻辑。

实现 InvocationHandler 接口:这是 JDK 动态代理的核心接口,它包含了一个 invoke() 方法,当代理对象调用方法时,该方法会被调用。在 invoke() 方法中,我们可以添加额外的逻辑。

使用 Proxy 类创建代理对象:在运行时,通过 Proxy 类的静态方法 newProxyInstance() 创建代理对象,同时指定目标类的 ClassLoader 和 InvocationHandler。Proxy 类会根据接口和 InvocationHandler 动态地创建一个代理类,并返回代理对象。

2. CGLIB 代理
CGLIB 代理是基于继承的代理,它利用字节码技术在运行时动态生成一个目标类的子类作为代理类。当一个类没有实现接口,或者需要代理的方法是 final 或 static 类型时,可以使用 CGLIB 代理。

创建目标类:目标类是我们要代理的类,它不需要实现任何接口。

创建 MethodInterceptor 实现类:MethodInterceptor 是 CGLIB 提供的接口,它包含了一个 intercept() 方法,用于拦截方法调用。我们需要创建一个 MethodInterceptor 的实现类,并在 intercept() 方法中添加额外的逻辑。

使用 Enhancer 类创建代理对象:通过 Enhancer 类的 create() 方法创建代理对象,同时指定目标类和 MethodInterceptor。Enhancer 类会在运行时动态生成目标类的子类,并覆盖目标方法,使得我们可以在方法调用前后添加自定义逻辑。

总的来说,无论是 JDK 动态代理还是 CGLIB 代理,都是在运行时动态生成代理对象,并在方法调用前后添加额外的逻辑。选择使用哪种代理方式取决于目标类是否实现了接口以及是否需要代理 final 或 static 方法。

第一级缓存里面存储完整的 Bean 实例,这些实例是可以直接被使用的。
第二级缓存里面存储的是实例化以后,但是还没有设置属性值的 Bean 实例,也就是 Bean 里面的
依赖注入还没有做。
第三级缓存用来存放 Bean 工厂,它主要用来生成原始 Bean 对象并且放到第二级缓存里面。三级缓存的核心思想,就是把 Bean 的实例化,和 Bean 里面的依赖注入进行分离。
采用一级缓存存储完整的 Bean 实例,采用二级缓存来存储不完整的 Bean 实例,
通过不完整的 Bean 实例作为突破口,解决循环依赖的问题。
至于第三级缓存,主要是解决代理对象的循环依赖问题。
以上就是我的理解。

1.6 Spring 中事务的传播行为有哪些?

事务传播行为来说,它解决的核心问题是,多个声明了事务的方法相互调用的时候存在事务嵌套问题,那么这个事务的行为应该如何进行传递?
image.png
比如说,methodA()调用 methodB(),两个方法都显示的开启了事务。
那么 methodB()是开启一个新事务,还是继续在 methodA()这个事务中执行?就取决于事物的传播行为。
所以,Spring 为了解决这个问题,定义了 7 种事务传播行为。

    1. REQUIRED:默认的 Spring 事务传播级别,如果当前存在事务,则加入这个事务,如果不存在事

      务,就新建一个事务。

    1. REQUIRE_NEW:不管是否存在事务,都会新开一个事务,新老事务相互独立。外部事务抛出异常

      回滚不会影响内部事务的正常提交。

    1. NESTED:如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,

      类似于 REQUIRE_NEW。

    1. SUPPORTS:表示支持当前事务,如果当前不存在事务,以非事务的方式执行。
    1. NOT_SUPPORTED:表示以非事务的方式来运行,如果当前存在事务,则把当前事务挂起。
    1. MANDATORY:强制事务执行,若当前不存在事务,则抛出异常.
    1. NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。

很多同学会尝试用背诵的方法去记下这些行为,但其实如果大家站在代码设计的角度去思考,是可以直接推导出来至少 5 种的吧。
好了,今天的分享就到这里,在面试的时候大家还有遇到哪些比较难的问题,欢迎在评论区留言。

1.7 Spring 里面的事务和分布式事务的使用如何区分,以及这两个事务之间有什么关联?

“Spring 里面的事务和分布式事务的使用如何区分,以及这两个事务之间有什么关联”
好的,面试官。

首先, 在 Spring 里面并没有提供事务,它只是提供了对数据库事务管理的封装。
通过声明式的事务配置,使得开发人员可以从一些复杂的事务处理中得到解脱,
我们不再需要关心连接的获取、连接的关闭、事务提交、事务回滚这些操作。
更加聚焦在业务开发层面。
所以,Spring 里面的事务,本质上就是数据库层面的事务,
这种事务的管理,主要是针对单个数据库里面多个数据表操作的,去满足事务的 ACID 特性。 (如图)分布式事务,是解决多个数据库的事务操作的数据一致性问题,
传统的关系型数据库不支持跨库事务的操作,所以需要引入分布式事务的解决方案。
image.png
而 Spring 并没有提供分布式事务场景的支持,所以 Spring 事务和分布式事务在使用上并没有直接的关联性。
但是我们可以使用一些主流的事务解决框架,比如 Seata,集成到 Spring 生态里面,去解决分布式事务的问题。
以上就是我对这个问题的理解!

其实面试的时候不应该问这一类的问题,
因为 Spring 的事务和分布式事务虽然在名字上类似,但是完全就是两个概念。我估计这个问题,面试官考察的是一些刚毕业的小朋友吧。
image.png
Spring 是一个轻量级应用框架,它提供了 IoC 和 AOP 这两个核心的功能。
它的核心目的是简化企业级应用程序的开发,使得开发者只需要关心业务需求,不需要关心 Bean 的管理,
以及通过切面增强功能减少代码的侵入性。
从 Spring 本身的特性来看,我认为有几个关键点是我们选择 Spring 框架的原因。

  • 轻量:Spring 是轻量的,基本的版本大约 2MB。

  • IOC/DI:Spring 通过 IOC 容器实现了 Bean 的生命周期的管理,以及通过 DI 实现依赖注入,从

    而实现了对象依赖的松耦合管理。

  • 面向切面的编程(AOP):Spring 支持面向切面的编程,从而把应用业务逻辑和系统服务分开。

  • MVC 框架:Spring MVC 提供了功能更加强大且更加灵活的 Web 框架支持

  • 事务管理:Spring 通过 AOP 实现了事务的统一管理,对应用开发中的事务处理提供了非常灵活的

    支持.
    image.png
    (如图)最后,Spring 从第一个版本发布到现在,它的生态已经非常庞大了。在业务开发领域,Spring
    生态几乎提供了
    非常完善地支持,更重要的是社区的活跃度和技术的成熟度都非常高,以上就是我对这个问题的理解。

任何一个技术框架,一定是为了解决某些特定的问题,只是大家忽视了这个点。为什么要用,再往高一点来说,其实就是技术选型,能回答这个问题,
意味着面对业务场景或者技术问题的解决方案上,会有自己的见解和思考。
所以,我自己也喜欢在面试的时候问这一类的问题。

1.8 Spring 中有哪些方式可以把 Bean 注入 IOC 容器?

好的,把 Bean 注入 IOC 容器里面的方式有 几种方式

  1. XML配置:通过在XML文件中声明Bean的定义,Spring容器在启动时会加载并解析这个XML,将Bean装载到IOC容器中。13
  2. 注解方式
    • @ComponentScan:使用@ComponentScan注解来扫描声明了@Controller、@Service、@Repository、@Component注解的类,并将它们注册为Spring应用上下文中的Bean。13
    • @Configuration和@Bean:使用@Configuration注解声明配置类,并在其中使用@Bean注解定义Bean,这种方式是XML配置方式的一种演变。13
    • @Import:使用@Import注解导入配置类或普通的Bean。13
  3. 工厂Bean:使用FactoryBean工厂Bean,动态构建一个Bean实例,例如Spring Cloud OpenFeign中的动态代理实例就是使用FactoryBean来实现的。13
  4. 动态Bean定义
    • ImportBeanDefinitionRegistrar:实现ImportBeanDefinitionRegistrar接口,可以动态注入Bean实例,这个在Spring Boot启动注解中有用到。13
    • ImportSelector:实现ImportSelector接口,可以动态批量注入配置类或Bean对象,这个在Spring Boot自动装配机制中有用到。13
  5. 依赖注入
    • 构造器注入:通过构造器传递依赖项,Spring容器在创建Bean时自动注入。2
    • Setter注入:通过Setter方法传递依赖项,Spring容器在创建Bean后自动注入。2
    • 字段注入:通过直接在字段上使用@Autowired注解进行注入,Spring容器在创建Bean时自动注入。2
  6. Java配置:通过Java配置类,结合@Bean注解,可以直接在Java代码中定义Bean,并将其注册到IOC容器中。18

这道题目很有意义,要想更加优雅地去解决一些实际业务问题,首先得有足够多的工具积累。
你可曾想过,Bean 的注入竟然有这么多方式,而且还有些方式是没听过的呢?
image.png

1.9 介绍下 Spring IoC 的工作流程

好的,这个问题我会从几个方面来回答。
 IOC 是什么
 Bean 的声明方式
 IOC 的工作流程
IOC 的全称是 Inversion Of Control, 也就是控制反转,它的核心思想是把对象的管理权限交给容器。 应用程序如果需要使用到某个对象实例,直接从 IOC 容器中去获取就行,这样设计的好处是降低了程序里面对象与对象之间的耦合性。
image.png

image.png
Spring 里面很多方式去定义 Bean,(如图)比如 XML 里面的〈bean〉标签、@Service、@Component、 @Repository、@Configuration 配置类中的@Bean 注解等等。
Spring 在启动的时候,会去解析这些 Bean 然后保存到 IOC 容器里面。
image.png
Spring IOC 的工作流程大致可以分为两个阶段。
第一个阶段,就是 IOC 容器的初始化
这个阶段主要是根据程序中定义的 XML 或者注解等 Bean 的声明方式
(如图)通过解析和加载后生成 BeanDefinition,然后把 BeanDefinition 注册到 IOC 容器。
image.png
通过注解或者 xml 声明的 bean 都会解析得到一个 BeanDefinition 实体,实体中包含这个 bean 中定义的基本属性。
最后把这个 BeanDefinition 保存到一个 Map 集合里面,从而完成了 IOC 的初始化。
IoC 容器的作用就是对这些注册的 Bean 的定义信息进行处理和维护,它 IoC 容器控制反转的核心。第二个阶段,完成 Bean 初始化及依赖注入
然后进入到第二个阶段,这个阶段会做两件事情(如图)

  1. 通过反射针对没有设置 lazy-init 属性的单例 bean 进行初始化。
  2. 完成 Bean 的依赖注入。
    image.png
    第三个阶段,Bean 的使用
    (如图)通常我们会通过@Autowired 或者 BeanFactory.getBean()从 IOC 容器中获取指定的 bean 实例。
    另外,针对设置 layy-init 属性以及非单例 bean 的实例化,是在每次获取 bean 对象的时候,调用 bean 的初始化方法来完成实例化的,并且 Spring IOC 容器不会去管理这些 Bean。
    image.png
    面试点评
    对于工作原理或者工作流程性的问题,大家一定要注意回答的结构和节奏。否则面试官会觉得很混乱,无法理解,导致面试的效果大打折扣。
    image.png

2.0谈谈你对 Spring IOC 和 DI 的理解?

谈谈你对 Spring IOC 和 DI 的理解。

首先,Spring IOC,全称控制反转(Inversion of Control)。
(如图)在传统的 Java 程序开发中,我们只能通过 new 关键字来创建对象, 这种导致程序中对象的依赖关系比较复杂,耦合度较高。
image.png
(如图)而 IOC 的主要作用是实现了对象的管理,也就是我们把设计好的对象交给了 IOC 容器控制, 然后在需要用到目标对象的时候,直接从容器中去获取。
image.png
有了 IOC 容器来管理 Bean 以后,相当于把对象的创建和查找依赖对象的控制权交给了容器,这种设计理念
使得对象与对象之间是一种松耦合状态,极大提升了程序的灵活性以及功能的复用性。
然后,DI 表示依赖注入,也就是对于 IOC 容器中管理的 Bean,如果 Bean 之间存在依赖关系,那么 IOC 容器需要
自动实现依赖对象的实例注入,通常有三种方法来描述 Bean 之间的依赖关系。

  1. 接口注入
  2. setter 注入
  3. 构造器注入
    另外,为了更加灵活地实现 Bean 实例的依赖注入,Spring 还提供了@Resource 和@Autowired 这两个注解。
    分别是根据 bean 的 id 和 bean 的类型来实现依赖注入。
    image.png
    这个问题一般考察 1~3 年左右的程序员。
    基础的考察本身就是为了确保求职者对常用技术的理解程度。

2.1 @Resource 和 @Autowired 的区别

好的,面试官。
@Resource 和@Autowired 这两个注解的作用都是在 Spring 生态里面去实现 Bean 的依赖注入。
下面我分别说一下@Autowired 和@Resource 这两个注解。
闪现 [@Autowired 的作用详解 ] 几个字。
image.png
(如图)首先,@Autowired 是 Spring 里面提供的一个注解,默认是根据类型来实现 Bean 的依赖注入。
@Autowired 注解里面有一个 required 属性默认值是 true,表示强制要求 bean 实例的注入,
在应用启动的时候,如果 IOC 容器里面不存在对应类型的 Bean,就会报错。
当然,如果不希望自动注入,可以把这个属性设置成 false。
image.png
(如图)所以 Spring 启动的时候,会提示一个错误,大概意思原本只能注入一个单实例 Bean, 但是在 IOC 容器里面却发现有多个,导致注入失败。
image.png

在 Spring Boot 项目中,@Autowired 和 @Resource 都是用于自动装配依赖关系的注解,但它们之间存在一些关键差异:

  1. 装配策略
    • @Autowired 默认按照类型进行装配。如果依赖对象不存在,默认情况下会抛出异常,除非将其 required 属性设置为 false。如果希望根据名称来装配,可以结合 @Qualifier 注解一起使用。
    • @Resource 默认按名称进行装配。如果找不到与名称匹配的 bean,它会尝试按照类型进行装配。可以通过 name 属性指定 bean 的名称,如果未指定 name 属性,当注解标注在字段上时,会默认取字段的名称作为 bean 名称进行装配;如果注解标注在 setter 方法上,则会默认取属性名作为 bean 名称进行装配。
  2. 属性使用
    • @Resource 有两个重要属性:name 和 type。如果同时指定了 name 和 type,Spring 会优先使用 name 属性进行装配;如果只有 type 属性,则会使用 type 属性进行装配;如果两者都未指定,Spring 会尝试使用反射机制按照名称进行装配。
    • @Autowired 没有类似 @Resource 的 name 属性,它只能通过类型进行装配,除非配合 @Qualifier 注解来指定特定的 bean。
  3. 使用场景
    • @Autowired 通常用于自动装配 Spring 管理的 bean,因为它简单直接,易于理解和使用。
    • @Resource 由于支持按名称和类型两种方式装配,因此在某些复杂的场景下可能更加灵活。此外,@Resource 是 J2EE 标准注解,与 Spring 框架的耦合度较低,有时被认为更符合 Java EE 规范。

综上所述,选择使用 @Autowired 还是 @Resource 取决于具体的应用场景和开发者的偏好。如果需要根据名称装配,或者想要减少与 Spring 框架的耦合,可以考虑使用 @Resource。如果只是简单地按类型装配,那么 @Autowired 可能是一个更好的选择。

2.2 Spring 中,有两个 id 相同的 bean,会报错吗,如果会报错,在哪个阶段报错.

好的,关于这个问题,我从几个点来回答。
首先,在同一个 XML 配置文件里面,不能存在 id 相同的两个 bean,否则 spring 容器启动的时候会报错(如图)。
image.png
因为 id 这个属性表示一个 Bean 的唯一标志符号,所以 Spring 在启动的时候会去验证 id 的唯一性, 一旦发现重复就会报错,
这个错误发生 Spring 对 XML 文件进行解析转化为 BeanDefinition 的阶段。
但是在两个不同的 Spring 配置文件里面,可以存在 id 相同的两个 bean。 IOC 容器在加载 Bean 的时候,默认会多个相同 id的 bean 进行覆盖。

在 Spring3.x 版本以后,这个问题发生了变化
我们知道 Spring3.x 里面提供@Configuration 注解去声明一个配置类,然后使用@Bean 注解实现 Bean 的声明,这种方式完全取代了 XMl。
在这种情况下,(如图)如果我们在同一个配置类里面声明多个相同名字的 bean,在 Spring IOC 容器中只会注册第一个声明的 Bean 的实例。
后续重复名字的 Bean 就不会再注册了。
像这样一段代码,在 Spring IOC 容器里面,只会保存 UserService01 这个实例,后续相同名字的实例不会再加载。
image.png
(如图)如果使用@Autowired 注解根据类型实现依赖注入,因为 IOC 容器只有 UserService01 的实例,所以启动的时候会提示找不到 UserService02 这个实例。
image.png
如果使用@Resource 注解根据名词实现依赖注入,在 IOC 容器里面得到的实例对象是 UserService01, 于是 Spring 把 UserService01 这个实例赋值给 UserService02,就会提示类型不匹配错误。
image.png
这个错误,是在 Spring IOC 容器里面的 Bean 初始化之后的依赖注入阶段发生的。
以上就是我对这个问题的理解。
image.png

目录
相关文章
|
8天前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
34 0
|
7天前
|
消息中间件 Java 开发者
Spring Cloud微服务框架:构建高可用、分布式系统的现代架构
Spring Cloud是一个开源的微服务框架,旨在帮助开发者快速构建在分布式系统环境中运行的服务。它提供了一系列工具,用于在分布式系统中配置、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等领域的支持。
38 5
|
6天前
|
监控 Java 开发者
Spring Boot框架在java领域的优势
随着云计算、微服务架构的兴起,Java开发领域迫切需要一套高效、灵活且易于上手的框架来应对日益复杂的业务需求。正是在这样的背景下,Spring Boot应运而生,以其独特的魅力迅速成为了Java开发者手中的利器。
16 3
|
7天前
|
安全 前端开发 Java
Java技术栈中的核心组件:Spring框架
Java作为一门成熟的编程语言,其生态系统拥有众多强大的组件和框架,其中Spring框架无疑是Java技术栈中最闪耀的明星之一。Spring框架为Java开发者提供了一套全面的编程和配置模型,极大地简化了企业级应用的开发流程。
17 1
|
8天前
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
21 1
|
6天前
|
XML Java 关系型数据库
面试一口气说出Spring的声明式事务@Transactional注解的6种失效场景
面试一口气说出Spring的声明式事务@Transactional注解的6种失效场景
|
8天前
|
SQL Java 数据库连接
Java面试题:简述ORM框架(如Hibernate、MyBatis)的工作原理及其优缺点。
Java面试题:简述ORM框架(如Hibernate、MyBatis)的工作原理及其优缺点。
10 0
|
8天前
|
存储 安全 Java
Java面试题:请解释Java中的泛型集合框架?以及泛型的经典应用案例
Java面试题:请解释Java中的泛型集合框架?以及泛型的经典应用案例
15 0
|
8天前
|
设计模式 存储 缓存
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
14 0
|
8天前
|
设计模式 安全 NoSQL
Java面试题:结合单例模式与Java内存管理,设计一个线程安全的单例类?分析Java多线程工具类ExecutorService与Java并发工具包中的工具类,设计一个Java并发框架的分布式锁实现
Java面试题:结合单例模式与Java内存管理,设计一个线程安全的单例类?分析Java多线程工具类ExecutorService与Java并发工具包中的工具类,设计一个Java并发框架的分布式锁实现
16 0