【方向盘】版本历史&代码示例之:Servelt、JSP、EL表达式

2022-11-03 16:02:25 浏览数 (1)

工具好,也要用得巧。 本文已被https://yourbatman.cn收录;女娲Knife-Initializr工程可公开访问啦;程序员专用网盘https://wangpan.yourbatman.cn;

你好,我是方向盘(YourBatman、方哥)。笔者的公号(Java方向盘)是保留地,只分享原创,不转载、不发商务广告!!!

✍前言

若你还不太清楚Java EE是什么,可先移步这里:什么是Java EE?

最近一段时间,将Java EE/Jakarta EE进行了科普,almost也许可能大概是全网最全的,颇受好评。本专栏全部内容可点击上方专栏标题查看,这里做简单陈列:

  • 【方同学】Java EE几十种技术,“活着的”还剩几何(Web应用技术篇)
  • 【方同学】Java EE几十种技术,“活着的”还剩几何(企业应用技术篇)
  • 【方同学】Java EE几十种技术,“活着的”还剩几何(服务/安全/Java SE篇)

本以为科普到这就差不多了告一段落的,直到有小伙伴私信我说:看了这么多但依旧不知道Java EE如何使用?当头棒喝,作为一名Javaer,其实天天都在使用着Java EE。只是在Spring框架的襁褓之下,它优秀的抽象能力甚至让开发者感知不到底层技术的存在。

本着管生管养,管杀管埋的初心,决定再续写几篇,针对每一项(主流)Java技术给出示例,主要包括这几个方面:

  • 版本历史
  • 生存现状
  • 实现(框架)
  • 代码示例
所属专栏
  • BATutopia-Java EE
相关下载
  • 工程源代码:https://github.com/yourbatman/FXP-java-ee
  • 【女娲Knife-Initializr工程】访问地址:http://152.136.106.14:8761
  • Java开发软件包(Mac):https://wangpan.yourbatman.cn/s/rEH0 提取码:javakit
  • 程序员专用网盘上线啦,开放注册送1G超小容量,帮你实践做减法:https://wangpan.yourbatman.cn
版本约定
  • Java EE:6、7、8
  • Jakarta EE:8、9、9.1

✍正文

Servlet

Servlet是一种基于Java的动态Web资源动态Web资源技术,类似的技术还有ASP、PHP等。

代码语言:javascript复制
<!-- javax命名空间版本(Tomcat 9.x及以下版本支持) -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

<!-- jakarta命名空间版本(Tomcat 10.x及以上版本支持) -->
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>5.0.0</version>
    <!-- <version>4.0.4</version> 此版本命名空间同javax -->
    <scope>provided</scope>
</dependency>

✌版本历史

Servlet规范由Sun Microsystems公司创建,1.0版于1997年6月完成。从2.3版开始,该规范是在JCP下开发。

版本

发布日期

隶属于

JSR版本

焦点说明

1.0

1997.06

-

-

首个版本,由Sun公司发布

2.0

1997.08

-

-

2.1

1998.11

-

-

新增了RequestDispatcher, ServletContext等

2.2

1999.08

J2EE 1.2

-

成为J2EE的一部分。在.war文件中引入了self-contained Web applications的概念

2.3

2001.08

J2EE 1.3

JSR 53

增加了Filter,增加了关于Session的Listener(如HttpSessionListener)

2.4

2003.08

J2EE 1.4

JSR 154

没增加大的新内容,对不严格的地方加了些校验,如:对web.xml使用XML Schema

2.5

2005.09

Java EE 5

JSR 154

最低要求JDK 5。注解支持(如@WebService、@WebMethod等,注意不是@WebServlet这种哦)

3.0

2009.12

Java EE 6

JSR 315

史上最大变革。动态链接库和插件能力(Spring MVC利用此能力通过ServletContainerInitializer进行全注解驱动开发)、模块化开发、异步Servlet、安全性、新的文件上传API、支持WebSocket,新的注解(@WebServlet、@WebFilter、@WebListener),可脱离web.xml全注解驱动,此版本功能已经很完整了,应用的主流

3.1

2013.5

Java EE 7

JSR 340

新增非阻塞式IO。Spring的Web Flux若要运行在Servlet容器,至少需要此版本,因为从此版本起才有非阻断输入输出的支持

