@Resource error: "Naming binding already exists for foo.NewServlet/userName in namespace"

@Resource error: "Naming binding already exists for foo.NewServlet/userName in namespace"

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

我在 Netbeans 8.2 和 Glassfish 4.1.1 中玩过一个简单的 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 setter.

创建的名称
<?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. 这样的键
  3. 如果有任何错误,包括键值不存在,请大声失败。

建议?一个好的答案将获得500点赏金。

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

  1. 在部署描述符中指定资源名称时(例如 web.xml),无论是 env-entry-nameresource-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 注释的 "default" 名称的规则:

    A field of a class may be the target of injection. The field must not be final. By default, the name of the field is combined with the fully qualified name of the class and used directly as the name in the application component’s naming context. For example, a field named myDatabase in the class MyApp in the package com.example would correspond to the JNDI name java:comp/env/ com.example.MyApp/myDatabase. The annotation also allows the JNDI name to be specified explicitly. When a deployment descriptor entry is used to specify injection, the JNDI name and the field name are both specified explicitly. Note that, by default, the JNDI name is relative to the java:comp/env naming context.

    换句话说,如果您的 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;
    }

现在规范的同一章指出:

If the container fails to find a resource needed for injection, initialization of the class must fail, and the class must not be put into service.

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

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