关于 JSTL 依赖问题

起因

事情起因是因为在开发中,使用 Tomcat 9 以及引入相关JSTL依赖时,系统一直提示如下信息:

18-Jun-2020 13:32:07.855 严重 [http-nio-8080-exec-6] org.apache.catalina.core.ApplicationDispatcher.invoke Servlet[jsp]的Servlet.service()抛出异常
    org.apache.jasper.JasperException: 无法在web.xml或使用此应用程序部署的jar文件中解析绝对uri:[http://java.sun.com/jsp/jstl/core]
        at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:55)
        at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:294)
        at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:81)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)
18-Jun-2020 13:32:07.856 严重 [http-nio-8080-exec-6] org.apache.catalina.core.StandardWrapperValve.invoke 在路径为/demo02_war_exploded的上下文中,Servlet[jsp]的Servlet.service()引发了具有根本原因的异常无法在web.xml或使用此应用程序部署的jar文件中解析绝对uri:[http://java.sun.com/jsp/jstl/core]
    org.apache.jasper.JasperException: 无法在web.xml或使用此应用程序部署的jar文件中解析绝对uri:[http://java.sun.com/jsp/jstl/core]
        at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:55)
        at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:294)
        at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:81)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)

无法在web.xml或使用此应用程序部署的jar文件中解析绝对uri;这个问题搞得作者很头疼。所以查阅了很多资料去解决这个问题。大量资料都没有只是简单说没有引入jstl.jar包,然而我在pom.xml中引入了包,仍然出现了这个问题。后来切换不同的tomcat版本时,才发现了问题的根本原因,是因为没有手动定位标签库(实际上不是的,因为我通过下面的方式排查后,最终定位到原因,预知后事,继续看吧)?!

所以专门提一个章节将他记录在小本本上。

在maven引入相关依赖时,依赖可谓五花八门,种类繁多。首先是JSP、Servlet、JSTL以及EL;

JSP(Java Server Page)是一种动态网页技术,是由Sun公司提供的一种动态网页技术标准[1]

Servlet(Server Applet)是Java编写的服务器端程序。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者[2]

EL(Expression Language)是Java中一种特殊的通用变成语言。主要作用是在Java Web应用程序嵌入到网页(如JSP)中,用以访问页面的上下文以及不同作用域中的对象 ,取得对象属性的值,或执行简单的运算或判断操作。EL在得到某个数据时,会自动进行数据类型的转换[3]

JSTL(JSP Standard Tag Library)是Java EE网络应用程序开发平台的组成部分。它在JSP规范的基础上,扩充了一个JSP的标签库来完成一些通用任务,比如XML数据处理、条件执行、数据库访问、循环和国际化[4]

一个容器如果实现了JavaEE的全部标准,也称之为JavaEE的容器。比如,tomcat实现了Servlet规范,可以称之为Servlet容器。

这里先提供一个tomcat的版本关系。

Servlet规格 JSP规范 WebSocket规范 认证(JASIC)规范 Apache Tomcat版本 最新发行版本 支持的Java版本

Servlet标准

Java首先提供了Servlet的标准,那么其他的开发者可以基于Java的标准,实现自己不同的JAR包。那么这个标准包就是。

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

Apache Tomcat提供了一个对应Tomcat版本的实现包。

<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-servlet-api -->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-servlet-api</artifactId>
    <version>9.0.36</version>
</dependency>

因为Tomcat的servlet-api中已经实现了标准,所以我们只需要在maven的pom.xml中直接添加下面的内容即可。所以可以使用ApacheTomcat所提供的servlet-api版本

<properties>
    <tomcat.version>9.0.36</tomcat.version>