4.0

2017.09

Java EE 8

JSR 369

支持Http/2。从而支持服务器推技术,新的映射发现接口HttpServletMapping可用来提高内部的运行效率

5.0

2020.11

Jakarta EE 9

JSR 369

同Servlet 4.0(只是命名空间从javax.*变为了jakarta.*而已)

Spring Boot相关

  • 2.0.0.RELEASE版本(2018.05):正式内置Servlet 3.1,毕竟Spring Web Flux从此版本开始(Spring 5)
  • 2.1.0.RELEASE版本(2018.10):升级到Servlet 4.x,直到现在(2.6.x)也依旧是4.x版本
  • 2.2.0.RELEASE版本(2019.10):开始支持jakarta.servlet这个GAV,(和javax.servlet)二者并行
  • 2.5.0/2.6.0版本(2021.05):无变化
  • 3.0.0版本(预计2022.12):基于Spring 6.x、Jakarta EE 9,基于GraalVM全面拥抱云原生的新一代框架

说明:Spring Boot 2.6和2.7都还会基于Spring Framework 5.3.x内核。Spring Framework 6.0版本在2021年9月正式拉开序幕,将基于全新的Jakarta EE 9(命名空间为jakarta.*,不向下兼容)平台开发,相应的Spring Boot 3也会基于此内核

✌生存现状

随着Spring 5的发布推出WebFlux,Servlet技术从之前的必选项变为可选项

但考虑到业务开发使用WebFlux收益甚微但开发调试成本均增加,因此实际情况是基于Servlet的Spring MVC技术依旧是主流,暂时地位不可撼动,依旧非常活跃

✌实现(框架)

由于Servlet由Web容器负责创建并调用,因此只要实现了Servlet规范的Web容器均可作为它的实现(框架),如Tomcat、Jetty、Undertow、JBoss、Glassfish等。

✌代码示例

导入依赖包:

  1. scope一般provided即可,因为Web容器里会自带此Jar
  2. Spring Boot场景下无需显示导入,因为Tomcat已内嵌(相关API)
代码语言:javascript复制
servlet-api的GAV

继承HttpServlet写一个用于处理Http请求的Servlet处理器

代码语言:javascript复制
/**
 * 在此处添加备注信息
 *
 * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a>
 * @site https://yourbatman.cn
 * @date 2021/9/12 06:23
 * @since 0.0.1
 */
@WebServlet(urlPatterns = {"/hello"})
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello servlet...");
    }
}

IDEA添加(外置)Tomcat 9.x版本,以war包形式部署到Tomcat(小提示:<packaging>war</packaging>),并启动Tomcat

浏览器http://localhost:8080/hello即可完成正常访问。

说明:自Servlet 3.0之后,web.xml部署描述符并非必须(全注解即可搞定)

工程源代码:https://github.com/yourbatman/FXP-java-ee

JSP

Java Server Page的简称。那么,有了Servlet为何还需要JSP?其实它俩都属于动态Web技术,只是Servlet它用于输出页面简直太繁琐了(每一句html都需要用resp.getWriter()逐字逐句的输出),所以才出现了JSP技术来弥补其不足。

它使用JSP标签在HTML网页中插入Java代码。语法格式为:<% Java代码 %>。它有九大内置对象这么一说:

代码语言:javascript复制
1、request:请求对象。javax.servlet.http.HttpServletRequest
2、response:响应对象。javax.servlet.http.HttpServletResponse
3、session:会话对象。javax.servlet.http.HttpSession
4、application:应用程序对象。javax.servlet.ServletContext
5、config:配置对象。javax.servlet.ServletConfig
6、page:页面对象。当前jsp程序本身,相当于this
7、pageContext:页面上下文对象。javax.servlet.jsp.PageContext
8、out:输出流对象,用于输出内容到浏览器。javax.servlet.jsp.jspWriter
9、exception:异常对象,只有在包含isErrorPage=”true”的页面中才可以被使用。java.lang.Throwable

除了Servlet。与JSP 强相关 的技术还有EL表达式和JSP标签(JSTL),下面会接着介绍。

代码语言:javascript复制
<!-- javax命名空间版本(Tomcat 9.x及以下版本支持) -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    <scope>provided</scope>
</dependency>

