面试复盘:单例 Bean 和 Request Bean 注解注入,程序启动会报错吗?


面试复盘:单例 Bean 和 Request Bean 注解注入,程序启动会报错吗?

最近在复习 Spring Boot 时,突发奇想一个问题:如果有两个 Bean,一个是 singleton 作用域,另一个是 request 作用域,通过注解(比如 @Autowired)注入,程序启动时会报错吗?这个问题虽然没在面试中遇到,但感觉是个不错的考察点,既涉及 Bean 的作用域,又考验对 Spring 容器和依赖注入的理解。下面我就模拟一次"自我面试",复盘分析这个场景。

问题背景

在 Spring Boot 中,Bean 的作用域决定了它的生命周期和实例化方式:

  • singleton:全局唯一,容器启动时创建,适用于无状态对象。
  • request:每个 HTTP 请求一个实例,仅在 Web 环境下有效,请求结束后销毁。

假设有如下代码:

java 复制代码
@Component
@Scope("singleton")
public class SingletonBean {
    @Autowired
    private RequestBean requestBean;

    public void print() {
        System.out.println("RequestBean: " + requestBean);
    }
}

@Component
@Scope("request")
public class RequestBean {
    public String getMessage() {
        return "I am a request-scoped bean";
    }
}

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

问题来了:SingletonBean 是单例,生命周期跟容器绑定,而 RequestBean 是请求级别,只有在 HTTP 请求时才创建。程序启动时,Spring 容器会初始化所有单例 Bean,那这种依赖注入会报错吗?

分析过程

  1. Spring 容器启动流程

    • 程序启动时,Spring 容器会加载所有 singleton 作用域的 Bean。
    • SingletonBean 是单例,Spring 会尝试实例化它,并通过 @Autowired 注入依赖 RequestBean
    • RequestBeanrequest 作用域,启动时没有 HTTP 请求上下文,Spring 无法直接创建它的实例。
  2. 默认行为

    • Spring 的依赖注入是"急切"(eager)的,容器启动时会解析所有单例 Bean 的依赖。

    • 由于 RequestBean 在非 Web 请求环境下不可用,Spring 会抛出异常。

    • 可能的错误信息类似:

      dart 复制代码
      org.springframework.beans.factory.BeanCreationException: 
      Error creating bean with name 'singletonBean': 
      Injection of autowired dependencies failed; 
      nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: 
      No qualifying bean of type 'RequestBean' available
  3. 为什么会报错

    • singletonrequest 的生命周期不匹配。单例 Bean 在容器初始化时就固定,而 request Bean 需要动态创建。
    • Spring 容器启动时没有 Web 请求上下文,无法提供 RequestBean 的实例。

验证实验

我在本地写了个 demo 测试了一下,果然启动报错了。日志显示 Spring 找不到 RequestBean 的定义,因为它期待一个现成的 Bean,而 request 作用域的 Bean 在启动时根本不存在。这让我想到,Spring 肯定有办法处理这种场景,不然 Web 开发中这种需求太常见了。

解决方案

复盘时查了资料,发现 Spring 提供了几种方法来解决这种依赖注入问题:

  1. 使用代理(@Autowired + 代理模式)

    • Spring 可以为 request 作用域的 Bean 创建一个代理对象(CGLIB 动态代理)。

    • 修改代码,在 RequestBean 上加 @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)

      java 复制代码
      @Component
      @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
      public class RequestBean {
          public String getMessage() {
              return "I am a request-scoped bean";
          }
      }
    • 原理 :代理对象在容器启动时注入到 SingletonBean 中,实际调用时才动态解析到真实的 RequestBean 实例。

    • 结果 :程序启动不会报错,但在非 Web 环境下调用 requestBean 会抛出 IllegalStateException,提示缺少请求上下文。

  2. 使用 ProviderObjectFactory

    • 通过 javax.inject.Provider 或 Spring 的 ObjectFactory 延迟获取 RequestBean

      java 复制代码
      @Component
      @Scope("singleton")
      public class SingletonBean {
          @Autowired
          private Provider<RequestBean> requestBeanProvider;
      
          public void print() {
              RequestBean requestBean = requestBeanProvider.get();
              System.out.println("RequestBean: " + requestBean);
          }
      }
    • 原理Provider 是一个工厂接口,每次调用 get() 时才会创建 RequestBean,避免启动时依赖解析。

    • 结果:启动正常,只有在实际调用时才会检查请求上下文。

  3. 避免直接注入

    • 如果业务允许,可以通过 ApplicationContext 手动获取 RequestBean

      java 复制代码
      @Component
      @Scope("singleton")
      public class SingletonBean {
          @Autowired
          private ApplicationContext context;
      
          public void print() {
              RequestBean requestBean = context.getBean(RequestBean.class);
              System.out.println("RequestBean: " + requestBean);
          }
      }
    • 原理:运行时动态获取 Bean,绕过了启动时的依赖注入。

    • 结果:启动没问题,但需要在请求时调用。

复盘感想

这个问题让我意识到,Bean 作用域的差异会直接影响依赖注入的行为。如果直接用 @Autowiredrequest 作用域的 Bean 注入到单例 Bean,程序启动会报错,因为生命周期不兼容。

  • 面试应对 :如果被问到,我可以先说"默认会报错,因为容器启动时没有请求上下文",然后补充"可以用代理模式或 Provider 解决"。
  • 加分点 :提到代理模式的实现(CGLIB)和异常类型(BeanCreationException),显得更专业。

总结

单例 Bean 和 request Bean 的注解注入,程序启动会报错,除非采取特殊处理:

  1. 代理模式 :加 proxyMode,最常用。
  2. 延迟加载 :用 ProviderObjectFactory
  3. 手动获取 :通过 ApplicationContext
相关推荐
SimonKing1 分钟前
京东外卖,探索「距离最近」排序背后的秘密
redis·后端·mysql
AronTing3 分钟前
命令模式:从撤销操作到分布式调度的命令封装实践
java·后端·架构
okok__TXF7 分钟前
Spring分析-IOC
java·后端·spring
前端拧螺丝16 分钟前
使用Python实现钉钉Stream模式服务开发及内部程序通信
后端
bella18 分钟前
多线程
后端
倚栏听风雨22 分钟前
IDEA插件 ProjectComponent
后端
用户3383431092823 分钟前
谷歌云代理商:谷歌云服务器如何支持情感分析?
后端
剽悍一小兔25 分钟前
死锁可以彻底避免吗??
后端
星辰大海的精灵25 分钟前
微信客服小助手 python接入
后端·python
fliter26 分钟前
性能比拼: Rust vs Zig vs Go
后端