如何在CDI中注入运行时对象?HttpSender注入场景求解
我有如下代码:
@WebServlet("/produces") public class TestProducesServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) ... { HttpSender sender = new HttpSender(response); ... }
我希望将new HttpSender替换为@Inject注入方式,因此将HttpSender重构为带有@Inject HttpSender( HttpServletResponse response)构造方法的类。但我不清楚如何将运行时的HttpServletResponse response传入该构造方法,曾考虑使用@Produces但未能实现。
于是我采用了临时解决方案:给HttpSender添加默认构造方法和HttpServletResponse的setter方法,修改后的Servlet代码如下:
@WebServlet("/produces") public class TestProducesServlet extends HttpServlet { @Inject private HttpSender sender; protected void doPost(HttpServletRequest request, HttpServletResponse response) ... { sender.setHttpServletResponse(response); ... }
但我不确定@Inject是否在setter调用前执行,且认为存在更优的解决方案。
要实现运行时注入HttpServletResponse到HttpSender,可以通过以下步骤完成:
1. 定义HttpServletResponse的生产者方法
创建一个CDI生产者类,专门提供当前请求的HttpServletResponse实例。如果直接获取容器内的response有兼容性问题,推荐用过滤器提前把response存入请求属性:
第一步:添加请求过滤器
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebFilter("/*") public class ResponseStoringFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setAttribute("CURRENT_RESPONSE", response); chain.doFilter(request, response); } }
第二步:编写生产者类
import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Produces; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletRequest; import javax.inject.Inject; public class ServletResponseProducer { @Inject private HttpServletRequest request; @Produces @RequestScoped public HttpServletResponse produceHttpServletResponse() { return (HttpServletResponse) request.getAttribute("CURRENT_RESPONSE"); } }
2. 保持HttpSender的构造注入
不需要默认构造方法和setter,直接保留带@Inject的构造方法:
import javax.inject.Inject; public class HttpSender { private final HttpServletResponse response; @Inject public HttpSender(HttpServletResponse response) { this.response = response; } // 你的业务方法,比如send() }
3. 在Servlet中直接注入HttpSender
Servlet中直接注入HttpSender,无需手动设置response:
import javax.inject.Inject; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/produces") public class TestProducesServlet extends HttpServlet { @Inject private HttpSender sender; protected void doPost(HttpServletRequest request, HttpServletResponse response) { // 直接使用sender,它已经绑定了当前请求的response // sender.send(...); } }
关于临时方案的说明
你的临时方案存在线程安全隐患:Servlet默认是单例,@Inject只会在Servlet实例化时执行一次,而doPost是多请求并发调用的,多次调用setHttpServletResponse会覆盖之前的response实例,导致请求串数据。
而上述构造注入+生产者的方式是线程安全的:HttpSender默认是@Dependent作用域,每次注入都会生成新实例并绑定当前请求的response;如果需要HttpSender在整个请求周期内复用,可以给它加上@RequestScoped注解。
内容的提问来源于stack exchange,提问作者ericj

