Spring Boot整合jsp後必須經過spring-boot:run方式啓動?

爲何整合jsp後必須經過spring-boot:run方式啓動?

背景

Spring Boot - 整合Jsp/FreeMarker這篇文章中,咱們用了兩種啓動方式java

  1. mvn clean spring-boot:run
  2. main方法啓動
    測試發現,經過maven啓動可以正常渲染jsp頁面,而經過main方法啓動沒法渲染,本文分析下緣由。

分析

咱們代碼沒有調整,只是啓動方式不一樣,那麼懷疑是classpath不一致!web

  • 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;定位到這裏,咱們就能夠解決這個問題了。spring

  • 解決方法:去掉將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函數:express

@SpringBootApplication
public 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());
    }
    ……
}
// 判斷是否加載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沒有被執行apache

被吃掉的異常

圖片描述

繼續跟進(若是addJspServlet被執行)

圖片描述

jspServlet對象被添加到Tomcat上下文中,而且以jsp擴展名爲key放到HashMap中,接下來猜測,當有web請求會經過jsp擴展名去找到jspServlet對象,而後執行jsp。segmentfault

驗證

  • 訪問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接管,而後執行controllerapi

@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。tomcat

  • 處理jsp請求
    請求(WEB-INF/jsp/home.jsp)被Tomcat再次攔截,匹配Rule 3,經過擴展名jsp,在map中獲取到jspServlet對象,給你們截圖

圖片描述

總結

簡單總結一下,本文闡述的問題並非平常開發中的主要問題(可能連主要問題都算不上,誰會用main去調試??),可是遇到了就花時間來研究一下,仍是有所收穫的。springboot

  1. 分析問題思路
  2. Spring Boot 初始化的部分流程
  3. 請求轉發和重定向的區別

另外你們注意若是pom文件中<scope>去掉,再正常部署到tomcat容器中,會有jar衝突,建議你們試驗事後,修改回去。服務器

最後

若是以爲個人文章對您有用,請點贊、收藏。您的支持將鼓勵我繼續創做!

爲了提升你們學習效果,錄製了同步的視頻課程,還望你們支持視頻課程

圖片描述

相關文章
相關標籤/搜索