为什么整合jsp后必须通过spring-boot:run方式启动?

背景

Spring Boot - 整合Jsp/FreeMarker这篇文章中,我们用了两种启动方式

  1. mvn clean spring-boot:run
  2. main方法启动
    测试发现,通过maven启动能够正常渲染jsp页面,而通过main方法启动无法渲染,本文分析下原因。

分析

我们代码没有调整,只是启动方式不同,那么怀疑是classpath不一致!

  • mvn启动classpath
/Users/wanye/Code/springboot/target/classes//Users/wanye/.m2/repository/ch/qos/logback/logback-classic/1.1.9/logback-classic-1.1.9.jar/Users/wanye/.m2/repository/ch/qos/logback/logback-core/1.1.9/logback-core-1.1.9.jar/Users/wanye/.m2/repository/com/fasterxml/classmate/1.3.3/classmate-1.3.3.jar/Users/wanye/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0.jar/Users/wanye/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.8.6/jackson-core-2.8.6.jar/Users/wanye/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.8.6/jackson-databind-2.8.6.jar/Users/wanye/.m2/repository/javax/servlet/jstl/1.2/jstl-1.2.jar/Users/wanye/.m2/repository/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final.jar/Users/wanye/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/8.5.11/tomcat-embed-core-8.5.11.jar/Users/wanye/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/8.5.11/tomcat-embed-el-8.5.11.jar/Users/wanye/.m2/repository/org/apache/tomcat/embed/tomcat-embed-jasper/8.5.11/tomcat-embed-jasper-8.5.11.jar/Users/wanye/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/8.5.11/tomcat-embed-websocket-8.5.11.jar/Users/wanye/.m2/repository/org/hibernate/hibernate-validator/5.3.4.Final/hibernate-validator-5.3.4.Final.jar/Users/wanye/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar/Users/wanye/.m2/repository/org/slf4j/jcl-over-slf4j/1.7.22/jcl-over-slf4j-1.7.22.jar/Users/wanye/.m2/repository/org/slf4j/jul-to-slf4j/1.7.22/jul-to-slf4j-1.7.22.jar/Users/wanye/.m2/repository/org/slf4j/log4j-over-slf4j/1.7.22/log4j-over-slf4j-1.7.22.jar/Users/wanye/.m2/repository/org/slf4j/slf4j-api/1.7.22/slf4j-api-1.7.22.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/1.5.1.RELEASE/spring-boot-autoconfigure-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot-starter-logging/1.5.1.RELEASE/spring-boot-starter-logging-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/1.5.1.RELEASE/spring-boot-starter-tomcat-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot-starter-web/1.5.1.RELEASE/spring-boot-starter-web-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot-starter/1.5.1.RELEASE/spring-boot-starter-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot/1.5.1.RELEASE/spring-boot-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-aop/4.3.6.RELEASE/spring-aop-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-beans/4.3.6.RELEASE/spring-beans-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-context/4.3.6.RELEASE/spring-context-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-core/4.3.6.RELEASE/spring-core-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-expression/4.3.6.RELEASE/spring-expression-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-web/4.3.6.RELEASE/spring-web-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-webmvc/4.3.6.RELEASE/spring-webmvc-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/yaml/snakeyaml/1.17/snakeyaml-1.17.jar
  • Main启动classpath
