Tomcat @Resource annotations API annotation stops working in Tomcat 7

后端 未结 4 2041
暗喜
暗喜 2021-01-29 12:44

I have been using Tomcat 6.0.26-6.0.35 for several years with JSF 2 Mojarra, various versions up to 2.1.2 which I have been using for some months. I have several request-scoped

相关标签:
4条回答
  • 2021-01-29 13:02

    My guess is something is causing org.apache.catalina.core.DefaultInstanceManager to ignore annotations because faces requires the @PostConstruct method to be processed apart from @Resource fields. I have created a workaround that works for fields annotated with @Resource.

    Add the following to web.xml:

    <context-param>
        <param-name>com.sun.faces.injectionProvider</param-name>
        <param-value>com.example.faces.Tomcat7InjectionProvider</param-value>
    </context-param>
    

    And add the class to your source:

    package com.example.faces;
    
    import java.lang.reflect.Field;
    
    import javax.annotation.Resource;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    import javax.servlet.ServletContext;
    
    import org.apache.catalina.util.Introspection;
    
    import com.sun.faces.spi.InjectionProviderException;
    import com.sun.faces.vendor.WebContainerInjectionProvider;
    
    public class Tomcat7InjectionProvider extends WebContainerInjectionProvider {
    
        public Tomcat7InjectionProvider(ServletContext servletContext) {
        }
    
        @Override
        public void inject(Object managedBean) throws InjectionProviderException {
            if (managedBean != null) {
                // see org.apache.catalina.core.DefaultInstanceManager
                Field[] fields = Introspection.getDeclaredFields(managedBean.getClass());
                for (Field field : fields) {
                    // field may be private
                    field.setAccessible(true);
                    if (field.isAnnotationPresent(Resource.class)) {
                        Resource annotation = null;
                        try {
                            annotation = field.getAnnotation(Resource.class);
                            Context ctx = new InitialContext();
                            Object resource = ctx.lookup("java:comp/env/" + annotation.name());
                            field.set(managedBean, resource);
                        } catch (Exception e) {
                            throw new InjectionProviderException("cannot find resource " + annotation.name(), e);
                        }
                    }
                }
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-29 13:04

    Sorry I'm unable to comment due to rep or I would of asked for more clarification on what errors/etc you are seeing. That being said I have tested with both Tomcat 7.0.27 and Tomcat 7.0.41 with java full version "1.6.0_51-b11-457" and was able to use @Resource.

    Context.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <Context>
      <Resource name="jdbc/Sample" auth="Container"
          type="javax.sql.DataSource" username="nbuser" password="nbuser"
          driverClassName="org.apache.derby.jdbc.ClientDriver" 
          url="jdbc:derby://localhost/Sample"
          maxActive="8" maxIdle="4"/>
    </Context>
    

    ResourceTest.java

    import java.io.IOException;
    import java.io.PrintWriter;
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.Statement;
    
    import javax.annotation.Resource;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.sql.DataSource;
    
    @WebServlet("/ResourceTest")
    public class ResourceTest extends HttpServlet {
      private static final long serialVersionUID = 1L;
      @Resource(name="jdbc/Sample")
      private DataSource ds;
    
    
    public ResourceTest() {
        // TODO Auto-generated constructor stub
    }
    
    
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        PrintWriter out = response.getWriter();
        out.println("<body>");
    
    
        try {
            //Context initCtx = new InitialContext();
            //Context envCtx = (Context) initCtx.lookup("java:comp/env");
            //DataSource ds = (DataSource) envCtx.lookup("jdbc/Sample");
            Connection conn = ds.getConnection();
            Statement s = conn.createStatement();
            s.execute("Select * From \"NBUSER\".\"Friends\"");
            ResultSet rs = s.getResultSet();
            while (rs.next()) {
                out.println(rs.getString("NAME") + " is my friend.");
            }
            conn.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    
        out.println("</body>");
    
    }
    
    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
    }
    
    }
    
    0 讨论(0)
  • 2021-01-29 13:06

    Another possibility, yet I would think unlikely; is you are using metadata-complete="true" in your web.xml or web-fragment.xml file(s).

    Defined metadata-complete: The metadata-complete attribute defines whether this deployment descriptor and other related deployment descriptors for this module (e.g., web service descriptors) are complete, or whether the class files available to this module and packaged with this application should be examined for annotations that specify deployment information. If metadata-complete is set to "true", the deployment tool must ignore any annotations that specify deployment information, which might be present in the class files of the application. If metadata-complete is not specified or is set to "false", the deployment tool must examine the class files of the application for annotations, as specified by the specifications.

    Example web.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app metadata-complete="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
      <display-name>ResourceTest</display-name>
      <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
     </welcome-file-list>
    </web-app>
    
    0 讨论(0)
  • 2021-01-29 13:21

    Answering my own question, an improved version of @JeffE's answer. The basic problem is:

    1. A Tomcat6InjectionProvider was provided with JSF 2.0 but was removed at some point.
    2. The default WebContainerInjectionProvider doesn't process @Resource annotations, as JeffE points out.

    You can overcome this without web.xml context-entries as follows:

    1. Create a file called META-INF/services/com.sun.faces.spi.injectionprovider and add the following line to it:

      com.sun.faces.vendor.Tomcat7InjectionProvider:org.apache.catalina.core.DefaultInstanceManager
      

      The meaning of this line is that if the second class is present in the deployment, the first class is used as the injection provider. The second class above is part of Tomcat 7.

    2. Compile the following class.

    This version contains numerous improvements over JeffE's version. Specifically:

    • it processes superclasses, as required by the @Resource and @Resources Javadoc
    • it processes @Resource and @Resources annotations at the class level
    • it processes methods annotated with @Resource, as required by the @Resource Javadoc
    • it handles empty or missing name attributes of @Resources correctly, as required by the @Resource Javadoc
    • it restores the Field's original access
    • it has no dependencies on Tomcat classes.

    Adjust the package name above if you change its package name.

    package com.sun.faces.vendor;
    
    import com.sun.faces.spi.DiscoverableInjectionProvider;
    import com.sun.faces.spi.InjectionProviderException;
    import java.lang.reflect.Field;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.annotation.Resource;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import javax.servlet.ServletContext;
    
    /**
     * @author Jeff E
     * @author Esmond Pitt Improvements named above.
     * 
     * @see javax.annotation.Resource
     *
     * @see <a href="http://stackoverflow.com/a/21978577/207421">This StackOverflow
     * answer, although what org.apache.catalina.util.Introspection may be and where
     * it lives remains a mystery.</a>
     */
    public class Tomcat7InjectionProvider
        extends DiscoverableInjectionProvider
    {
        private Logger logger = Logger.getLogger(this.getClass().getName());
        private ServletContext  servletContext;
    
        private WebContainerInjectionProvider   delegate = new WebContainerInjectionProvider();
    
        public Tomcat7InjectionProvider(ServletContext servletContext)
        {
            logger.config("constructed");
            this.servletContext = servletContext;
        }
    
        @Override
        public void inject(Object managedBean) throws InjectionProviderException
        {
            logger.log(Level.CONFIG, "managedBean={0}", new Object[]{managedBean.getClass().getName()});
            Class<?> clazz = managedBean.getClass();
            do
            {
                List<Resource>  classResources = new LinkedList<>();
                // Process class-level @Resources and @Resource
                if (clazz.isAnnotationPresent(Resources.class))
                {
                    Resources annotation = clazz.getAnnotation(Resources.class);
                    for (Resource resource : annotation.value())
                    {
                        classResources.add(resource);
                    }
                }
                if (clazz.isAnnotationPresent(Resource.class))
                {
                    Resource    annotation = clazz.getAnnotation(Resource.class);
                    classResources.add(annotation);
                }
                for (Resource annotation : classResources)
                {
                    String  name = annotation.name();
                    // Make sure the resource exists.
                    try
                    {
                        Context ctx = new InitialContext();
                        Object resource = ctx.lookup("java:comp/env/" + name);
                    }
                    catch (NamingException exc)
                    {
                        throw new InjectionProviderException("checking class resource " + annotation.name()+" of "+clazz.getName(), exc);
                    }
                }
                // Process fields with @Resource
                // see org.apache.catalina.core.DefaultInstanceManager
    //            Field[] fields = Introspection.getDeclaredFields(managedBean.getClass());
                Field[] fields = managedBean.getClass().getDeclaredFields();
                for (Field field : fields)
                {
                    if (field.isAnnotationPresent(Resource.class))
                    {
                        Resource annotation = field.getAnnotation(Resource.class);
                        String name = annotation.name();
                        logger.log(Level.CONFIG, "injecting @Resource(name=\"{2}\") into {0}.{1}", new Object[]
                            {
                                managedBean.getClass().getName(), field.getName(), name
                            });
                        try
                        {
                            Context ctx = new InitialContext();
                            Object resource;
                            if (name != null && name.length() > 0)
                            {
                                resource = ctx.lookup("java:comp/env/" + name);
                            }
                            else
                            {
                                resource = ctx.lookup(clazz.getName() + "/" + field.getName());
                            }
                            // field may be private
                            boolean accessibility = field.isAccessible();
                            try
                            {
                                field.setAccessible(true);
                                field.set(managedBean, resource);
                            }
                            finally
                            {
                                field.setAccessible(accessibility);
                            }
                        }
                        catch (NamingException | IllegalAccessException exc)
                        {
                            throw new InjectionProviderException("injecting resource " + annotation.name()+" into "+clazz.getName()+"."+field.getName(), exc);
                        }
                    }
                }
                // Process methods with @Resource
                for (Method method : clazz.getDeclaredMethods())
                {
                    if (method.isAnnotationPresent(Resource.class)
                    && method.getName().startsWith("set")
                    && method.getName().length() > 3
                    && method.getReturnType() == void.class
                    && method.getParameterTypes().length == 1)
                    {
                        // It's a setter with @Resource
                        Resource annotation = method.getAnnotation(Resource.class);
                        String name = annotation.name();
                        logger.log(Level.CONFIG, "injecting @Resource(name=\"{2}\") via {0}.{1}", new Object[]
                            {
                                managedBean.getClass().getName(), method.getName(), name
                            });
                        try
                        {
                            Context ctx = new InitialContext();
                            Object resource;
                            if (name != null && name.length() > 0)
                            {
                                resource = ctx.lookup("java:comp/env/" + name);
                            }
                            else
                            {
                                name = method.getName().substring(3);
                                name = name.substring(0,1).toLowerCase()+name.substring(1);
                                resource = ctx.lookup(clazz.getName() + "/" + name);
                            }
                            // method may be private
                            boolean accessibility = method.isAccessible();
                            try
                            {
                                method.setAccessible(true);
                                method.invoke(managedBean, resource);
                            }
                            finally
                            {
                                method.setAccessible(accessibility);
                            }
                        }
                        catch (NamingException | IllegalAccessException | InvocationTargetException exc)
                        {
                            throw new InjectionProviderException("injecting resource " + annotation.name()+" via "+clazz.getName()+"."+method.getName(), exc);
                        }
                    }
                }
            } while ((clazz = clazz.getSuperclass()) != Object.class);
        }
    
        @Override
        public void invokePostConstruct(Object managedBean) throws InjectionProviderException
        {
            logger.log(Level.CONFIG, "managedBean={0}", new Object[]{managedBean});
            delegate.invokePostConstruct(managedBean);
        }
    
        @Override
        public void invokePreDestroy(Object managedBean) throws InjectionProviderException
        {
            logger.log(Level.CONFIG, "managedBean={0}", new Object[]{managedBean});
            delegate.invokePreDestroy(managedBean);
        }
    }
    

    E&OE

    0 讨论(0)
提交回复
热议问题