Servlets and scopes of CDI beans
tl;dr How is it possible that the CDI bean injected to servlet is also in proper scope?
In official oracle tutorial and in some books we can see simple examples that shows how to inject CDI bean to servlet. It is pretty simple since we just have to use @Inject annotation and enable bean discovery in beans.xml. The thing that I don't understand is how is it possible, that the @RequestScoped or @SessionScoped bean injected to the servlet has the right scope. The servlet object is created by the container only once, so as far as I understand the injection should occur also only once or some unexpected behavior should happen. But when we use ie. @RequestScoped on bean class the injection occurs after every request to this servlet(what is great). The question is how it works in depth?
Simple example
public interface BeanInterface {
public void beanInfo();
}
-
@RequestScoped
public class BeanImpl implements BeanInterface {
@Override
public void beanInfo() {
System.out.println(this);
}
}
-
@WebServlet("/bean")
public class BeanServlet extends HttpServlet {
//how is it injected with every GET/POST/... request
@Inject
private BeanInterface bean;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(this);
bean.beanInfo();
}
}
Result after sending 3 requests to /bean URL we can see that every time we get different bean injected to single servlet.
23:35:18,062 INFO [stdout] (default task-3) com.test.BeanServlet@1f2521b7
23:35:18,071 INFO [stdout] (default task-3) com.test.BeanImpl@4a49ab25
23:35:23,883 INFO [stdout] (default task-4) com.test.BeanServlet@1f2521b7
23:35:23,887 INFO [stdout] (default task-4) com.test.BeanImpl@6ff1609e
23:35:27,286 INFO [stdout] (default task-5) com.test.BeanServlet@1f2521b7
23:35:27,288 INFO [stdout] (default task-5) com.test.BeanImpl@1edc9ec
Injected beans retain their scope because what is really injected into the servlet is a proxy instead of the real bean.
Proxies are created dynamically by the CDI container, and are derived from injected class or interface. For classes, a dynamic subclass is created, for interfaces, dynamic proxy is created.
The proxy is responsible for resolving current context and decide if to create a new bean or reuse existing bean. Then, all methods called on injected proxy are forwarded to underlying created or reused bean.
You may even pass injected proxy to another bean or a plain object, and the context will be resolved properly and correct bean's methods will be called. This works because there is always at most one request context for a thread, and at any point, the proxy has access to its thread and can find out which request context is assigned to the thread.
The proxy is responsible also for initializing beans, therefore you may observer, that @PostConstruct
method is delayed and executed only when necessary - when a method on the proxy is called. In other words, when CDI bean is injected, its post-construct method is not executed immediately. You need to execute some method on the bean in order to trigger post-cnstruct method.
What is injected is not an actual instance of the request-scoped bean. It's in fact a dynamically-generated proxy. When calling a method ( foo()
for example) on that proxy, the proxy looks for an actual bean instance in the request or session scope, calls its foo()
method and returns the result to the servlet.
上一篇: @SessionScoped(CDI)和@Stateful(Java EE)之间的区别
下一篇: Servlet和CDI bean的范围