将一个EJB注入到动态映射的servlet中

8

我有一个过滤器,其中我正在动态映射servlet类:

    @Override
    public void init( FilterConfig filterConfig ) throws ServletException {
        servletContext = filterConfig.getServletContext();

        File directory = getConventionDirectory();
        FileSystemInspector fileInspector = new FileSystemInspector();
        Set<ActionInfoData> actions = fileInspector.getActions( directory );

        for ( ActionInfoData action : actions ) {
            servletContext
                .addServlet( action.getServletName(), action.getClassName() )
                .addMapping( action.getServletMapping() );
        }

    }

当我访问一个特定的映射时,EJB没有被注入。

    @EJB
    private I18nManager i18nManager;

    @Override
    protected void doGet( HttpServletRequest request, HttpServletResponse response )
    throws ServletException, IOException {
        I18nManager i18n = i18nManager; //null
    }

如果我在web.xml中手动创建一个映射,那么 EJB 在该servlet中是可以工作的。这让我想知道,如果我在运行时注册 servlets,容器是否不将这些 servlets 视为已托管。
如果是这种情况,注入 EJB 的适当方法是什么,而又不改变它们通过过滤器动态注册的方式?
是通过 JNDI 注入 EJB 的唯一方法吗?
编辑1: 我已经尝试实现了一个ServletContextListener类,如“Will”所建议的,在web.xml中使用以下代码:
<listener>
        <listener-class>com.megafone.web.filter.convention.InitServlet</listener-class>
    </listener>

以下是相关的实现部分:

...

@Override
    public void contextInitialized( ServletContextEvent sce ) {
        ServletContext servletContext = sce.getServletContext();

        FileSystemInspector fileInspector = new FileSystemInspector();
        Set<ActionInfoData> actions = fileInspector.getActions( getConventionDirectory() );

        for ( ActionInfoData action : actions ) {
            servletContext
                .addServlet( action.getServletName(), action.getClassName() )
                .addMapping( action.getServletMapping() );
        }
    }

...

很遗憾,它不能使容器注入EJBs,空指针仍然存在。我正在制作一种自定义类型安全的JNDI查找服务。显然,这比使用正确的注入方式更加昂贵(如果我错了,请纠正我,我还没有进行性能方面的实验)。

使用:
Java EE 6
JBoss AS 7.1

3个回答

3

Servlet 3.0规范,第4.4.3.5节

所有通过编程添加或创建的组件(Servlet、过滤器和监听器),除了通过带有实例的方法添加的组件外,仅在组件为托管Bean时支持资源注入[e.g. @EJB]。有关托管Bean的详细信息,请参阅作为Java EE 6和JSR 299一部分定义的托管Bean规范。

托管Bean声明

Java EE 6托管Bean使用@javax.annotation.ManagedBean进行注释,并具有无参数构造函数。JSR 299 (CDI)托管Bean仅需要一个无参数构造函数或一个使用@javax.inject.Inject进行注释的构造函数。

答案

要启用资源注入,您需要:

  • 在动态添加的Servlet上放置@ManagedBean注解

    或者

  • 启用CDI并包含一个空的beans.xml


编辑

即使您正在动态创建Servlet,容器执行创建也很重要。不要认为在ServletContext内部创建将支持注入。Servlet文档在这里非常模糊。

使用CDI尝试:

 servletContext.addServlet("your servlet name", @Inject YourServletClass servlet)