<!-- jakarta命名空间版本(Tomcat 10.x及以上版本支持) -->
<dependency>
    <groupId>jakarta.servlet.jsp</groupId>
    <artifactId>jakarta.servlet.jsp-api</artifactId>
    <version>3.0.0</version>
    <!-- <version>2.3.6</version> 此版本命名空间同javax -->
    <scope>provided</scope>
</dependency>

✌版本历史

由于JSP的本质就是Servlet,它的的版本号需要与Servlet对应看待。

版本

发布日期

JSR版本

对应Servlet版本

JSP 1.1

2000.07

JSR 906

Servlet 2.2

JSP 1.2

2002.06

JSR 53

Servlet 2.3

JSP 2.0

2003.11

JSR 152

Servlet 2.4

JSP 2.1

2005.09

JSR 245

Servlet 2.5

JSP 2.2

2009.12

JSR 245(升级版)

Servlet 3.0

JSP 2.3

2013.05

JSR 372(升级版)

Servlet 3.1

JSP 3.0

2020.11

----(Jakarta旗下)

Servlet 5.x

Spring Boot相关:Spring Boot从1.x版本开始就一直没有“带”JSP一起玩,若要Spring Boot支持JSP需要特殊开启。

JSP 2.0是个重要版本,最重要的特性就是开始支持EL表达式了,可以用它来访问应用程序数据。JSP 2.3版本可断定是最后一个版本,因为JSP已走到尽头,成为历史。

✌生存现状

JSP诞生之后,程序员写页面写得确实很爽了。但是,它带来了坏处:很多程序员同学将业务逻辑、页面展示逻辑都往JSP塞,耦合在一起,导致JSP扛不住了,更重要的是程序员扛不住了,非常凌乱。

虽然后面出现了EL表达式和JSTL标签来帮助程序员不要在JSP里写Java代码,但只要不是强制的你能限制住自由的程序员么?然后呢,后来出现了Freemarker和Velocity这种模板引擎,使得程序员没有办法在页面上写Java代码了,达到了分离的效果。

模板引擎出现后,JSP的地位已经岌岌可危了。但真正杀死它的还是前端的崛起,从而进入前后端完全分离的状态,至此基本可以宣布JSP(甚至包括模板引擎)的死亡。

所以JSP目前的生存状态是:基本死亡状态。你看,这不Spring Boot(默认)都不带他玩了嘛~

✌实现(框架)

与Servlet相同的Web容器。

✌代码示例

导包。由于我们不可能直接使用JSP的API,因此99.9999%情况下无需导包。

代码语言:javascript复制
无需导包

创建webapp内容文件夹。这点很重要,因为是要创建一个web文件夹,以IDEA为例:在jsp-demo工程下添加web模块

完成后工程目录结构如下:

值得一提的是:web目录名称叫什么无所谓(只是很多喜欢叫webapp、webroot等),重要的是要有这个小圆点。不乏听见不少小伙伴说这个目录名必须叫webapp,其实它名字叫什么、甚至位置放在哪都无所谓,重要是找得到就行。掌握原理,一通百通。

这里附上HelloJsp的内容:

代码语言:javascript复制
/**
 * 在此处添加备注信息
 *
 * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a>
 * @site https://yourbatman.cn
 * @date 2021/9/12 06:26
 * @since 0.0.1
 */
@WebServlet(urlPatterns = {"/hellojsp"})
public class HelloJsp extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RequestDispatcher requestDispatcher = request.getRequestDispatcher("hello.jsp");
        // 放在WBE-INF下面的.jsp页面必须通过Servlet转发才能访问到,更加安全
        // RequestDispatcher requestDispatcher = request.getRequestDispatcher("/WEB-INF/hello.jsp");
        requestDispatcher.forward(request, response);
    }
}

以war包形式部署至Tomcat

浏览器访问下面两个路径均可得到响应结果:

  • http://localhost:8080/hellojsp:请求 -> Servlet转发 -> jsp页面(即使jsp页面放到WEB-INF目录下依旧可访问)
  • http://localhost:8080/hello.jsp:请求 -> jsp页面(此直接方式只能访问非WEB-INF目录下的jsp文件)

页面响应:

