正文
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幾天就能大概搞定(畢竟如果一門技術入門很難的話也幾乎不太可能大眾化的流行起來),而理解一門技術的單位可能就是月、甚至是年了,這需要靜下心來學習和研究。
原文鏈接:https://mp.weixin.qq.com/s/j14hlm5qk2wf0Pi3fNUvFA