/Users/wanye/Code/springboot/target/classes//Users/wanye/.m2/repository/ch/qos/logback/logback-classic/1.1.9/logback-classic-1.1.9.jar/Users/wanye/.m2/repository/ch/qos/logback/logback-core/1.1.9/logback-core-1.1.9.jar/Users/wanye/.m2/repository/com/fasterxml/classmate/1.3.3/classmate-1.3.3.jar/Users/wanye/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0.jar/Users/wanye/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.8.6/jackson-core-2.8.6.jar/Users/wanye/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.8.6/jackson-databind-2.8.6.jar/Users/wanye/.m2/repository/javax/servlet/jstl/1.2/jstl-1.2.jar/Users/wanye/.m2/repository/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final.jar/Users/wanye/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/8.5.11/tomcat-embed-core-8.5.11.jar/Users/wanye/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/8.5.11/tomcat-embed-el-8.5.11.jar/Users/wanye/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/8.5.11/tomcat-embed-websocket-8.5.11.jar/Users/wanye/.m2/repository/org/hibernate/hibernate-validator/5.3.4.Final/hibernate-validator-5.3.4.Final.jar/Users/wanye/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar/Users/wanye/.m2/repository/org/slf4j/jcl-over-slf4j/1.7.22/jcl-over-slf4j-1.7.22.jar/Users/wanye/.m2/repository/org/slf4j/jul-to-slf4j/1.7.22/jul-to-slf4j-1.7.22.jar/Users/wanye/.m2/repository/org/slf4j/log4j-over-slf4j/1.7.22/log4j-over-slf4j-1.7.22.jar/Users/wanye/.m2/repository/org/slf4j/slf4j-api/1.7.22/slf4j-api-1.7.22.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/1.5.1.RELEASE/spring-boot-autoconfigure-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot-starter-logging/1.5.1.RELEASE/spring-boot-starter-logging-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/1.5.1.RELEASE/spring-boot-starter-tomcat-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot-starter-web/1.5.1.RELEASE/spring-boot-starter-web-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot-starter/1.5.1.RELEASE/spring-boot-starter-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/boot/spring-boot/1.5.1.RELEASE/spring-boot-1.5.1.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-aop/4.3.6.RELEASE/spring-aop-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-beans/4.3.6.RELEASE/spring-beans-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-context/4.3.6.RELEASE/spring-context-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-core/4.3.6.RELEASE/spring-core-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-expression/4.3.6.RELEASE/spring-expression-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-web/4.3.6.RELEASE/spring-web-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/springframework/spring-webmvc/4.3.6.RELEASE/spring-webmvc-4.3.6.RELEASE.jar/Users/wanye/.m2/repository/org/yaml/snakeyaml/1.17/snakeyaml-1.17.jar
  • 对比
192:~ wanye$ diff Desktop/mainsort Desktop/mvnsort12a13
> /Users/wanye/.m2/repository/org/apache/tomcat/embed/tomcat-embed-jasper/8.5.11/tomcat-embed-jasper-8.5.11.jar

对比后发现,通过main启动后classpath缺少tomcat-embed-jasper.jar;定位到这里,我们就可以解决这个问题了。

  • 解决方法:去掉将pom.xml中tomcat-embed-jasper依赖<scope>provided</scope>,依赖调整如下
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <!--
    <scope>provided</scope>
    --></dependency>

还原现场,分析Spring Boot启动流程

在IDE里,直接运行的main函数:

@SpringBootApplicationpublic class Start {    public static void main(String[] args) {
        SpringApplication.run(Start.class, args);
    }
}

Embead Tomcat的Servlet加载流程

  1. 判断是否在web环境,略,大家自己查阅相关资料,不是本文重点。
  2. spring boot通过TomcatEmbeddedServletContainerFactory来启动对应的web服务器。
//TomcatEmbeddedServletContainerFactory
    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer(            ServletContextInitializer... initializers) {        Tomcat tomcat = new Tomcat();        File baseDir = (this.baseDirectory != null ? this.baseDirectory
                : createTempDir("tomcat"));        tomcat.setBaseDir(baseDir.getAbsolutePath());        Connector connector = new Connector(this.protocol);        tomcat.getService().addConnector(connector);        customizeConnector(connector);        tomcat.setConnector(connector);        tomcat.getHost().setAutoDeploy(false);        configureEngine(tomcat.getEngine());        for (Connector additionalConnector : this.additionalTomcatConnectors) {            tomcat.getService().addConnector(additionalConnector);
        }        // 初始化上下文,加载Servlet
        prepareContext(tomcat.getHost(), initializers);        return getTomcatEmbeddedServletContainer(tomcat);
    }