</properties>
<dependencies>
    <!-- 引入JSP以及Servlet API依赖包 -->
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-servlet-api</artifactId>
        <version>${tomcat.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

其次Servlet3.0.1以前的写法是:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <scope>provided</scope>
    <version>2.5</version>
</dependency>

3.0.1以后的写法,如下:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <scope>provided</scope>
    <version>3.1.0</version>
</dependency>

当然我们也可以只引入标准包,但是使用标准的时候,我们需要和tomcat的版本相对应,否则将出现兼容的问题。

JSP标准

JSP与Servlet类似,同样Java提供了JSP的标准包,如下所示:

<!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    <scope>provided</scope>
</dependency>

当然也有Tomcat的实现版本,如下所示:

<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-jsp-api -->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jsp-api</artifactId>
    <version>9.0.36</version>
</dependency>

其次maven中加入版本控制

<properties>
    <tomcat.version>9.0.36</tomcat.version>
</properties>
<dependencies>
    <!-- 引入JSP以及Servlet API依赖包 -->
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-jsp-api</artifactId>
        <scope>provided</scope>
        <version>${tomcat.version}</version>
    </dependency>
</dependencies>

JSTL标准

引入无标准包只有实现的方式

在进行JSTL相关开发时,我们需要引入两个包:jstl包以及standard标准包。因为JSTL早在2011年也就停止维护了,所以其标准包的引入如下,需要排除掉servlet-api以及jsp-api:

<dependency>
    <groupId>javax.servlet.jsp.jstl</groupId>
    <artifactId>jstl-api</artifactId>
    <version>1.2</version>
    <exclusions>
        <exclusion>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>

这个标准包则没有依赖standard标准包,如下所示:

<!-- https://mvnrepository.com/artifact/taglibs/standard -->
<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
</dependency>

其完整形式,应该是这样的:

<dependencies>
    <dependency>
      <groupId>javax.servlet.jsp.jstl</groupId>
      <artifactId>jstl-api</artifactId>
      <version>1.2</version>
      <exclusions>
        <exclusion>
          <groupId>javax.servlet</groupId>
          <artifactId>servlet-api</artifactId>
        </exclusion>
        <exclusion>
          <groupId>javax.servlet.jsp</groupId>
          <artifactId>jsp-api</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <!-- https://mvnrepository.com/artifact/taglibs/standard -->
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
</dependencies>

引入既有标准也有实现的方式

下面三种包:javax.servlet.jstljavax.servlet.jsp.jstl.jstl以及jstl.jstl都是包含标准包的依赖:

<!-- 这些写法虽然还存在于mvnrepository.com,但是对应的jar文件已经在maven的公共库中不存在了。 -->
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl -->
<dependency>
  <groupId>javax.servlet.jsp.jstl</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>
<!-- 下面这两种写法都是就是包括了标准,也包括了实现 -->
<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/jstl/jstl -->
<dependency>
  <groupId>jstl</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>

下面配置一个截图:

上面三种都提供了实现以及标准包。所以只用引入一个包即可。比如下面这种方式:

<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>

或者下面这种方式都可以:

<!-- https://mvnrepository.com/artifact/jstl/jstl -->
<dependency>
  <groupId>jstl</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>

上面两种安装依赖的方式并不是特别推荐哈。因为容易产生依赖包冲突(如果需要安装可以排除下servlet-api以及jsp-api这两个包文件)。这样不用再去安装标准包,也不用在/WEB-INF/目录下添加任何的tld显示申明文件(显示申明标准库也不推荐)。

EL的标准和实现

直接使用tomcat的就可以了,感觉这样安全写吧(指兼容性的问题),如下所示:

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-el-api</artifactId>
    <version>9.0.36</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jasper-el</artifactId>
    <version>9.0.36</version>
</dependency>

如果使用tomcat作为容器,其已经集成了EL,而在JSP构建阶段是不会被编译的,所以使用tomcat时,可以不用加入EL表达式的依赖。

如果按照上述的依赖选择,仍然出现了《起因》中的问题,则需要检查下tomcat的配置文件catalina.properties下面的tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\是否修改为了tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar;如果是,则改回原来的配置即可。

因为改了这项后,将不会扫描一些必要jar包了。同样将导致上述的问题。

作者的根本原因也就是这个造成的。

  • 《菜鸟教程——JSP 标准标签库(JSTL)》
  • 《J2EE 全面简介》,作者:刘湛,发布时间:2001 年 7 月 07 日 《How to install JSTL? The absolute uri: http://java.sun.com/jstl/core cannot be resolved
    以及stackoverflow中JSTL TAGLIB说明文档。