再强调一遍:自Servlet 3.0之后,web.xml部署描述符并非必须。即使有jsp页面也是一样~~~

工程源代码:https://github.com/yourbatman/FXP-java-ee

EL表达式

Expression Language表达式语言。EL表达式语言的灵感来自于ECMAScriptXPath表达式语言(表达式语言当然还有比较著名的Spring的SpEL,以及OGNL),它提供了在 JSP 中简化表达式的方法,目的是替代掉在Jsp里写Java代码,让Jsp的代码更加简化。

基本语法为:${EL表达式 },只能读取数据不能设置数据(设置数据用JSP内或者Servlet里的Java代码均可)

请务必注意,基本语法中右边的}的前面有个空格,使用时请务必注意

在EL中有四大域对象11大内置对象这么一说:

  • 请求参数
代码语言:javascript复制
1、param 包含所有的参数的Map,可以获取参数返回String。其底层实际调用request.getParameter()
	- name=${param.name }
2、paramValues 包含所有参数的Map,可以获取参数的数组返回String[]。其底层实际调用request.getParameterValues()
	- hobby[0]=${paramValues.hobby[0] }
  • 头信息
代码语言:javascript复制
3、header 包含所有的头信息的Map,可以获取头信息返回String。
	- ${header.Connection }
4、headerValues 包含所有的头信息的Map,可以获取头信息数组返回String[]。
	- ${headerValues["user-agent"][0] }
  • Cookie
代码语言:javascript复制
5、cookie包含所有cookie的Map,key为Cookie的name属性值
	- ${cookie.JSESSIONID.name }
  • 初始化参数
代码语言:javascript复制
6、iniParam 包含所有的初始化参数(一般配在web.xml里)的Map,可以获取初始化的参数
	- ${initParam.username} ${initParam.password}
  • 四大作用域(重点)
代码语言:javascript复制
7、pageScope 包含page作用域内的Map
	- ${pageScope.name }
8、requestScope 包含request作用域内的Map
	- ${requestScope.name }
9、 包含session作用域内的Map
	- ${sessionScope.name }
10、applicationScope 包含application作用域内的Map
	- ${applicationScope.name }
  • 页面上下文
代码语言:javascript复制
11、pageContext 包含页面内的变量的Map,可获取JSP中的九大内置对象
	- ${pageContext.request.scheme }
	- ${pageContext.session.id}
代码语言:javascript复制
<!-- javax命名空间版本(Tomcat 9.x及以下版本支持) -->
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>

<!-- jakarta命名空间版本(Tomcat 10.x及以上版本支持) -->
<dependency>
    <groupId>jakarta.el</groupId>
    <artifactId>jakarta.el-api</artifactId>
    <version>4.0.0</version>
    <!-- <version>3.0.3</version> 此版本命名空间同javax -->
</dependency>

除此之外,还可以通过Tomcat的GAV直接导入,版本号同Tomcat
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-el-api</artifactId>
    <version>Tomcat版本号</version> <!-- 9.x版本是javax.*,10.x以及后面是jakarta.* -->
</dependency>
嵌入式Tomcat提供的实现
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-el</artifactId>
    <version>Tomcat版本号</version> <!-- 9.x版本是javax.*,10.x以及后面是jakarta.* -->
</dependency>

另外,还有二合一的GAV:3.x版本的API和impl实现都在一个jar里。
4.x使用jakarta.*命名空间,并且API分离(依赖于)jakarta.el-api
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <version>4.0.2</version>
    <!-- <version>3.0.3</version> 此版本命名空间同javax -->
</dependency>

值得注意的是,EL并非Web独享而是可独立使用,因此它的scope用默认的即可。另外,这只是API,并非Impl实现,是不能直接运行的,否则会遇到类似如下异常:

代码语言:javascript复制
Caused by: javax.el.ELException: Provider com.sun.el.ExpressionFactoryImpl not found
	at javax.el.FactoryFinder.newInstance(FactoryFinder.java:101)
	...

✌版本历史

EL从JSP 2.0版本开始引入,用于在JSP页面获取数据的简单方式。因此它是随着JSP的发展而出现的,只是可独立使用而已。

版本

发布日期

JSR版本

对应JSP版本

对应Servlet版本

EL 2.0

2003.11

JSR 152

JSP 2.0

