You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在@Stateless中注入@RequestScoped的JavaEE依赖注入疑问

Stateless Bean中注入RequestScoped依赖的原理疑问

我在@Stateless无状态对象中注入客户端依赖对象成功实现,但对JavaEE依赖注入的运行原理存在疑问,涉及的类包括:负责数据库取数的BookmarkRepository、存储用户请求信息的RequestData(@RequestScoped)、REST端点BookmarkEndpoint。

代码示例

RequestData.java

import java.util.UUID;
import javax.enterprise.context.RequestScoped;

@RequestScoped
public class RequestData {

    private final UUID correlationId;

    public RequestData() {
        correlationId = UUID.randomUUID();
    }

    public UUID getCorrelationId() {
        return correlationId;
    }
}

BookmarkRepository.java

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class BookmarkRepository {

    @PersistenceContext
    private EntityManager em;

    @Inject
    RequestData requestData;

    public String test() {
        StringBuilder sb = new StringBuilder();
        String objectid = Integer.toString(System.identityHashCode(this));
        String correlationId = requestData.getCorrelationId().toString();

        sb.append(correlationId);
        sb.append("    OBJECT ID: " + objectid);
        return sb.toString();
    }
}

BookmarkEndpoint.java

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("bookmarks")
public class BookmarkEndpoint {

    @Inject
    private BookmarkRepository bookmarkRepository;

    @Inject
    private RequestData requestData;

    @GET
    @Path("/test")
    @Produces(MediaType.TEXT_PLAIN)
    public Response testEndpoint() {
        StringBuilder sb = new StringBuilder();
        String outerCorelationId = requestData.getCorrelationId().toString();
        sb.append(outerCorelationId);

        String innerCorelationId1 = bookmarkRepository.test();
        sb.append("\n" + innerCorelationId1);

        String innerCorelationId2 = bookmarkRepository.test();
        sb.append("\n" + innerCorelationId2);

        return Response.ok(sb.toString()).build();
    }
}

两次HTTP调用输出结果

FIRST HTTP CALL
ba4ac8b9-8e3b-4632-9c5e-89fe9b6f2cc5
ba4ac8b9-8e3b-4632-9c5e-89fe9b6f2cc5    OBJECT ID: 717717270
ba4ac8b9-8e3b-4632-9c5e-89fe9b6f2cc5    OBJECT ID: 717717270

SECOND HTTP CALL
9f563047-3d8f-48f5-849d-9a0bf1df46ae
9f563047-3d8f-48f5-849d-9a0bf1df46ae    OBJECT ID: 717717270
9f563047-3d8f-48f5-849d-9a0bf1df46ae    OBJECT ID: 717717270

技术疑问解答

1. @Inject是否会在BookmarkEndpoint方法每次调用时触发?

@Inject本身只会在Bean实例创建时执行一次注入动作,不会在方法每次调用时重复触发。你看到“每次请求都有新的RequestData”,是因为RequestData是@RequestScoped,注入到Bean中的其实是一个代理对象,而非实际的RequestData实例。当你调用requestData.getCorrelationId()时,代理会自动关联到当前HTTP请求对应的RequestData实例——每个请求对应一个独立的RequestData,所以每次请求拿到的CorrelationId都不同。

2. 两个HTTP请求是否可能同时获取同一个@Stateless对象,导致客户端RequestData被覆盖?

不会出现这种情况。Stateless Bean注入的RequestScoped依赖是代理对象,容器会保证每个请求的上下文是隔离的。当多个请求复用同一个Stateless Bean实例时,调用requestData的方法时,代理会根据当前线程的请求上下文,找到属于该请求的RequestData实例,不同请求的状态完全隔离,不会互相覆盖。

3. @Stateless对象的创建与销毁时机具体是什么?

Stateless Bean由容器的对象池管理:

  • 容器会预先创建一批Stateless Bean实例放在池中,当有请求过来时,从池中取出可用实例分配给请求;
  • 请求处理完成后,实例会被放回池中复用,不会立即销毁;
  • 只有当容器检测到池中空闲实例过多(比如长时间无请求)或资源紧张时,才会销毁多余的实例。
    你的输出中同一个对象ID重复出现,就是因为该实例被放回池后,后续请求又复用了它。

4. 因BookmarkRepository持有客户端独有的状态,它是否应标记为@Stateless?

可以继续使用@Stateless。因为BookmarkRepository并没有真正持有客户端特有的状态——requestData是代理对象,实际的状态(CorrelationId)属于@RequestScoped的RequestData实例,而非Stateless Bean本身。Stateless Bean的核心要求是自身不存储客户端专属状态,这里完全符合这个要求。如果改成@RequestScoped也能运行,但@Stateless可以利用对象池复用的优势,性能更优。


内容的提问来源于stack exchange,提问作者Pero122

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 16:37:44