Spring Boot - Tomcat JNDI failure in beans service

I am trying to switch c3p0 datasource to Tomcat JNDI in Spring Boot 1.1.6 web project. I found a sample app on GitHub that works great when the DataSource instance is available from the @RestController

annotated class .

@RestController
public class TestController {

  @Autowired
  private DataSource dataSource;

  @RequestMapping("/test")
  @ResponseBody
  public String test() {

  // Gets object instance... everything is OK...
  System.out.println(this.dataSource); 
  }

      

However, when I try to inject the same datasource into the @Service

annotated bean, I get javax.naming.NameNotFoundException

as soon as the instance is used in the code.

@Service
public class TestService {

  @Autowired
  private DataSource dataSource;

  @PostConstruct
  private void init() {

    // Throws exception...
    System.out.println(this.dataSource);

}

      

Stack trace:

Caused by: javax.naming.NameNotFoundException: Name [java:comp/env/jdbc/myDataSource] is not bound in this Context. Unable to find [java:comp].
    at org.apache.naming.NamingContext.lookup(NamingContext.java:819)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:167)
    at javax.naming.InitialContext.lookup(InitialContext.java:411)
    at org.springframework.jndi.JndiTemplate$1.doInContext(JndiTemplate.java:155)
    at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:87)
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:152)
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179)
    at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:95)
    at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:106)
    at org.springframework.jndi.JndiObjectTargetSource.getTarget(JndiObjectTargetSource.java:135)
    ... 11 more

      

I wonder why the JNDI datasource bean is not available from the @Service class? Any ideas?

+3


source to share


1 answer


The real problem is that you are trying to get the datasource in PostConstruct

, not that it is not working in the beans service.

By default, Tomcat uses the thread context classloader to determine which JNDI context to search against. Therefore, when you bind a resource in the JNDI context of a web application, you need to ensure that the lookup is performed when the web application org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader

classloader (i.e. ) is the thread context classloader.

Below is the code snippet for org.apache.naming.ContextBindings.getClassLoader

that performs the check mentioned above:

/**
 * Retrieves the naming context bound to a class loader.
 */
public static Context getClassLoader()
    throws NamingException {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    Context context = null;
    do {
        context = clBindings.get(cl);
        if (context != null) {
            return context;
        }
    } while ((cl = cl.getParent()) != null);
    throw new NamingException
        (sm.getString("contextBindings.noContextBoundToCL"));
}

      



So, when the code is inside PostConstruct

and you are doing a JNDI lookup (which is done implicitly when accessing the object), the thread context classloader is still the application classloader (i.e. sun.misc.Launcher$AppClassLoader

). It is for this reason that JNDI cannot find the object as it tries to find java:comp/env/jdbc/myDataSource

the naming context (i.e. javax.naming.Context

) from sun.misc.Launcher$AppClassLoader

.

You can move the code from PostConstruct

and it should work fine.

Crossbreeding is another related question that also provides more information on the issue.

Hope this answers your question.

+4


source







All Articles