好的,我尝试在扩展HttpServlet的类中使用@javax.annotation.ManagedBean注释(正在动态映射的类)。我的bean没有被注入。我尝试通过在META-INF目录中放置一个空的beans.xml文件来启用CDI。但是当使用@Inject时仍然没有被注入。有什么想法吗?虽然我已经得到了一些信息,但我将进行几个测试。 - Fagner Brack
1
当ServletContext创建Servlet时,可能会出现这种情况。强制容器创建Servlet-对于CDI,请调用servletContext.addServlet("your servlet name", @Inject YourServletClass servlet)。 - Glen Best
只是为了澄清@GlenBest,“定义为Java EE 6和JSR 299的一部分”。JSR 299定义了托管Bean,Java EE 6定义了支持注入的其他类,包括Servlet(规范的第69页)。没有必要采取额外的步骤使Servlet成为ManagedBean。 - Will Hartung
Java EE 6(JSR 318)定义了“基本”托管Bean(http://jcp.org/aboutJava/communityprocess/final/jsr316/index.html)。JSR 299定义了扩展的托管Bean。需要进一步采取额外措施- Servlet规范明确表示除托管Bean外,不支持注入。 - Glen Best
确实缺失的部分就是使用已经注入的Servlet来创建动态映射。感谢Glen。 - cmonsqpr

3
问题似乎与尚未解决的报告的错误有关。资源解析对于JSF规范定义的托管bean正常工作,但对于CDI托管bean则不起作用。只需在动态servlet类上注释@javax.faces.bean.ManagedBean即可解决问题(是的,这是一个相当丑陋的解决方法)。
@ManagedBean
public class DynServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @EJB
    private LoginService loginService;

    public DynServlet() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        response.getOutputStream().println(
                "Request made to: " + getClass().getSimpleName());
        response.getOutputStream().println("Login Service: " + loginService);

        return;
    }
}

@WebListener
public class DynamicServletLoadListener implements ServletContextListener {

    public DynamicServletLoadListener() {
        super();
    }

    @Override
    public void contextDestroyed(final ServletContextEvent contextEvent) {
        return;
    }

    @Override
    public void contextInitialized(final ServletContextEvent contextEvent) {
        contextEvent.getServletContext().addServlet("dynservlet", DynServlet.class)
                .addMapping("/services/dynservlet");
    }
}

已测试过JEE6(当然),并且在JBoss 7.1.1和7.2.0(EAP 6.1.0 Alpha)上均可使用。

编辑:

动态映射的servlet存在的问题实际上深入到JBoss架构的基础中。它们使用JBossWeb(Tomcat的分支版本)作为servlet实现,在其上下文管理代码的核心部分,它会确定是通过注入还是常规的new来实例化新组件。据我所知,截至目前,您的servlet需要以某种方式进行注释,以便通过注入处理:我在原始答案中提到了@ManagedBean,但似乎用@WebServlet进行注释也可以。


有没有不需要为每个servlet添加注释的解决方案呢?=( - Fagner Brack
@FagnerBrack - 迄今为止我还没有发现。问题是你没有访问servlet源代码吗?还是它们只是太多了? - Perception
实际上,我正在创建一个映射约定,根据给定的 jsp 和 servlet 位置(文件系统)动态创建 Http URI 路径。Java EE 的主要用途是用于后端管理。在这个项目中,我不使用 JSF,因为它的限制性质,而是利用 HTML5 Boilerplate、jQuery、requirejs、bootstrap(自定义)以及所有前端酷炫功能,JSF 中没有内置。使用注释的主要问题是,在将来每个类都需要应用它,而不能将这样的任务委托给自动过程。只是方便之举。 - Fagner Brack

0

首先,在我的测试中,使用Glassfish V3的一个版本运行良好。

但是,其次,您可能会违反Servlet 3.0规范中的以下条款。

自Servlet 3.0以来,以下方法已添加到ServletContext中,以便以编程方式定义servlet、过滤器和它们映射到的url模式。这些方法只能在应用程序初始化期间从ServletContextListener实现的contexInitialized方法或ServletContainerInitializer实现的onStartup方法调用。

值得注意的是,这些方法不能从Filter.init()方法中调用。我最初尝试在Servlet.init()方法中进行此操作,但init方法因上下文已经初始化而失败。

因此,我的实验并没有完全复制您的测试——我没有在Filter.init()方法中使用这个,而是将代码放在了ServletContextListener中。当我这样做时,我的@EJB注释被采纳了。

编辑:

虽然听起来不太有帮助,但我建议这是JBoss中的一个错误。当我最初尝试使用来自过滤器的注入运行您的原始代码时,Glassfish抛出了异常,因为您不允许在我之前提到的地方进行注入保存。现在也许这是JBoss的“附加功能”,但显然@EJB注入处理根本不起作用。根据规范,这应该按照广告运作。


谢谢回复。不幸的是,在通过ServletContextListener加载时它没有起作用。我已经编辑了上面的问题,您可以告诉我是否做错了什么。 - Fagner Brack

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接