@Resource error: “Naming binding already exists fo

2019-07-21 06:29发布

问题:

I am looking at using the "@Resource String ..." injection available in servlet 3.0+ containers for providing configuration parameters easily to servlets. I would like for the defaults to work and fail if the key is not present in JNDI (indicating a configuration error)

I have toyed with a simple servlet in Netbeans 8.2 with Glassfish 4.1.1 where I would like to have the userName field, and the setFullName(String fullName) set:

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
}

Without "web.xml" the fields are just null (and no failure). I have then played around with "web.xml" to see how I could define this. The "java:comp/env/foo:NewServlet/fullName" name is what Glassfish 4.1.1 appears to create as the name for the 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>

This then fails with

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!@@}

There is nothing else but these two files in the project. Apparently I am misunderstanding something basic, but reading the Java EE tutorial and searching for suggestions did not help me. I would really like two things:

  1. Either not providing any hints to the @Resource-tag using the container generated defaults or just a key like "our.application.fullName".
  2. Fail loudly if anything is wrong, including the key-value not being present.

Suggestions? A good answer will give a 500 point bounty.

回答1:

You have really only missed two important details:

  1. When specifying the name of a resource in a deployment descriptor (such as the web.xml), whether it be an env-entry-name, resource-env-ref-name or ejb-ref-name, etc, the java:comp/env part of the JNDI name is always implicit. Therefore, if you want a resource defined by an env-entry to appear in JNDI at java:comp/env/foo, then you specify its env-entry-name as:

         <env-entry-name>foo</env-entry-name>
    
  2. The Java EE specification (§EE.5.2.5) modifies the rules for the "default" name applied to @Resource annotations:

    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.

    In other words, if the fully qualified name of your servlet is com.p45709634.NewServlet, then the JNDI name of the userName field is going to be java:comp/env/com.p45709634.NewServlet/userName. Therefore its env-entry-name is going to be:

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

So, if you use these fully qualified names in your web.xml file then you can happily declare the annotated fields as you have requested:

    @Resource
    private String userName;

    private String fullname;

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

Now that same chapter of the specification states:

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.

However this does not seem to happen in practice (at least on GlassFish for you and WildFly for me). This may be due to some deferment to the CDI spec for injection, which does not seem to have much to say about failure to locate resources for injection.

Consequently we may be stuck with validating these fields in the init method or an @PostConstruct annotated method.