Servlet 2.4

EL 2.2

2009.12

JSR 245

JSP 2.2

Servlet 2.5

EL 3.0

2013.05

JSR 341

JSP 2.3

Servlet 3.1

EL 4.0

2020.10

纳入Jakarta

JSP 3.0

Servlet 5.0

EL表达式3.0于2013年4月份发布(可认为是最后一次功能升级),它的新特性包括:字符串拼接操作符、赋值(以前只能读取,现在可以赋值啦)、分号操作符、对象方法调用(以前只能用JavaBean属性导航)、Lambda表达式、静态字段/方法调用、构造器调用、Java8集合操作。具体就不一一举例了,详细情况可阅读我收录的JSR文档。

✌生存现状

随着JSP的消亡,EL的存在感越来越弱。

好在它可以作为单独的表达式语言使用,有Hibernate Validator对它是强依赖,所以生命力还行。但由于Hibernate Validator里使用得简单,所以EL并没有必要再更新(动力不足)。

✌实现(框架)

EL大部分情况下伴随着JSP一起使用,所以交由Web容器去解析实现。

另外,EL作为一种表达式语言,也可以作为”工具“供以使用,比如著名的Hibernate Validator内部就依赖于EL表达式语言来书写校验规则(所以它在编译期就强依赖于EL的API)。

✌代码示例

在JSP中使用EL是由org.apache.tomcat:tomcat-jasper-el或者org.apache.tomcat.embed:tomcat-embed-jasper完成和JSP的整合,以及解析支持的。在JSP页面里使用方式由于已经过时(主要是使用示例一搜一大把),这里为了节约篇幅,就略了哈。

如果把EL当做工具使用的话(比如Hibernate Validator用来错误消息里插值用),需要了解一些API和常见用法,演示一下:

导包:

代码语言:javascript复制
上面的GAV随便选一个(记得太impl实现,推荐org.glassfish:jakarta.el)

直接使用API书写Demo

代码语言:javascript复制
/**
 * 在此处添加备注信息
 *
 * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a>
 * @site https://yourbatman.cn
 * @date 2021/9/12 10:12
 * @since 0.0.1
 */
public class ElDemo {

    public static void main(String[] args) {
        ExpressionFactory factory = ELManager.getExpressionFactory();
        StandardELContext elContext = new StandardELContext(factory);

        // 将instance转为对应类型
        ValueExpression valueExpression = factory.createValueExpression("18", Integer.class);
        System.out.println(valueExpression.getValue(elContext));

        // 计算表达式的值
        valueExpression = factory.createValueExpression(elContext, "${1 1}", Integer.class);
        System.out.println(valueExpression.getValue(elContext));

        // 方法调用
        // MethodExpression methodExpression = factory.createMethodExpression(elContext, "${Math.addExact()}", Integer.class, new Class[]{Integer.class, Integer.class});
        // System.out.println(methodExpression.invoke(elContext, new Object[]{1, 2}));
    }

}

运行,结果输出:
18
2

工程源代码:https://github.com/yourbatman/FXP-java-ee

✍总结

现在越来越卷的IT行业,衡量一个求职者的专业能力,深度往往比广度更为重要

正所谓这辈子听过很多大道理,却依旧过不好这一生;技术也一样,听过/知道过/使用过很多技术,但依旧写不出好的代码。究其原因,就是理解不深刻。

自上而下的用,自底向上的学,这是我个人一直秉承的一个观念。知道一门技术、使用一门技术一般几个小时or几天就能大概搞定(毕竟如果一门技术入门很难的话也几乎不太可能大众化的流行起来),而理解一门技术的单位可能就是月、甚至是年了,这需要静下心来学习和研究。

本专栏文章

  • 【方同学】Java EE几十种技术,“活着的”还剩几何(Web应用技术篇)
  • 【方同学】Java EE几十种技术,“活着的”还剩几何(企业应用技术篇)
  • 【方同学】Java EE几十种技术,“活着的”还剩几何(服务/安全/Java SE篇)

我是方向盘(YourBatman):前25年不会写Hallo World、早已毕业的大龄程序员。高中时期《梦幻西游》骨灰玩家,网瘾失足、清考、延期毕业、房产中介、保险销售、送外卖…是我不可抹灭的黑标签