Spring Boot: How to add another WAR files to the embedded tomcat?

后端 未结 3 1714
逝去的感伤
逝去的感伤 2020-11-28 08:28

Spring Boot\'s embedded tomcat is very handy, for both development and deploy.

But what if an another (3rd-party) WAR file (for example, GeoServer) should be added?<

相关标签:
3条回答
  • 2020-11-28 08:37

    You can add a war file to embedded Tomcat using Tomcat.addWebapp. As its javadoc says, it's the "equivalent to adding a web application to Tomcat's web apps directory". To use this API in Spring Boot, you need to use a custom TomcatEmbeddedServletContainerFactory subclass:

    @Bean
    public EmbeddedServletContainerFactory servletContainerFactory() {
        return new TomcatEmbeddedServletContainerFactory() {
    
            @Override
            protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                    Tomcat tomcat) {
                // Ensure that the webapps directory exists
                new File(tomcat.getServer().getCatalinaBase(), "webapps").mkdirs();
    
                try {
                    Context context = tomcat.addWebapp("/foo", "/path/to/foo.war");
                    // Allow the webapp to load classes from your fat jar
                    context.setParentClassLoader(getClass().getClassLoader());
                } catch (ServletException ex) {
                    throw new IllegalStateException("Failed to add webapp", ex);
                }
                return super.getTomcatEmbeddedServletContainer(tomcat);
            }
    
        };
    }
    
    0 讨论(0)
  • 2020-11-28 08:41

    It took a while to figure this out for Spring Boot 2 as none of the answers fully worked for me. I finally came up with this (fyi I have SSL turned on): WarRun.java with Gradle dependencies below to make it work.

    What it gives:

    embedded tomcat with context path / at https://localhost:8070

    sample.war at https://localhost:8070/sample

    SampleWebApp.war at https://localhost:8070/yo

    import java.io.File;
    import java.io.IOException;
    import java.io.PrintStream;
    import java.util.Properties;
    import org.apache.catalina.Context;
    import org.apache.catalina.startup.Tomcat;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.WebApplicationType;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
    import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
    import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Profile;
    import org.springframework.core.io.ClassPathResource;
    
    @ComponentScan({ "com.towianski.controllers" })
    @SpringBootApplication
    @Profile("server")
    public class WarRun extends SpringBootServletInitializer {
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(WarRun.class).web(  WebApplicationType.SERVLET );
        }
    
        public static void main(String[] args) {
    
            SpringApplication app = new SpringApplication(WarRun.class);
            System.out.println( "Entered WarRun.main");
    
            String loggingFile = "";
            String dir = "";
    
            for ( int i = 0; i < args.length; i++ )
                {
    //            logger.info( "** args [" + i + "] =" + args[i] + "=" );
                System.out.println( "** args [" + i + "] =" + args[i] + "=" );
                if ( args[i].toLowerCase().startsWith( "-dir" ) )
                    {
                    dir = args[i].substring( "-dir=".length() );
                    }
                else if ( args[i].toLowerCase().startsWith( "--logging.file" ) )
                    {
                    loggingFile = args[i].substring( "--logging.file=".length() );
                    stdOutFilePropertyChange( loggingFile );
                    stdErrFilePropertyChange( loggingFile );
                    }
                }
    
            Properties properties = new Properties();
    //        properties.setProperty( "spring.resources.static-locations",
    //                               "classpath:/home/stan/Downloads" );
            properties.setProperty( "server.port", "8070" );
    //        System.setProperty("server.servlet.context-path", "/prop");     <--- Will set embedded Spring Boot Tomcat context path
            properties.setProperty( "spring.security.user.name", "stan" );
            properties.setProperty( "spring.security.user.password", "stan" );
            System.out.println( "Entered WarRun.main after set properties");
            app.setDefaultProperties(properties);
            System.out.println( "Entered WarRun.main after call set props. before app.run");
    
            app.run(args);
            System.out.println( "Entered WarRun.main after app.run()");
        }
    
        @Bean
        public ServletWebServerFactory servletContainer() {
            return new TomcatServletWebServerFactory() {
                protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
                    System.out.println( "tomcat.getServer().getCatalinaBase() =" + tomcat.getServer().getCatalinaBase() + "=" );
                    new File(tomcat.getServer().getCatalinaBase(), "/webapps").mkdirs();
        //            try {
        //                Files.copy( (new File( "/home/stan/Downloads/sample.war" ) ).toPath(), (new File( tomcat.getServer().getCatalinaBase() +"/webapp/sample.war") ).toPath());
        //            } catch (IOException ex) {
        //                Logger.getLogger(WarRun.class.getName()).log(Level.SEVERE, null, ex);
        //            }
                    try {
                        System.out.println( "Entered ServletWebServerFactory servletContainer()");
                        Context context2 = tomcat.addWebapp("/sample", new ClassPathResource("file:/home/stan/Downloads/sample.war").getFile().toString());
                        Context context3 = tomcat.addWebapp("/yo", new ClassPathResource("file:/home/stan/Downloads/SampleWebApp.war").getFile().toString());
        //                Context context = tomcat.addWebapp("/what", new ClassPathResource( "file:" + tomcat.getServer().getCatalinaBase() +"/webapps/sample.war" ).getFile().toString() );
    
                        context2.setParentClassLoader(getClass().getClassLoader());
                        context3.setParentClassLoader(getClass().getClassLoader());
    
        //  also works but above seems better
        //                WebappLoader loader2 = new WebappLoader(Thread.currentThread().getContextClassLoader());
        //                WebappLoader loader3 = new WebappLoader(Thread.currentThread().getContextClassLoader());
        //                context2.setLoader(loader2);
        //                context3.setLoader(loader3);
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                    return super.getTomcatWebServer(tomcat);
                }
            };
        }
    }
    

    Gradle:

    apply plugin: 'war'
    
    war {
        enabled = true
    }
    
    . . . .
    dependencies {
        compile("org.springframework.boot:spring-boot-starter:2.1.6.RELEASE")
        compile("org.springframework.boot:spring-boot-starter-web:2.1.6.RELEASE") 
        compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '9.0.21'
        compile("org.springframework.boot:spring-boot-starter-security:2.1.6.RELEASE")
        compile 'org.apache.httpcomponents:httpclient:4.5.7'
        compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.5.6'
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.jcraft:jsch:0.1.55'
    
        testCompile group: 'junit', name: 'junit', version: '4.12'
    }
    
    0 讨论(0)
  • 2020-11-28 08:48

    The accepted answer covers Spring Boot 1.x. The class mentioned is no longer present in Spring Boot 2.x. When using version 2, you need to use a different one:

        @Bean
        @ConditionalOnProperty(name = "external.war.file")
        public TomcatServletWebServerFactory servletContainerFactory(@Value("${external.war.file}") String path,
                                                                     @Value("${external.war.context:}") String contextPath) {
            return new TomcatServletWebServerFactory() {
    
                @Override
                protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
                    new File(tomcat.getServer().getCatalinaBase(), "webapps").mkdirs();
    
                    Context context = tomcat.addWebapp(contextPath, path);
                    context.setParentClassLoader(getClass().getClassLoader());
    
                    return super.getTomcatWebServer(tomcat);
                }
    
            };
        }
    

    Also, Spring boot enbedded Tomcat does not by default contain dependencies for JSPs. If you are using JSPs in your external war, you need to include them.

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
    </dependency>
    

    UPDATE: I've written a more detailed blog post on how to set this up for both Spring Boot 1 and 2.

    0 讨论(0)
提交回复
热议问题