5
votes

I have created a simple test case that shows the issue I'm currently facing.

What I was trying to do is manually starting Tomcat embedded from a CommandLineRunner and manually deploying a war file available somewhere on the file system:

package example;

import java.io.File;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class MyCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {

        File tomcatBaseDir = new File("<tomcat-base-dir>");
        File warToBeDeployed = new File("<war-to-be-deployed>");

        Tomcat tomcat = new Tomcat();
        tomcat.setPort(8080);
        tomcat.setBaseDir(tomcatBaseDir.getAbsolutePath());
        tomcat.getHost().setAppBase(tomcatBaseDir.getAbsolutePath());
        tomcat.getHost().setAutoDeploy(true);
        tomcat.getHost().setDeployOnStartup(true);

        try {
            tomcat.start();
            System.out.println("Tomcat started on " + tomcat.getHost());
        } catch (LifecycleException e) {
            System.err.println("Tomcat could not be started");
            System.exit(-1);
        }

        Context appContext = tomcat.addWebapp(tomcat.getHost(), "/hello-world", warToBeDeployed.getAbsolutePath());
        System.out.println("Deployed " + appContext.getBaseName() + " on " + tomcat.getHost());

        tomcat.getServer().await();

    }

}

This is my simple pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.5.RELEASE</version>
</parent>

<artifactId>spring-boot-tomcat-embedded-manually</artifactId>
<version>0.0.1-SNAPSHOT</version>

<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-catalina</artifactId>
        <version>${tomcat.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-util</artifactId>
        <version>${tomcat.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

So this is what happens:

  1. if I run the application from within Eclipse, no problem at all;

  2. if I run "mvn clean package" and then "java -jar [path-to-jar]", I get:

    java.lang.ClassNotFoundException: org.apache.tomcat.util.descriptor.web.ServletDef
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1305) ~[tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1139) ~[tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.tomcat.util.IntrospectionUtils.callMethod1(IntrospectionUtils.java:359) ~[tomcat-util-8.0.33.jar!/:8.0.33]
        at org.apache.tomcat.util.digester.SetNextRule.end(SetNextRule.java:145) ~[tomcat-util-scan-8.0.33.jar!/:8.0.33]
        at org.apache.tomcat.util.digester.Digester.endElement(Digester.java:956) [tomcat-util-scan-8.0.33.jar!/:8.0.33]
        at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(Unknown Source) [na:1.8.0_91]
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(Unknown Source) [na:1.8.0_91]
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(Unknown Source) [na:1.8.0_91]
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(Unknown Source) [na:1.8.0_91]
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) [na:1.8.0_91]
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source) [na:1.8.0_91]
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source) [na:1.8.0_91]
        at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source) [na:1.8.0_91]
        at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(Unknown Source) [na:1.8.0_91]
        at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source) [na:1.8.0_91]
        at org.apache.tomcat.util.digester.Digester.parse(Digester.java:1451) [tomcat-util-scan-8.0.33.jar!/:8.0.33]
        at org.apache.tomcat.util.descriptor.web.WebXmlParser.parseWebXml(WebXmlParser.java:120) [tomcat-util-scan-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1115) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:779) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:306) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:95) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5150) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:725) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:701) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.startup.Tomcat.addWebapp(Tomcat.java:558) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at org.apache.catalina.startup.Tomcat.addWebapp(Tomcat.java:523) [tomcat-catalina-8.0.33.jar!/:8.0.33]
        at example.MyCommandLineRunner.run(MyCommandLineRunner.java:37) [spring-boot-tomcat-embedded-manually-0.0.1-SNAPSHOT.jar!/:0.0.1-SNAPSHOT]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:806) [spring-boot-1.3.5.RELEASE.jar!/:1.3.5.RELEASE]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:790) [spring-boot-1.3.5.RELEASE.jar!/:1.3.5.RELEASE]
        at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:777) [spring-boot-1.3.5.RELEASE.jar!/:1.3.5.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) [spring-boot-1.3.5.RELEASE.jar!/:1.3.5.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191) [spring-boot-1.3.5.RELEASE.jar!/:1.3.5.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1180) [spring-boot-1.3.5.RELEASE.jar!/:1.3.5.RELEASE]
        at example.Main.main(Main.java:10) [spring-boot-tomcat-embedded-manually-0.0.1-SNAPSHOT.jar!/:0.0.1-SNAPSHOT]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_91]
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_91]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_91]
        at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_91]
        at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:54) [spring-boot-tomcat-embedded-manually-0.0.1-SNAPSHOT.jar!/:0.0.1-SNAPSHOT]
        at java.lang.Thread.run(Unknown Source) [na:1.8.0_91]
    
  3. if I replace the spring-boot-maven-plugin with the maven-shade-plugin and then try "mvn clean package" followed by "java -jar [path-to-jar]" everything works fine.

Am I missing some configuration on the spring-boot-maven-plugin to make my executable jar actually work?

UPDATE 1

My issue is somewhat related to this one. I tried the solution proposed there and indeed it does work, from both command line and Eclipse.

But still, I don't understand the inconsistent behavior manifested in my specific case:

  • why it works in eclipse and it doesn't work from the command line?
  • why it works if I replace the spring-boot-maven-plugin with the maven-shade-plugin?
2
Why? Why are you starting it yourself, you are basically working around (against) spring boot here. Just let that start tomcat for you.M. Deinum
Unfortunately, I don't want MY web application to be deployed within an embedded tomcat otherwise I would have built a standard spring-boot web app. As a matter of fact, my application is not even a web application. I want my application to start, deploy some other web applications within an embedded Tomcat and then call those services. I'm curious to understand whether this is a spring-boot-maven-plugin limitation, issue or wrong configuration.fabriziocucci
Feels like you are making things to complex and I don't see why it needs to be a boot application that way. However from what it looks like you are mixing regular tomcat jars with tomcat embedded.M. Deinum
I want it to be a spring boot application because right now I want to use spring DI and profiles but I might need something else that spring boot offers out-of-the-box in a bit. I don't see much of complexity in my application logic, actually it's pretty trivial.fabriziocucci
Running an embedded tomcat,launching an external war, and call some services. Seems overkill to me, just make the war executabel. Hence my suggestion it is too complex.M. Deinum

2 Answers

3
votes

Look at this question. You should add class loader to your context. Add directly under line

Context appContext = tomcat.addWebapp(tomcat.getHost(), "/hello-world", warToBeDeployed.getAbsolutePath());

This code

WebappLoader loader = new WebappLoader(Thread.currentThread().getContextClassLoader());
appContext.setLoader(loader);

It worked in my case.

1
votes

It is kind of strange, I would have thought your pom looks ok. That said, try replacing your tomcat entries in the pom by :

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

Hope it helps