面试复盘:单例 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
相关推荐
追逐时光者1 小时前
分享一个纯净无广、原版操作系统、开发人员工具、服务器等资源免费下载的网站
后端·github
JavaPub-rodert2 小时前
golang 的 goroutine 和 channel
开发语言·后端·golang
ivygeek3 小时前
MCP:基于 Spring AI Mcp 实现 webmvc/webflux sse Mcp Server
spring boot·后端·mcp
GoGeekBaird4 小时前
69天探索操作系统-第54天:嵌入式操作系统内核设计 - 最小内核实现
后端·操作系统
鱼樱前端4 小时前
Java Jdbc相关知识点汇总
java·后端
canonical_entropy5 小时前
NopReport示例-动态Sheet和动态列
java·后端·excel
kkk哥5 小时前
基于springboot的母婴商城系统(018)
java·spring boot·后端
Asthenia04126 小时前
面试复盘:关于 Redis 如何实现分布式锁
后端
Asthenia04126 小时前
如何修改 MySQL 的数据库隔离级别:命令global、session/my.cnf中修改
后端