【问题标题】:@Resource error: "Naming binding already exists for foo.NewServlet/userName in namespace"@Resource 错误:“命名空间中 foo.NewServlet/userName 的命名绑定已经存在”
【发布时间】:2018-01-24 08:45:41
【问题描述】:

我正在考虑使用 servlet 3.0+ 容器中可用的“@Resource String ...”注入来轻松地向 servlet 提供配置参数。如果 JNDI 中不存在密钥(表示配置错误),我希望默认值能够正常工作并失败

我在带有 Glassfish 4.1.1 的 Netbeans 8.2 中玩弄了一个简单的 servlet,我希望在其中设置 userName 字段和 setFullName(String fullName) 设置:

package foo;

import java.io.IOException;
import java.io.PrintWriter;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "NewServlet", urlPatterns = {"/NewServlet"})
public class NewServlet extends HttpServlet {

    @Resource(description="user name")
    String userName;

    private String fullname;

    @Resource()   
    public void setFullName(String fullName){
        this.fullname = fullName;
    }

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        try (PrintWriter out = response.getWriter()) {
            /* TODO output your page here. You may use following sample code. */
            out.println("<!DOCTYPE html>");
            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet NewServlet</title>");            
            out.println("</head>");
            out.println("<body>");
            out.println("Full name = " + fullname);
            out.println("<h1>Servlet NewServlet at " + request.getContextPath() + "</h1>");
            out.println("Username = " + userName);
            out.println("</body>");
            out.println("</html>");


        }
    }
// Autogenerated stuff omitted
}

没有“web.xml”,这些字段只是空的(并且没有失败)。然后我玩弄了“web.xml”,看看我如何定义它。 “java:comp/env/foo:NewServlet/fullName”名称是 Glassfish 4.1.1 似乎创建为 fullName 设置器的名称。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
     version="3.0">
    <env-entry >
        <env-entry-name>java:comp/env/foo.NewServlet/fullName</env-entry-name>
        <env-entry-type>java.lang.String</env-entry-type>
        <env-entry-value>!BAR!</env-entry-value> 
    </env-entry>
    <env-entry >
        <env-entry-name>java:comp/env/foo.NewServlet/userName</env-entry-name>
        <env-entry-type>java.lang.String</env-entry-type>
        <env-entry-value>!USERNAME!</env-entry-value>  
    </env-entry>
</web-app>

然后失败并显示

Severe:   Exception while deploying the app [WebApplication4] : Naming binding already exists for foo.NewServlet/userName in namespace {java:module/env/foo.NewServlet/userName=Env-Prop: java:comp/env/foo.NewServlet/userName@Non-Injectable Resource@java.lang.String@!USERNAME!@@, java:module/env/foo.NewServlet/fullName=Env-Prop: java:comp/env/foo.NewServlet/fullName@Non-Injectable Resource@java.lang.String@!BAR!@@}

项目中只有这两个文件。显然我误解了一些基本的东西,但是阅读 Java EE 教程和搜索建议并没有帮助我。我真的很想要两件事:

  1. 要么不使用容器生成的默认值向 @Resource-tag 提供任何提示,要么仅使用“our.application.fullName”之类的键。
  2. 如果出现任何错误(包括键值不存在),请大声失败。

建议?一个好的答案将给予 500 点赏金。

【问题讨论】:

标签: java servlets jakarta-ee jndi servlet-3.0


【解决方案1】:

你真的只漏掉了两个重要的细节:

  1. 在部署描述符(如web.xml)中指定资源名称时,无论是env-entry-name、resource-env-ref-name还是ejb-ref-name等,JNDI名称的java:comp/env部分总是隐式。因此,如果您希望由env-entry 定义的资源出现在JNDI 中的java:comp/env/foo,那么您将其env-entry-name 指定为:

         <env-entry-name>foo</env-entry-name>
    
  2. Java EE 规范 (§EE.5.2.5) 修改了应用于 @Resource 注释的“默认”名称的规则:

    类的一个字段可能是注入的目标。该字段不能是最终的。默认情况下,字段的名称与类的完全限定名称组合在一起,并直接用作应用程序组件命名上下文中的名称。例如,com.example 包中 MyApp 类中名为 myDatabase 的字段将对应于 JNDI 名称 java:comp/env/com.example.MyApp/myDatabase。该注释还允许显式指定 JNDI 名称。当部署描述符条目用于指定注入时,JNDI 名称和字段名称都被显式指定。请注意,默认情况下,JNDI 名称是相对于 java:comp/env 命名上下文的。

    换句话说,如果您的 servlet 的完全限定名称是 com.p45709634.NewServlet,那么 userName 字段的 JNDI 名称将是 java:comp/env/com.p45709634.NewServlet/userName。因此它的env-entry-name 将是:

         <env-entry-name>com.p45709634.NewServlet/userName</env-entry-name>
    

因此,如果您在 web.xml 文件中使用这些完全限定名称,那么您可以按照您的要求愉快地声明带注释的字段:

    @Resource
    private String userName;

    private String fullname;

    @Resource
    public void setFullName(String fullName){
        this.fullname = fullName;
    }

现在规范的同一章节指出:

如果容器找不到注入所需的资源,则类的初始化一定会失败,并且类不能投入使用。

然而,这似乎并没有在实践中发生(至少在 GlassFish 上对你来说,WildFly 对我来说)。这可能是由于对 CDI 注入规范的一些延迟,这似乎没有太多关于未能找到注入资源的说明。

因此,我们可能无法在 init 方法或 @PostConstruct 注释方法中验证这些字段。

【讨论】:

  • 我从 env-entry-name 标记中删除了“java:/comp/env”部分,它起作用了!谢谢!我猜错误消息是关于“/comp/env”已经被绑定,而不是已经使用的实际密钥。我想知道这是否是 Wildfly/Glassfish 的已知错误或功能,是否可以使用开关强制执行?无论如何,我会尽可能地给你赏金。如果我忘记了,请提醒我。
猜你喜欢
  • 2023-04-04
  • 1970-01-01
  • 2023-04-02
  • 2014-08-23
  • 2016-03-06
  • 2014-05-09
  • 2013-06-17
  • 2020-07-16
  • 2014-10-02
相关资源
最近更新 更多