CDI bean 的 Servlet 和范围
Servlets and scopes of CDI beans
tl;dr 注入到 servlet 的 CDI bean 怎么可能也在正确的范围内?
在官方的oracle教程和一些书籍中我们可以看到简单的例子,展示了如何将CDI bean注入到servlet中。这非常简单,因为我们只需要使用 @Inject 注释并在 beans.xml 中启用 bean 发现。我不明白的是,注入到 servlet 的 @RequestScoped 或 @SessionScoped bean 怎么可能具有正确的范围。
servlet 对象仅由容器创建一次,因此据我所知,注入也应该只发生一次,否则应该发生一些意外行为。但是当我们使用 ie. @RequestScoped on bean class 注入发生在对该 servlet 的每个请求之后(很棒)。问题是它是如何深入运作的?
简单示例
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();
}
}
向 /bean 发送 3 个请求后的结果 URL 我们可以看到,每次我们将不同的 bean 注入到单个 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
注入的不是请求作用域 bean 的实际实例。它实际上是一个动态生成的代理。在该代理上调用方法(例如 foo()
)时,代理在请求或会话范围内查找实际的 bean 实例,调用其 foo()
方法并将结果 returns小服务程序。
注入的 bean 保留其作用域,因为真正注入 servlet 的是代理而不是真正的 bean。
代理由 CDI 容器动态创建,并派生自注入的 class 或接口。对于 classes,创建动态子class,对于接口,创建动态代理。
代理负责解析当前上下文并决定是创建新 bean 还是重用现有 bean。然后,所有在注入代理上调用的方法都被转发到底层创建或重用的 bean。
您甚至可以将注入的代理传递给另一个 bean 或一个普通对象,上下文将被正确解析并调用正确的 bean 方法。这是有效的,因为一个线程总是最多有一个请求上下文,并且在任何时候,代理都可以访问它的线程并且可以找出哪个请求上下文被分配给该线程。
代理还负责初始化 bean,因此您可能观察到,@PostConstruct
方法仅在必要时延迟并执行 - 当调用代理上的方法时。换句话说,当注入 CDI bean 时,其 post-construct 方法不会立即执行。您需要在 bean 上执行一些方法才能触发 post-cnstruct 方法。
tl;dr 注入到 servlet 的 CDI bean 怎么可能也在正确的范围内?
在官方的oracle教程和一些书籍中我们可以看到简单的例子,展示了如何将CDI bean注入到servlet中。这非常简单,因为我们只需要使用 @Inject 注释并在 beans.xml 中启用 bean 发现。我不明白的是,注入到 servlet 的 @RequestScoped 或 @SessionScoped bean 怎么可能具有正确的范围。 servlet 对象仅由容器创建一次,因此据我所知,注入也应该只发生一次,否则应该发生一些意外行为。但是当我们使用 ie. @RequestScoped on bean class 注入发生在对该 servlet 的每个请求之后(很棒)。问题是它是如何深入运作的?
简单示例
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();
}
}
向 /bean 发送 3 个请求后的结果 URL 我们可以看到,每次我们将不同的 bean 注入到单个 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
注入的不是请求作用域 bean 的实际实例。它实际上是一个动态生成的代理。在该代理上调用方法(例如 foo()
)时,代理在请求或会话范围内查找实际的 bean 实例,调用其 foo()
方法并将结果 returns小服务程序。
注入的 bean 保留其作用域,因为真正注入 servlet 的是代理而不是真正的 bean。
代理由 CDI 容器动态创建,并派生自注入的 class 或接口。对于 classes,创建动态子class,对于接口,创建动态代理。
代理负责解析当前上下文并决定是创建新 bean 还是重用现有 bean。然后,所有在注入代理上调用的方法都被转发到底层创建或重用的 bean。
您甚至可以将注入的代理传递给另一个 bean 或一个普通对象,上下文将被正确解析并调用正确的 bean 方法。这是有效的,因为一个线程总是最多有一个请求上下文,并且在任何时候,代理都可以访问它的线程并且可以找出哪个请求上下文被分配给该线程。
代理还负责初始化 bean,因此您可能观察到,@PostConstruct
方法仅在必要时延迟并执行 - 当调用代理上的方法时。换句话说,当注入 CDI bean 时,其 post-construct 方法不会立即执行。您需要在 bean 上执行一些方法才能触发 post-cnstruct 方法。