面试复盘:单例 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。
    • 但 RequestBean 是 request 作用域,启动时没有 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. 为什么会报错

    • singleton 和 request 的生命周期不匹配。单例 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. 使用 Provider 或 ObjectFactory

    • 通过 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 作用域的差异会直接影响依赖注入的行为。如果直接用 @Autowired 把 request 作用域的 Bean 注入到单例 Bean,程序启动会报错,因为生命周期不兼容。

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

总结

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

  1. 代理模式 :加 proxyMode,最常用。
  2. 延迟加载 :用 Provider 或 ObjectFactory。
  3. 手动获取 :通过 ApplicationContext。
相关推荐
IT_陈寒31 分钟前
SpringBoot自动配置的坑,这次终于爬出来了
前端·人工智能·后端
RainbowFish40 分钟前
# RAG 与智能体记忆:从原理到工程实践 > 一篇给学习者的总结。
后端
苏三说技术1 小时前
为什么越来越多人用Arthas 4 ?
后端
ServBay1 小时前
AI 开发平台出事后,API Key 泄露要怎么办
后端·aigc·ai编程
程序员老申1 小时前
解剖 PDF 底层二进制:一行代码如何抓出假薪资流水的 3 处破绽
后端
JuiceFS2 小时前
从 Meta 的工程实践看 EB 级 AI 存储架构
人工智能·后端
数据库小学妹2 小时前
MySQL NULL陷阱:NOT IN、三值逻辑与NOT EXISTS
数据库·后端·mysql
AI入职记2 小时前
10 万个会话吃掉 1.1G 内存:Spring AI 会话记忆的两个隐藏成本
后端
小兔子2 小时前
getenv 返回的是别人的内存,改一下会改变整个进程的环境
后端
程序员cxuan2 小时前
通了个宵,终于把 Super-Agent 架构玩明白了
人工智能·后端·程序员