javajakarta-eecdi

Inject @RequestScoped inside @Stateless


I am trying to inject a client depended object inside a statless object. I did manage to do it, but I am not really sure how and why it works because I don't fully understand JavaEE dependency injection . I have 3 relevant classes


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;
    }
}


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;

    @Override
    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();
    }
}
import javax.inject.Inject;
import javax.ws.rs.GET;
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() {
        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 ResponseBuilder.createOkResponse(Response.Status.OK, finalMessage);
    }
}

Output when I call the program with 2 HTTP calls

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

My questions:


Solution

  • On very important point your questions are missing:

    CDI Injects a proxy, which is like a router. It does not inject the actual object. If you put a breakpoint in getCorrelationId() and take a look at the stack trace, you'll see there's a stack frame where a CDI Proxy is called at some point.

    Why is this important?

    This allows for the creating programs where a RequestScoped bean can be injected with a SessionScoped bean, and vice-versa. The container routes you to the correct instance of the class when the function on the the proxy is called. It literally looks it up and sends the function call to the right place!

    So to get to your questions:

    Does @Inject trigger each time a method from BookmarkEndpoint is called?

    Neither. @Inject is just an annotation. BookmarkEndpoint is injected with a proxy of BookmarkRepository that is a sublcass of BookmarkRepository, and the CDI Framework defines this subclass at runtime. BookmarkRepository is a stateless EJB, so it will have a pool of instances. The proxy will pick an instance out of the pool and invoke the method on it. Since you don't have a CDI scope on BookmarkEndpoint, I believe it will be re-created every request (don't quote me on that, use the debugger). We generally slap @ApplicationScoped on all of our JAX-RS beans as an optimization.

    Is it possible that two HTTP calls get the same @Statless object during the same time and then the RequestData of one client will be squashed because of the other client RequestData

    No. Instead, Stateless beans are pooled. If there is a bean available in the pool, two requests will be handled at once. If there is no bean available, the request blocks until an instance becomes available. Pool sizes and timeouts are tunable and are a container specific setting.

    When is @Statless created and when it is destoyed?

    Short story: The pool of beans is created at boot time and destroyed at shutdown, unless you throw an exception out of the bean, then that instance is destroyed. See here for the full explanation: https://docs.oracle.com/javaee/6/tutorial/doc/giplj.html

    Should BookmarkRepository really be @Statless

    @Stateless will absolutely work, but it's not necessary: you don't really need a pool for anything. @Singleton @Lock(LockType.Read) would be more appropriate. What would be best is @ApplicationScoped @Transactional(TxType.Required) since that avoids EJBs altogether

    because I obviously have a state that is unique to each client

    You do... but not in the way you're thinking! Remember the following is a proxy:

        @Inject
        RequestData requestData;
    

    It's going to route you to an instance of the bean that is bound to the current request. Your "state" is being held by the container.