在@Stateless中注入@RequestScoped的JavaEE依赖注入疑问
我在@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

