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?
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.