资讯专栏INFORMATION COLUMN

Spring Boot整合jsp后必须通过spring-boot:run方式启动?

roundstones / 789人阅读

摘要:为什么整合后必须通过方式启动背景在整合这篇文章中,我们用了两种启动方式方法启动测试发现,通过启动能够正常渲染页面,而通过方法启动无法渲染,本文分析下原因。通过来启动对应的服务器。

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

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

mvn clean spring-boot:run

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/mvnsort
12a13
> /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依赖provided,依赖调整如下


    org.apache.tomcat.embed
    tomcat-embed-jasper
    
还原现场,分析Spring Boot启动流程

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

@SpringBootApplication
public class Start {
    public static void main(String[] args) {
        SpringApplication.run(Start.class, args);
    }
}
Embead Tomcat的Servlet加载流程

判断是否在web环境,略,大家自己查阅相关资料,不是本文重点。

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());
    }
    ……
}
// 判断是否加载jspServlet
protected 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去调试??),但是遇到了就花时间来研究一下,还是有所收获的。

分析问题思路

Spring Boot 初始化的部分流程

请求转发和重定向的区别

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

最后

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70110.html

相关文章

  • Spring Boot - 整合Jsp/FreeMarker

    摘要:大家自己了解一下的使用方法,我这里就不进行详细的讲述了。启动方式两种方式都可以主函数启动或者验证访问页面,验证是否输出了当前时间。为了提高大家学习效果,录制了同步的视频课程,还望大家支持视频课程 Spring Boot - 初识 Hello World 索引 Spring Boot - 初识 Hello World Spring Boot - Servlet、过滤器、监听器、拦截器 ...

    AbnerMing 评论0 收藏0
  • Spring Boot - 单元测试(Junit4&Mockito)

    摘要:当面讲给你听讲堂地址,或许是最实用的教程,新课促销中,只要你敢来,保你收货满满。优惠报名全程撸码快速入门教程全原价,优惠价全程撸码进阶全原价,优惠价 回顾 Spring Boot - 初识 Hello World Spring Boot - Servlet、过滤器、监听器、拦截器 Spring Boot - 静态资源处理、启动加载、日志处理 Spring Boot - 部署Deplo...

    raoyi 评论0 收藏0
  • Spring Boot - 自定义启动banner

    摘要:背景这段时间较忙,有些想念小红,为了表达我对小红的思念之情,决定将启动的研究一下,看看是否能够自定义,让我天天能够看到她。 背景 这段时间较忙,有些想念小红,为了表达我对小红的思念之情,决定将spring boot启动的banner研究一下,看看是否能够自定义,让我天天能够看到她。 展示 经过调研,发现自定义banner是一个轻松愉快的过程,忍不住让我多启动几次,先看看效果:(省略了一...

    CollinPeng 评论0 收藏0
  • Spring Boot - 表单校验(JSR303&Hibernate Validator)

    摘要:初步使用主要使用注解的方式对进行校验,第一个例子在需要校验的字段上指定约束条件然后在中可以这样调用,加上注解即可。如果校验失败,默认会返回框架的出错信息。指定到的分组名会全部进行校验,不指定的不校验。 Spring Boot - 表单校验(JSR303&Hibernate Validator) 回顾 Spring Boot - 初识 Hello World Spring Boot -...

    tinyq 评论0 收藏0
  • Spring-Boot学习笔记

    摘要:学习笔记使用很容易创建一个独立运行运行内嵌容器准生产级别的基于框架的项目,使用你可以不用或者只需要很少的配置。异常消息如果这个错误是由异常引起的。错误发生时请求的路径。 Spring-Boot 1.5 学习笔记 使用Spring Boot很容易创建一个独立运行(运行jar,内嵌Servlet容器)、准生产级别的基于Spring框架的项目,使用Spring Boot你可以不用或者只需要很...

    curlyCheng 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<