SpringBoot 使用外部Tomcat方法及启动原理

SpringBoot外置Tomcat
本文介绍如何在SpringBoot项目中使用外部Tomcat服务器,并详细解释了启动原理。包括配置war包、调整Tomcat作用域及自定义启动类等步骤。

SpringBoot 使用外部Tomcat方法及启动原理

基于 SpringBoot 2.x

方法

  • 一、必须是一个war项目,利用IDEA可以直接创建,或者是修改pom.xml文件
<packaging>war</packaging>
  • 二、将内置Tomcat的作用范围修改成provided
dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-tomcat</artifactId>
   <scope>provided</scope>
</dependency>
  • 三、自定义一个类继承 SpringBootServletInitializer 重写其configure()方法
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 传入SpringBoot的主程序类
        return builder.sources(SbdemoApplication.class);
    }
}

启动原理

前提

  • 根据Servlet3.0规范,服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例。
  • ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名。
  • 使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类。

SpringBoot中启动流程

  • 一、SpringBoot 中的 ServletContainerInitializer 实现类位置在spring-web模块下
    这里写图片描述
    文件内容:
org.springframework.web.SpringServletContainerInitializer
  • 二、SpringServletContainerInitializer类
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        // 将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例。
                        initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                // 每一个WebApplicationInitializer都调用自己的onStartup()
                initializer.onStartup(servletContext);
            }

        }
    }
}
  • 三、Tomcat 启动引导类
// ServletInitializer 继承 SpringBootServletInitializer
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(SbdemoApplication.class);
    }
}

SpringBootServletInitializer 继承 WebApplicationInitializer 类,就是SpringServletContainerInitializer类的@HandlesTypes({WebApplicationInitializer.class}), 下面展示的是 onStartup() 、createRootApplicationContext() 和 configure() 方法

public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
    public void onStartup(ServletContext servletContext) throws ServletException {
        this.logger = LogFactory.getLog(this.getClass());
        // 创建web应用容器
        WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
        if (rootAppContext != null) {
            servletContext.addListener(new ContextLoaderListener(rootAppContext) {
                public void contextInitialized(ServletContextEvent event) {
                }
            });
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }

    }

    protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
        // 1、创建SpringApplicationBuilder
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        StandardServletEnvironment environment = new StandardServletEnvironment();
        // 2、准备环境
        environment.initPropertySources(servletContext, (ServletConfig)null);
        builder.environment(environment);
        builder.main(this.getClass());
        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
            builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
        }
        // 3、初始化
        builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
        builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
        // 4、调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
        builder = this.configure(builder);

        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }

        Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }
        // 5、启动Spring应用
        return this.run(application);
    }
}

// 此方法被启动引导类 ServletInitializer有方法重写, 传入的是应用构建器SpringApplicationBuilder, 也就是SpringBoot的主程序类
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder;
}

个人随笔,如有错误,欢迎指正!

<think>好的,我现在需要帮助用户了解如何使用Tomcat启动Java Spring Boot项目。首先,我需要回忆Spring Boot和Tomcat之间的关系。根据引用[1],Spring Boot默认会内嵌Tomcat,但用户可能想用外部Tomcat实例来部署。这时候需要将项目打包成WAR文件,而不是默认的JAR。 接下来,我应该按照步骤来指导用户。第一步是修改打包方式,在pom.xml中将packaging标签改为war。然后需要排除内嵌的Tomcat依赖,添加spring-boot-starter-tomcat的provided范围,这样打包时就不会包含内嵌的Tomcat了。 接下来,用户需要修改启动类,继承SpringBootServletInitializer并重写configure方法。这一步是为了让Tomcat能够正确初始化Spring Boot应用。引用[3]提到Tomcat通过SPI机制加载Spring Boot的相关类,所以这一步是关键。 然后,打包成WAR文件,使用mvn clean package命令。生成WAR文件后,将其部署到Tomcat的webapps目录下,启动Tomcat即可。引用[2]和[3]都提到了这些步骤,特别是外部Tomcat部署的流程。 可能遇到的问题需要提醒用户,比如端口冲突,或者上下文路径的配置。比如,在application.properties中设置server.port可能不会生效,因为外部Tomcat使用自己的配置,这时候需要修改Tomcat的server.xml或者使用管理界面调整。 另外,用户可能需要了解内嵌Tomcat外部Tomcat的区别,以及为什么有时候选择外部部署。引用[1]和[4]提到Spring Boot自动创建嵌入式Tomcat的情况,而外部部署适用于需要统一管理多个应用的情况,或者需要特定Tomcat配置的场景。 还需要检查用户是否已经正确配置了Servlet API的依赖,确保provided范围正确,避免打包时包含不必要的库,导致冲突。同时,确保项目结构符合WAR标准,比如WEB-INF目录的位置。 最后,生成相关问题,比如性能优化、配置调整、日志查看等,帮助用户进一步深入。确保回答的结构清晰,步骤明确,用户能够一步步跟随操作,避免遗漏关键点。</think>### 如何使用Tomcat启动Java Spring Boot项目? #### 一、项目打包方式调整 1. **修改`pom.xml`文件** 将打包方式从默认的JAR改为WAR: ```xml <packaging>war</packaging> ``` 添加Tomcat依赖(标记为`provided`,避免与外部Tomcat冲突): ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> ``` 2. **修改启动类** 继承`SpringBootServletInitializer`并重写`configure`方法,以支持外部Tomcat启动: ```java @SpringBootApplication public class Application extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(Application.class); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` #### 二、构建与部署 1. **生成WAR包** 执行Maven命令: ```bash mvn clean package ``` 生成的WAR文件位于`target`目录下(例如:`myapp.war`)。 2. **部署到Tomcat** - 将WAR文件复制到Tomcat的`webapps`目录 - 启动Tomcat(执行`bin/startup.sh`或`bin/startup.bat`) - 访问应用:`http://localhost:8080/myapp` #### 三、关键配置说明 - **端口冲突处理** 外部Tomcat默认使用`8080`端口,若需修改,编辑Tomcat的`conf/server.xml`: ```xml <Connector port="8081" protocol="HTTP/1.1" ... /> ``` - **上下文路径配置** 在`application.properties`中设置: ```properties server.servlet.context-path=/myapp ``` 但实际路径由Tomcat决定(WAR文件名即默认上下文路径)[^2][^3]。 #### 四、原理说明 - **嵌入式Tomcat vs 外部Tomcat** Spring Boot默认内嵌Tomcat启动时通过`TomcatServletWebServerFactory`创建实例[^1][^4]。 外部部署时,Tomcat通过`ServletContainerInitializer`加载Spring Boot上下文[^3]。 - **类加载机制** Tomcat利用JDK SPI机制发现`SpringServletContainerInitializer`,进而初始化Spring应用[^3]。 #### 五、常见问题 1. **日志文件位置** Tomcat日志位于`logs/catalina.out`,Spring Boot日志需配置独立输出路径。 2. **静态资源加载失败** 确保资源文件位于`src/main/webapp`目录而非`static`或`templates`。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值