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?
source to share
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.
source to share