import org.apache.catalina.Context;
import org.apache.catalina.deploy.ContextResource;
import org.apache.catalina.startup.Tomcat;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@EnableAutoConfiguration
@ComponentScan
@ImportResource("classpath:applicationContext.xml")
public class Application {
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder()
.showBanner(false)
.sources(Application.class)
.run(args);
}
@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
@Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory = (TomcatEmbeddedServletContainerFactory) container;
tomcatEmbeddedServletContainerFactory.addContextCustomizers(new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
ContextResource mydatasource = new ContextResource();
mydatasource.setName("jdbc/mydatasource");
mydatasource.setAuth("Container");
mydatasource.setType("javax.sql.DataSource");
mydatasource.setScope("Sharable");
mydatasource.setProperty("driverClassName", "oracle.jdbc.driver.OracleDriver");
mydatasource.setProperty("url", "jdbc:oracle:thin:@mydomain.com:1522:myid");
mydatasource.setProperty("username", "myusername");
mydatasource.setProperty("password", "mypassword");
context.getNamingResources().addResource(mydatasource);
}
});
}
}
};
}
}
I'm using spring boot and trying to startup with an embedded tomcat that creates a JNDI context for my datasources:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>1.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>1.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-oracle</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
If I remove the @ImportResource my application starts up just fine. I can connect to the tomcat instance. I can check all of my actuator endpoints. Using JConsole, I can connect to the application I can see my datasource in the MBeans (Catalina -> Resource -> Context -> "/" -> localhost -> javax.sql.DataSource -> jdbc/mydatasource)
I also have MBeans showing up, via JConsole, here (Tomcat -> DataSource -> / -> localhost -> javax.sql.DataSource -> jdbc/mydatasource)
However, when I @ImportResource what is actually looking for mydatasource via JNDI, it's not finding it.
<bean id="myDS" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/mydatasource"/>
</bean>
The relevant part of my imported xml file
The ContextResource that I'm configuring above is with the exact same parameters that I was using in the context.xml that is getting deployed when the application is deployed to a tomcat container. My imported beans and my application are working properly when deployed to a tomcat container.
So it appears that I have a context now, but it doesn't appear that the naming is right. I've tried to various combinations of the resource name, but can't seem to generate a "comp" bound in this context.
Caused by: javax.naming.NameNotFoundException: Name [comp/env/jdbc/mydatasource] is not bound in this Context. Unable to find [comp].
at org.apache.naming.NamingContext.lookup(NamingContext.java:819)
at org.apache.naming.NamingContext.lookup(NamingContext.java:167)
at org.apache.naming.SelectorContext.lookup(SelectorContext.java:156)
at javax.naming.InitialContext.lookup(InitialContext.java:392)
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.JndiObjectFactoryBean.lookupWithFallback(JndiObjectFactoryBean.java:231)
at org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.java:217)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549)
... 30 more
Have you tried
@Lazy
loading the datasource? Because you're initialising your embedded Tomcat container within the Spring context, you have to delay the initialisation of yourDataSource
(until the JNDI vars have been setup).N.B. I haven't had a chance to test this code yet!
You may also need to add the
@Lazy
annotation wherever the DataSource is being used. e.g.After all i got the answer thanks to wikisona, first the beans:
the full code it's here: https://github.com/wilkinsona/spring-boot-sample-tomcat-jndi
Please note instead of
I had to use the following method signature
I recently had the requirement to use JNDI with an embedded Tomcat in Spring Boot.
Actual answers give some hint to solve the issue but it was not enough as probably not updated.
Here is my contribution tested with Spring Boot 2.0.3.RELEASE.
Pom.xml
According to the datasource you specified, you may need to provide the Tomcat-DBCP library.
For example, with the default configuration, the instantiation of the datasource threw an exception :
As workaround you can add this dependency :
Of course, adapt the artifact version according to your Tomcat version.
Spring Configuration
You have to customize the bean that creates the
TomcatServletWebServerFactory
instance.Two things to do :
enabling the JNDI naming which is disabled by default
creating and add the JNDI resource(s) in the server context
For example with a PostgreSQL database datasource it looks like :
You should now be able to lookup the JNDI ressource anywhere by using a standard
InitialContext
instance :You can also use
JndiObjectFactoryBean
of Spring to lookup up the resource :To take advantage of the DI container you can also make the
DataSource
a Spring bean :And so you can now inject the DataSource in any Spring beans such as :
Note that many examples on the internet seems to disable the lookup of the JNDI resource on startup :
But I think that it is helpless as it invokes just after
afterPropertiesSet()
that does the lookup !In spring boot 2.1, i found another solution. Extend standard factory class method getTomcatWebServer. And then return it as a bean from anywhere.
Loading resources from context.xml doesn't work though. Will try to find out.
By default, JNDI is disabled in embedded Tomcat which is causing the
NoInitialContextException
. You need to callTomcat.enableNaming()
to enable it. The easiest way to do that is with aTomcatEmbeddedServletContainer
subclass:If you take this approach, you can also register the
DataSource
in JNDI by overriding thepostProcessContext
method in yourTomcatEmbeddedServletContainerFactory
subclass.context.getNamingResources().addResource
adds the resource to thejava:comp/env
context so the resource's name should bejdbc/mydatasource
notjava:comp/env/mydatasource
.Tomcat uses the thread context class loader to determine which JNDI context a lookup should be performed against. You're binding the resource into the web app's JNDI context so you need to ensure that the lookup is performed when the web app's class loader is the thread context class loader. You should be able to achieve this by setting
lookupOnStartup
tofalse
on thejndiObjectFactoryBean
. You'll also need to setexpectedType
tojavax.sql.DataSource
:This will create a proxy for the DataSource with the actual JNDI lookup being performed on first use rather than during application context startup.
The approach described above is illustrated in this Spring Boot sample.