初始化Servlet

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    ……    if (isRegisterDefaultServlet()) {
        addDefaultServlet(context);
    }    // 初始化JspServlet
    if (shouldRegisterJspServlet()) {
        addJspServlet(context);
        addJasperInitializer(context);        context.addLifecycleListener(new StoreMergedWebXmlListener());
    }
    ……
}// 判断是否加载jspServletprotected boolean shouldRegisterJspServlet() {    return this.jspServlet != null && this.jspServlet.getRegistered() && ClassUtils            .isPresent(this.jspServlet.getClassName(), getClass().getClassLoader());
}

shouldRegisterJspServlet是重点了,这里ClassUtils.isPresent会去classLoader里面加载jspServlet(org.apache.jasper.servlet.JspServlet),但是classpath里面没有这个类。addJspServlet没有被执行

被吃掉的异常

继续跟进(如果addJspServlet被执行)

jspServlet对象被添加到Tomcat上下文中,并且以jsp扩展名为key放到HashMap中,接下来猜想,当有web请求会通过jsp扩展名去找到jspServlet对象,然后执行jsp。

验证

  • 访问http://localhost:8080/jsp/home,请求首先被tomcat容器拦截到,然后查找适合的servlet来处理(Tomcat本身是Servlet容器,通过servlet来响应请求,Spring也是通过注册servlet到tomcat才能处理请求的,当然Jsp也就是servlet),我们来看下核心类org.apache.catalina.mapper.Mapper
public void map(MessageBytes host, MessageBytes uri, String version,
                MappingData mappingData) throws IOException {    if (host.isNull()) {
        host.getCharChunk().append(defaultHostName);
    }
    host.toChars();
    uri.toChars();        // 通过uri,找servlet映射
    internalMap(host.getCharChunk(), uri.getCharChunk(), version,
            mappingData);
}// 匹配servlet的核心方法private final void internalMapWrapper(ContextVersion contextVersion,
                                      CharChunk path,
                                      MappingData mappingData) throws IOException {      // 很多匹配规则,这我们关注 Extension Match (扩展名)
    // Rule 3 -- Extension Match
    MappedWrapper[] extensionWrappers = contextVersion.extensionWrappers;    if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
        internalMapExtensionWrapper(extensionWrappers, path, mappingData,                true);
    }    // Rule 7 -- Default servlet
    if (mappingData.wrapper == null && !checkJspWelcomeFiles) {        if (contextVersion.defaultWrapper != null) {              // 默认使用org.springframework.web.servlet.DispatcherServlet
            mappingData.wrapper = contextVersion.defaultWrapper.object;
     }}
}

我们的请求path是:/jsp/home,所以没有匹配Rule 3,而是使用默认servlet,那么这个请求被spring的dispatcherServlet接管,然后执行controller

@RequestMapping("/jsp/home")public String home() {    return "home";
}

方法执行完毕,spring会去加载home这个view,由于我们整合了jsp,所以通过配置spring.mvc.view.prefix=/WEB-INF/jsp/ 这个路径下,寻找home.jsp文件,然后将请求转发(这里大家需要了解一下,请求转发和重定向的区别)到/WEB-INF/jsp/home.jsp。

  • 处理jsp请求
    请求(WEB-INF/jsp/home.jsp)被Tomcat再次拦截,匹配Rule 3,通过扩展名jsp,在map中获取到jspServlet对象,给大家截图

总结

简单总结一下,本文阐述的问题并不是日常开发中的主要问题(可能连主要问题都算不上,谁会用main去调试??),但是遇到了就花时间来研究一下,还是有所收获的。

  1. 分析问题思路
  2. Spring Boot 初始化的部分流程
  3. 请求转发和重定向的区别

另外大家注意如果pom文件中<scope>去掉,再正常部署到tomcat容器中,会有jar冲突,建议大家试验过后,修改回去。

最后

如果觉得我的文章对您有用,请点赞、收藏。您的支持将鼓励我继续创作!

为了提高大家学习效果,录制了同步的视频课程,还望大家支持视频课程

时间: 2024-10-14 19:49:54

