【问题标题】:Spring Boot: How to add another WAR files to the embedded tomcat?Spring Boot:如何向嵌入式 tomcat 添加另一个 WAR 文件?
【发布时间】:2015-10-01 04:58:30
【问题描述】:

Spring Boot 的嵌入式 tomcat 非常方便,无论是开发还是部署。

但是如果应该添加另一个(第 3 方)WAR 文件(例如 GeoServer)怎么办?

也许以下是正常的程序:

  1. 安装普通的 Tomcat 服务器。
  2. 将 Spring Boot 应用程序构建为 WAR 文件,并将其添加到 Tomcat 的 webapps 文件夹中。
  3. 还将另一个(第 3 方)WAR 文件添加到 webapps 文件夹。

但如果可以进行以下配置就好了。

  1. 将 Spring Boot 应用程序构建为独立 Jar,其中包括嵌入式 Tomcat。
  2. 部署 Spring Boot 应用程序 Jar。
  3. 将另一个(第 3 方)WAR 文件添加到嵌入式 Tomcat 可识别的文件夹中。
  4. 使用嵌入式 Tomcat 提供 Spring Boot 应用程序内容和另一个 WAR 的内容。

怎么做?

更新

spring boot应用用fat jar(=executable jar)做的时候,答案里面的代码是不够的。修改后的如下:

@Bean
public EmbeddedServletContainerFactory servletContainerFactory() {
    return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            try {
                Context context = tomcat.addWebapp("/foo", "/path/to/foo.war");
                WebappLoader loader =
                    new WebappLoader(Thread.currentThread().getContextClassLoader());
                context.setLoader(loader);
            } catch (ServletException ex) {
                throw new IllegalStateException("Failed to add webapp", ex);
            }
            return super.getTomcatEmbeddedServletContainer(tomcat);
        }

    };
}

由于系统类加载器无法加载 fat jar 中的 jar 文件,因此必须指定显式父类加载器。否则,附加的 WAR 无法加载添加了 WAR 的 spring boot 应用的 fat jar 中的库 jar。

【问题讨论】:

  • 阅读标签说明。 “嵌入”不是“嵌入”!
  • 我目前正在尝试做同样的事情 (see here),但由于缺少文件依赖项,我遇到了大量异常。知道我在这里错过了什么吗?

标签: spring tomcat spring-boot


【解决方案1】:

您可以使用Tomcat.addWebapp 将war 文件添加到嵌入式Tomcat。正如其 javadoc 所说,它“相当于将 Web 应用程序添加到 Tomcat 的 Web 应用程序目录”。要在 Spring Boot 中使用该 API,需要使用自定义的 TomcatEmbeddedServletContainerFactory 子类:

@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);
        }

    };
}

【讨论】:

  • 谢谢!我已成功将 WAR 与您的代码集成。 (出现了一些警告,但它特定于我添加的 WAR,我想它可以解决)。
  • 一个小问题:application.properties中没有指定server.tomcat.basedir时,WAR扩展失败。原因如下:如果未指定该变量,Spring 会创建一个临时目录并将其分配为 baseDir。但该目录没有“wepapps”子目录,嵌入式 tomcat 在其中尝试 mkdir() WAR 扩展文件的子目录。
  • spring boot应用用fat jar做成的时候,上面的代码是不够的。请查看我的更新。
  • 为了修复webapps dir不存在导致的失败,我还覆盖了TomcatEmbeddedServletContainerFactory中的createTempDir并添加了自己创建webapps目录
  • 在 Spring Boot 2 中,TomcatEmbeddedServletContainerFactory 已被替换:stackoverflow.com/questions/47700115/…
【解决方案2】:

Spring Boot 2 花了一些时间才弄清楚这一点,因为没有一个答案对我完全有效。我终于想出了这个(仅供参考,我已经打开了 SSL):WarRun.java,下面带有 Gradle 依赖项以使其工作。

它给出了什么:

https://localhost:8070处嵌入了带有上下文路径/的tomcat

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

https://localhost:8070/yo 上的 SampleWebApp.war

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);
            }
        };
    }
}

分级:

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'
}

【讨论】:

    【解决方案3】:

    接受的答案涵盖 Spring Boot 1.x。提到的类在 Spring Boot 2.x 中不再存在。使用版本 2 时,您需要使用不同的版本:

        @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);
                }
    
            };
        }
    

    此外,默认情况下,嵌入 Spring Boot 的 Tomcat 不包含 JSP 的依赖项。如果您在外部战争中使用 JSP,则需要包含它们。

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

    更新:我写了a more detailed blog post on how to set this up for both Spring Boot 1 and 2

    【讨论】:

      猜你喜欢
      • 2016-07-22
      • 2014-11-17
      • 2019-09-30
      • 1970-01-01
      • 2018-02-19
      • 2017-10-08
      • 2018-05-28
      • 2021-08-31
      相关资源
      最近更新 更多