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 中工作。这让我想知道我在运行时注册 servlet 是否容器不认为这些 servlet 是托管的。

如果是这种情况,将 EJB 注入我的 servlet 而不改变它们通过过滤器动态注册的方式的正确方法是什么?

通过 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() );
        }
    }

...

不幸的是,它不会使容器注入 EJB,并且空指针仍然存在。我目前正在对服务进行自定义类型安全的 JNDI 查找。显然这比使用正确的注入要昂贵得多(如果我错了,请纠正我,还没有做过关于性能的实验)。

使用:
Java EE 6
JBoss AS 7.1

4

3 回答 3

3

Servlet 3.0 规范,第。4.4.3.5

所有以编程方式添加或以编程方式创建的组件(Servlet、过滤器和侦听器)上的资源注入[例如 @EJB],除了通过采用实例的方法添加的组件外,仅当组件是托管 Bean 时才受支持。有关什么是托管 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 doc 在这里非常模糊。

使用 CDI 尝试:

 servletContext.addServlet("your servlet name", @Inject YourServletClass servlet)
于 2013-05-08T01:25:39.267 回答
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 (ofc) 和 JBoss 7.1.1 和 7.2.0 (EAP 6.1.0 Alpha) 测试。

编辑

动态映射 servlet 的问题实际上在基本的 JBoss 架构中很深。他们使用 JBossWeb(Tomcat 的一个分支版本)作为 servlet 实现,并且在其上下文管理代码的内部,它决定是通过注入还是常规new实例化新组件。迄今为止,Afaik 需要以某种方式对您的 servlet 进行注释,以便通过注入对其进行处理:我在原始答案中提到了 @ManagedBean,但看起来使用 @WebServlet 进行注释也可以。

于 2013-05-08T14:35:57.347 回答
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 注入处理根本不起作用。根据规范,这应该像宣传的那样工作。

于 2013-05-06T05:02:43.300 回答