-- MezianeKettou - 03 Sep 2019

How To Make A Servlet Container Aware Spring Boot Application Startable In Eclipse

A Spring Boot application must extend SpringBootServletInitializer and have its packaging property set to war in its pom file in order to get a war file that can be deployed into a servlet container like Tomcat. This is a so called traditional-deployment.

It's also advisable to set the scope of spring-boot-starter-tomcat to provided in order to make the war file a bit thinner

 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope><b>provided</b></scope>
</dependency>

The maven-dependencies

<dependencies>  
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc7</artifactId>
<version>12.1.0.1</version>
</dependency>

<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.19</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

Eliminating the need for defining a Configuration Bean for our DataSource

In general a Spring Boot application communicates with a database and exchanges data with it, and it's usual and best practice to let Spring work for us and automatically configure the DataSource, provinding just the information needed, what makes development faster and easier.

The easyiest way to do that is using JNDI.

Configuring JNDI resource name in application.properties file.

In the application.properties we add the following property:

spring.datasource.jndi-name=java:comp/env/jdbc/operdb

Using JNDI lookup:

We deploy our application on Tomcat by just putting the generated war file under the $CATALINA_HOME\webapps directory wehre CATALINA_HOME is the directory where Tomcat server is installed.

We add a file context.xml under the $CATALINA_HOME\webapps/our-application-name/META-INF directory with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/operdb"
type="javax.sql.DataSource"
driverClassName="oracle.jdbc.driver.OracleDriver"
username="DBUSER"
password="SECRET"
url="jdbc:oracle:thin:@//dbla0ab.acc.gsi.de:1521/AccDbP.acc.gsi.de"
maxActive="100"
maxIdle="20"
minIdle="5"
maxWait="10000"/>
</Context>

We must restart Tomcat so that it takes the new content.xml in account.

How about starting our Spring Application in eclipse?

After all it's a Spring Boot Application.

To be able to start it as such, we have to
  1. add a main method and
  2. inform the application which database to use and how to connect to it.
The easiest way to make the application aware of our database is here again to use JNDI in the embedded Tomcat.

How it works?

When lauched as a standalone application, the the main method is considered as the entry point and is executed and SpringBootServletInitializer is simply ignored.

When deployed to a servlet container, the container uses SpringBootServletInitializer to start the application and the main method is just ignored.

For moer information look at this nice post on Stackoverflow.

A TomcatServletWebServerFactory Bean to get an embedded Tomcat

If we add a method that returns a TomcatServletWebServerFactory Bean to the main class of our Application, this bean will be available only if the application is started as a standalone one, in other words as a Spring Boot Application.

@SpringBootApplication
public class ServerStarter extends SpringBootServletInitializer {
private static final Logger logger = LoggerFactory.getLogger(ServerStarter.class);

public static void main(final String[] args) {
new ServerStarter().configure(new SpringApplicationBuilder(ServerStarter.class)).run(args);
..
}

@Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory(final Tomcat tomcat) {
logger.debug("Inside TomcatServletWebServerFactory");
return super.getTomcatWebServer(tomcat);
}
}
}

Enable the use of JNDI in the embedded Tomcat

By default, JNDI is disabled in embedded Tomcat. So we have to enable it. The best way to do it, is to override TomcatServletWebServerFactory's getTomcatWebServer() method

@SpringBootApplication public class ServerStarter extends SpringBootServletInitializer {
..
@Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
@Override
protected TomcatWebServer getTomcatWebServer(final Tomcat tomcat) {
logger.debug("Inside TomcatServletWebServerFactory");
<b>tomcat.enableNaming();</b>
return super.getTomcatWebServer(tomcat);
}
}
}
}

Register The DataSource in JNDI

To register the DataSource in JNDI we use the same class TomcatServletWebServerFactory and override its postProcessContext() method:

@SpringBootApplication public class ServerStarter extends SpringBootServletInitializer {
..
@Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
..
}
}

@Override
protected void postProcessContext(final Context context) {
final ContextResource resource = new ContextResource();
final String dbUrl = "jdbc:oracle:thin:@//dbla0ab.acc.gsi.de:1521/AccDbP.acc.gsi.de";
resource.setName("jdbc/operdb");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "oracle.jdbc.driver.OracleDriver");
resource.setProperty("url", dbUrl);
resource.setProperty("username", "DBUSER");
resource.setProperty("password", "SECRET");
context.getNamingResources().addResource(resource);
logger.debug("resource added to context.");
}
}

Define a DataSource Bean for the embedded Tomcat

We must now let Spring configure a Datasource using the registred JNDI resource:

@SpringBootApplicationpublic class ServerStarter extends SpringBootServletInitializer {
..
@Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() { ..
}

@Override
protected void postProcessContext(final Context context) {
..
}

@Bean(destroyMethod = "")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
final JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:comp/env/jdbc/operdb");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
}

That's it!

We can now start our application as Spring Boot Application directly in eclipse and we can buid a war file that is servlet container aware.

Using properties for a DataSource Bean when running the application in an embedded Tomcat Server

It is possible to add custom properties to use for the configuration of a DataSource Bean when the application is running in an embedded Tomcat Server.
# for a dedicated Tomcat
spring.datasource.jndi-name=jdbc/dirserver

# for the embedded Tomcat
embedded.datasource.driver-class-name=oracle.jdbc.OracleDriver
#embedded.datasource.url=jdbc:oracle:thin:@AccDbP
embedded.datasource.url=jdbc:oracle:thin:@//dbla0ab.acc.gsi.de:1521/AccDbP.acc.gsi.de
embedded.datasource.username=superuse
embedded.datasource.password=topsecret

In the @SpringBootApplication annotated class we define a DataSource Bean that we configure with the properties provided in the application.properties.
@SpringBootApplication
public class MwConfigurationApplication extends SpringBootServletInitializer {
  private static final Logger lg = LoggerFactory.getLogger(MwConfigurationApplication.class);

  @Value("${embedded.datasource.username}")
  String username;
  @Value("${embedded.datasource.password}")
  String password;
  @Value("${embedded.datasource.driver-class-name}")
  String driverClassName;
  @Value("${embedded.datasource.url}")
  String url;
  public static void main(final String[] args) {
    lg.debug("Inside {}", MwConfigurationApplication.class);
    new MwConfigurationApplication().configure(new SpringApplicationBuilder(MwConfigurationApplication.class)).run(args);
  }
  
@Bean(destroyMethod = "")
  public DataSource oracledataSoutŕce() throws SQLException {
    final OracleDataSource dataSource = new OracleDataSource();
    dataSource.setUser(username);
    dataSource.setPassword(password);
    dataSource.setURL(url);
    dataSource.setImplicitCachingEnabled(true);
    dataSource.setFastConnectionFailoverEnabled(true);
    return dataSource;
  }
}
The application can be now started as standlone or deployed to a dedicated Tomcat server.
Topic revision: r5 - 16 Sep 2019, MezianeKettou
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback