正文
Servlet
Servlet是一种基于Java的动态Web资源动态Web资源技术,类似的技术还有ASP、PHP等。
- <!-- 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等。
代码示例
导入依赖包:
scope一般provided即可,因为Web容器里会自带此Jar
Spring Boot场景下无需显示导入,因为Tomcat已内嵌(相关API)
- servlet-api的GAV
继承HttpServlet写一个用于处理Http请求的Servlet处理器
- /**
- * 在此处添加备注信息
- *
- * @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(小提示:
浏览器http://localhost:8080/hello即可完成正常访问。
说明:自Servlet 3.0之后,web.xml部署描述符并非必须(全注解即可搞定)
工程源代码:https://github.com/yourbatman/BATutopia-java-ee
JSP
Java Server Page的简称。那么,有了Servlet为何还需要JSP?其实它俩都属于动态Web技术,只是Servlet它用于输出页面简直太繁琐了(每一句html都需要用resp.getWriter()逐字逐句的输出),所以才出现了JSP技术来弥补其不足。
它使用JSP标签在HTML网页中插入Java代码。语法格式为:<% Java代码 %>。它有九大内置对象这么一说:
- 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),下面会接着介绍。
- <!-- 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%情况下无需导包。
- 无需导包
创建webapp内容文件夹。这点很重要,因为是要创建一个web文件夹,以IDEA为例:在jsp-demo工程下添加web模块图片图片完成后工程目录结构如下:
完成后工程目录结构如下:
值得一提的是:web目录名称叫什么无所谓(只是很多喜欢叫webapp、webroot等),重要的是要有这个小圆点。不乏听见不少小伙伴说这个目录名必须叫webapp,其实它名字叫什么、甚至位置放在哪都无所谓,重要是找得到就行。掌握原理,一通百通。
这里附上HelloJsp的内容:
- /**
- * 在此处添加备注信息
- *
- * @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/BATutopia-java-ee
EL表达式
Expression Language表达式语言。EL表达式语言的灵感来自于ECMAScript和XPath表达式语言(表达式语言当然还有比较著名的Spring的SpEL,以及OGNL),它提供了在 JSP 中简化表达式的方法,目的是替代掉在Jsp里写Java代码,让Jsp的代码更加简化。
基本语法为:${EL表达式 },只能读取数据不能设置数据(设置数据用JSP内或者Servlet里的Java代码均可)
请务必注意,基本语法中右边的}的前面有个空格,使用时请务必注意
在EL中有四大域对象和11大内置对象这么一说:
- 请求参数
- 1、param 包含所有的参数的Map,可以获取参数返回String。其底层实际调用request.getParameter()
- - name=${param.name }
- 2、paramValues 包含所有参数的Map,可以获取参数的数组返回String[]。其底层实际调用request.getParameterValues()
- - hobby[0]=${paramValues.hobby[0] }
- 头信息
- 3、header 包含所有的头信息的Map,可以获取头信息返回String。
- - ${header.Connection }
- 4、headerValues 包含所有的头信息的Map,可以获取头信息数组返回String[]。
- - ${headerValues["user-agent"][0] }
- Cookie
- 5、cookie包含所有cookie的Map,key为Cookie的name属性值
- - ${cookie.JSESSIONID.name }
- 初始化参数
- 6、iniParam 包含所有的初始化参数(一般配在web.xml里)的Map,可以获取初始化的参数
- - ${initParam.username} ${initParam.password}
- 四大作用域(重点)
- 7、pageScope 包含page作用域内的Map
- - ${pageScope.name }
- 8、requestScope 包含request作用域内的Map
- - ${requestScope.name }
- 9、 包含session作用域内的Map
- - ${sessionScope.name }
- 10、applicationScope 包含application作用域内的Map
- - ${applicationScope.name }
- 页面上下文
- 11、pageContext 包含页面内的变量的Map,可获取JSP中的九大内置对象
- - ${pageContext.request.scheme }
- - ${pageContext.session.id}
- <!-- 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实现,是不能直接运行的,否则会遇到类似如下异常:
- 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和常见用法,演示一下:
导包:
- 上面的GAV随便选一个(记得太impl实现,推荐org.glassfish:jakarta.el)
直接使用API书写Demo
- /**
- * 在此处添加备注信息
- *
- * @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/BATutopia-java-ee
总结
现在越来越卷的IT行业,衡量一个求职者的专业能力,深度往往比广度更为重要。
正所谓这辈子听过很多大道理,却依旧过不好这一生;技术也一样,听过/知道过/使用过很多技术,但依旧写不出好的代码。究其原因,就是理解不深刻。
自上而下的用,自底向上的学,这是我个人一直秉承的一个观念。知道一门技术、使用一门技术一般几个小时or几天就能大概搞定(毕竟如果一门技术入门很难的话也几乎不太可能大众化的流行起来),而理解一门技术的单位可能就是月、甚至是年了,这需要静下心来学习和研究。