面试复盘:单例 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
相关推荐
uzong17 分钟前
技术故障复盘模版
后端
GetcharZp1 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程1 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研1 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi2 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国3 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy3 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack3 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9654 小时前
pip install 已经不再安全
后端
寻月隐君4 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github