为什么整合jsp后必须通过spring-boot:run方式启动?的相关文章

涨姿势:Spring Boot 2.x 启动全过程源码分析

上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入口类 SpringApplication 的源码,并知道了其构造原理,这篇我们继续往下面分析其核心 run 方法. [toc] SpringApplication 实例 run 方法运行过程 上面分析了 SpringApplication 实例对象构造方法初始化过程,下面继续来看下这个 SpringApplication 对象的 run 方法的源码和运行流程. public Conf

【spring boot】mybatis启动报错:Consider defining a bean of type &#39;com.newhope.interview.dao.UserMapper&#39; in your configuration.

启动报错: 2018-02-24 22:41:00.442 WARN 2952 --- [ main] ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error c

Spring Boot注解方式集成Mybatis

一.无配置文件注解版 1.pom文件必要jar包的引入 1 <dependency> 2 <groupId>mysql</groupId> 3 <artifactId>mysql-connector-java</artifactId> 4 </dependency> 5 <dependency> 6 <groupId>org.mybatis.spring.boot</groupId> 7 <a

一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式

前言 有时候我们需要在应用启动时执行一些代码片段,这些片段可能是仅仅是为了记录 log,也可能是在启动时检查与安装证书 ,诸如上述业务要求我们可能会经常碰到 Spring Boot 提供了至少 5 种方式用于在应用启动时执行代码.我们应该如何选择?本文将会逐步解释与分析这几种不同方式 CommandLineRunner CommandLineRunner 是一个接口,通过实现它,我们可以在 Spring 应用成功启动之后 执行一些代码片段 @Slf4j @Component @Order(2)

Spring boot mybatis项目启动后一直刷日志的bug修复……

最近接手一个项目,使用的框架是springboot+mybatis: 其中持久层是使用mybatis集成的,sql是配置在mapper.xml文件中: 然后呢,有时候做新功能的时候,往xml文件中增加新的sql逻辑的时候,总会因为疏忽,或者手误 等原因,造成一些错误 具体表现就是:eclipse的控制台一直在快速的刷日志,也没有看到什么报错的东西,就是刷一些加载class文件的内容: 百度之后说是xml配置文件错的,还有一些文章是说要去 源码中的某个抛错的地方增加断点,可是每次加了断点之后 启动

Spring Boot微服务启动脚本

#!/bin/bash#description: starts and stops the boot.sh app_name=$1pid=ps aux | grep java | grep $app_name | grep -v "grep" | awk '{print $2}' usags() {echo "Usags: sh boot.sh [eureka|config|auth|emqtt|crm|upm|uc|upload|gateway|notification|w

Spring Boot web容器启动

一.启动前的准备: 1.SpringApplication构造方法,赋值webApplicationType Debug启动项目后,进入SpringApplication构造函数,里面有个webApplicationType 2.根据classpath下是否存在特定类来决定哪种类型,分别为SERVLET, REACTIVE, NONE deduceFromClasspath方法返回webApplicationType为Servlet 3.然后进入run方法,进入创建应用程序上下文方法create

Spring Boot使用——项目启动自动执行sql脚本

背景 在项目上线前,需要提供一批测试数据到数据库,数据需求是:每次修改缺陷重启项目后,测试数据会初始化成最初的数据 核心思想 在SpringBoot的架构中,DataSourceInitializer类可以在项目启动后初始化数据,我们可以通过自动执行自定义sql脚本初始化数据.通过自定义DataSourceInitializer Bean就可以实现按照业务要求执行特定的脚本. 使用 前提:项目数据源配置完成 方法 通过@Configuration.@Bean和@Value三个注解实现自定义Dat

Spring boot centos部署启动停止脚本

原文地址:http://www.cnblogs.com/skyblog/p/7243979.html 使用脚本启动和关闭服务,centos下的脚本启动和关闭可以如下: start(){ now=`date "+%Y%m%d%H%M%S"` exec java -Xms64m -Xmx256m -jar ./simple-service-0.0.1.jar --server.port=7085 --config.name=pro > logs/simple-service.log