開心一刻css
有個同窗去非洲援建,剛到工地接待他的施工員是個黑人,他就用英語跟人家交流,黑人沒作聲。 而後他又用法語,黑人仍是沒說話。 而後他用手去比劃。黑人終於開口了:瞎比劃嘎哈,整個工地都中國人html
在利用maven/eclipse搭建ssm(spring+spring mvc+mybatis)一文的問題反饋中,大致分兩個:404和頁面無數據;至於500,我的認爲比較好解決,按照提示進行處理就好,本文就不討論500了前端
主要也是兩種java
一、webapp未發佈git
相關資源未部署,例如webapp未發佈部署,相似以下web
不僅是webapp,main下的java、resources、webapp,maven依賴都是須要部署到tomcat,否則就不完整,就會存在各類各樣的少內容的問題;spring
二、請求URL不對數據庫
這個確實是不少新入行的小夥伴容易出現的問題express
若是工程正常部署,請求URL出現404,頗有多是咱們請求的URL不對;咱們到tomcat的home目錄下看看工程是否正常部署,相似以下apache
還能夠看看工程發佈的內容(問題1中須要發佈的內容)是否都在;若是工程部署正常,而請求的URL又出現404,那不用想,就是你的URL寫錯了
404的解決方案就是:確認工程是否正確部署到tomcat,確認請求的URL是否正確,基本只要確認這兩點也就能找到問題了;後文不會再詳細的講404,咱們將重點放到下面這個問題上
具體的問題應該是這樣的:當咱們請求:http://localhost:端口/工程名/personController/showPerson時,數據正常顯示以下
當咱們直接請求jsp時,只有title沒有數據,以下
這是爲何?
對於這個問題一開始確實沒太在乎,只是提示小夥伴去看servlet的四大做用域和jsp的九大內置對象,後面陸陸續續不少小夥伴都問了我,包括評論區留言、站內消息、QQ私聊等
站內信
評論區
我發現這個問題好像不是個別小夥伴的問題,不少新入門的小夥伴都存在這樣的疑問,下面咱們就對這次問題就行一個詳細的探究;後續篇幅較長,基礎鋪墊較多,但願你們耐心看完!
狹義上來說,servlet指的就是接口:javax.servlet.Servlet,廣義上來說,servlet指的是servlet規範:Java Servlet API 標準;javax.servlet.Servlet與servlet容器都是servlet規範下的產物。Java Servlet API是Servlet容器和Servlet之間的接口,它定義了Servlet的各類方法,還定義了Servlet容器傳送給Servlet的對象類,其中最重要的是請求對象ServletRequest和響應對象ServletResponseo這兩個對象都是由Servlet容器在客戶端調用Servlet時產生的,Servlet容器把客戶請求信息封裝在ServletRequest對象中,而後把這兩個對象都傳送給要調用的Servlet,Servlet處理完後把響應結果寫入ServletResponse,而後由Servlet容器把響應結果發送到客戶端。
Servlet與Servlet容器的關係有點像槍和子彈的關係,槍是爲子彈而生,而子彈又讓槍有了殺傷力。雖然它們是彼此依存的,可是又相互獨立發展,這一切都是爲了適應工業化生產的結果。從技術角度來講是爲了解耦,經過標準化接口來相互協做。Servlet 容器做爲一個獨立發展的標準化產品,目前它的種類不少,包括Jetty、tomcat、resin、JBoss、WebSphere、Weblogic等,這些都是成熟的產品,有專門的公司或者組織進行維護,咱們直接拿來用就好。
咱們約定下,下文中的servet指的都是servlet接口:javax.servlet.Servlet,servlet容器指的是:Tomcat,Web服務器與Servlet容器是同一個內容(實際是有區別的,具體區別你們自行去查閱)
Tomcat容器模型以下
Tomcat響應客戶請求過程
其中,①處表示Web服務器接收到客戶端發出的HTTP請求後,轉發給Servlet容器,再由Servlet容器轉發給具體的Servlet實例進行請求的處理;②處表示Servlet實例將處理結果封裝進ServletResponse中,再由Servlet容器把ServletResponse發給Web服務器,通知Web服務器以HTTP響應的方式把結果發送到客戶端。也就是說,與客戶端直接打交道的是tomcat(servlet容器),而不是咱們的Servlet實例,而真正處理請求的纔是咱們的Servlet實例。
說的簡單點,咱們自定義的Servlet,實際上是對servlet容器在業務層面的拓展,至關於業務定製同樣;咱們能夠這樣理解,servlet容器對servlet提供技術支持,而servlet對servlet容器提供業務拓展,二者缺一不可,缺了技術支持,業務拓展實施不起來,缺了業務拓展,技術支持沒有現實意義。Servlet容器封裝了底層複雜的技術實現,使咱們能夠專一於業務實現,而Servlet容器與業務實現之間的紐帶就是Servlet接口,它是咱們對Servlet容器進行業務拓展的標準,因此咱們的業務須要實現Servlet接口。套用阿基米德的槓桿原理:給Servlet容器多個servlet實例,Servlet容器還你豐富的web服務。
示例代碼:our-servlet
咱們先來看看在jsp出現以前,servlet如何輸出頁面,HelloServlet以下
package com.lee.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String businessDate = "業務數據...."; resp.setCharacterEncoding("utf-8"); resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.write("<html>"); out.write("<head>"); out.write("<title>Hello World</title>"); out.write("</head>"); out.write("<body>"); out.write("<h1>Hello World!</h1>"); out.write("<div><span><strong>"); out.write(businessDate); out.write("</strong></span></div>"); out.write("</body>"); out.write("</html>"); out.flush(); out.close(); } }
不只僅是業務數據,還包括靜態頁面的內容,統統在servlet返回,若是頁面簡單,這麼處理也能接受,可是若是頁面像淘寶、京東那樣很是複雜,你能想象嗎?太容易出錯了,一旦靜態頁面的元素少了或者多了內容,都不知道如何排查,面對茫茫多的out.write,就只有哭的份了。因此jsp就應運而生了。
JSP全稱:Java Server Pages,容許在傳統靜態網頁HTML中插入Java代碼片斷(Scriptlet)和JSP標籤,以簡化頁面靜態內容的開發。但須要注意的是,JSP文件的本質仍是Servlet,只不過與Servlet不一樣的是,JSP是專門用於進行數據展現的Servlet;JSP最終會被Tomcat解析成Servlet,在Tomcat內置了一個JSP解析引擎,當第一次訪問該JSP頁面時,解析引擎會將JSP頁面解析成Servlet,而後再由Servlet將動態數據、靜態內容所有輸出到瀏覽器供展現。咱們來看看jsp解析後的文件在哪裏、內容是什麼,以示例中的index.jsp爲例。路徑以下圖
index_jsp.java
/* * Generated by the Jasper component of Apache Tomcat * Version: Apache Tomcat/7.0.47 * Generated at: 2019-04-08 13:14:31 UTC * Note: The last modified time of this file was set to * the last modified time of the source file after * generation to assist with modification tracking. */ package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private javax.el.ExpressionFactory _el_expressionfactory; private org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public void _jspInit() { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); } public void _jspDestroy() { } public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null; final javax.servlet.ServletContext application; final javax.servlet.ServletConfig config; javax.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; javax.servlet.jsp.JspWriter _jspx_out = null; javax.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html; charset=utf-8"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write("\n"); out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"); out.write("<html>\n"); out.write("<head>\n"); out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); out.write("<title>index</title>\n"); out.write("</head>\n"); out.write("<body>\n"); out.write(" <div>\n"); out.write(" Welcome, my friend!\n"); out.write(" </div>\n"); out.write("</body>\n"); out.write("</html>"); } catch (java.lang.Throwable t) { if (!(t instanceof javax.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { out.clearBuffer(); } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } }
發現熟悉的out.write又回來了,只是此時的out.write不是咱們手動寫的,而是Tomcat解析jsp後生成的;若是jsp沒變更,jsp只會在第一次被調用時解析、編譯一次,後續的請求都會由編譯後的servlet處理,咱們來驗證下,如何驗證了? 不變index.jsp內容再請求index.jsp,看看上圖中文件的修改時間會不會變
發現文件的修改時間沒有變更,也就是說上面的的結論:若是jsp沒變更,jsp只會在第一次被調用時解析、編譯一次是對的。感興趣的朋友能夠去看下Tomcat的源碼,看看具體的實現細節。
有人可能會問:爲何不將jsp的內容直接返回給瀏覽器?咱們要明白一點:瀏覽器只能解析html、css、js,除此以外的內容它解析不了,那麼咱們能直接將jsp的內容返回給瀏覽器嗎?因此中間有處理過程,最終由servlet將靜態內容返回給瀏覽器。有些愛問的小夥伴可能又會問了:瀏覽器爲何只能解析:html、css、js,這涉及到瀏覽器規範的問題,除非你有能力改變這個規範,讓瀏覽器支持你想要的內容,這個問題不作過深的討論,咱們姑且認爲這是瀏覽器的限制,既然咱們改變不了這個限制,那就適應這個限制。
Servlet四大做用域包括:page域、request域、session域、application域,做用域指的是變量的有效期限,具體以下
當變量的做用域是page,它的有效範圍只在當前jsp頁面裏有效;
當變量的做用域是request,它的有效範圍是當前請求週期,所謂請求週期,就是指從http請求發起,到服務器處理結束,返回響應的整個過程,在這個過程當中可能使用forward的方式跳轉了多個jsp頁面,在這些頁面裏你均可以使用這個變量;
當變量的做用域是session,它的有效範圍是當前會話,何爲當前會話,就是指從用戶打開瀏覽器開始,到用戶關閉瀏覽器的整個過程,這個過程可能包含多個請求響應;
當變量的做用域是application,它的有效範圍是整個應用,何爲整個應用,就是指從應用啓動,到應用結束;
JSP九大內置對象包括:page、request 、response、pageContext、session、application、out、config、exception,內置對象指的是Servlet容器建立的一組對象,不需使用new關鍵字就能夠直接使用的內置對象。
四大做用域與九大內置對象對應關係以下
更多詳情須要你們本身去查閱資料了
咱們知道jsp中能夠插入Java代碼片斷,相似以下
<%pageContext.setAttribute("sex", "男"); %> <!-- 設置值,做用域是當前jsp頁面 -->
<div>
<%=pageContext.getAttribute("sex") %> <!-- 注意去看解析後的el_jsp.java,被解析成了out.print(pageContext.getAttribute("sex") ); -->
Welcome, my friend!
</div>
其中<% %>包裹的就是java片斷,<%= %>輸出表達式值到頁面;能夠看到不夠簡潔,閱讀性也不太友好,因此EL表達式就應運而生了,上述代碼能夠替換成以下代碼
<%pageContext.setAttribute("sex", "男"); %> <!-- 設置值,做用域是當前jsp頁面 -->
<div>
${sex} Welcome, my friend! <!-- ${expression} EL的語法結構 -->
</div>
EL可以訪問頁面的上下文以及不一樣做用域中的對象 ,取得對象屬性的值,或執行簡單的運算或判斷操做,用來簡化JSP中的java代碼。EL表達式是JSP1.2以後內置支持的,能夠直接在JSP中使用,它從servlet四大做用域(範圍servletContext > session > request > pageContext)中取值,這四個域都有setAttribute("",object)方法和getAttribute("")方法, EL表達式會自動按做用範圍從小到大的順序從四大做用域中尋找對應名字的值,找到了就當即返回再也不繼續尋找,其內部調用的就是pageContext的findAttribute("")方法。
EL當然能簡化JSP中的java代碼,可是它功能很是簡單,不能知足一些複雜的代碼邏輯,因此就誕生了JSTL。JSP標準標籤庫(JSTL)是一個JSP標籤集合,它封裝了JSP應用的通用核心功能,支持通用的、結構化的任務,好比迭代,條件判斷,XML文檔操做,國際化標籤,SQL標籤,另外還支持自定義標籤,它實現了JSP頁面中的代碼複用、簡化了代碼的書寫,同時也保證了JSP的可讀性更強。JSTL功能比較豐富,但它不是JSP內置支持的,因此須要導入標籤庫到JSP頁面(還要添加jstl的jar包依賴)。JSTL每每會集合EL表達式來使用,簡單示例以下
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!-- 引入JSTL標籤庫,c表示標籤庫別名,能夠任意命名,通常而言用c --> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>index</title> </head> <body> <c:set var="sex" value="女" scope="page"></c:set> <!-- scope指定做用域,page/request/session/application --> <c:if test="${sex == '男' }"> Hello, ${sex} </c:if> <c:if test="${sex != '男' }"> Hi, girl </c:if> </body> </html>
這代碼看起來就清爽多了,沒有java代碼,前端開發者也很容易看懂;關於EL表達式與JSTL標籤更詳細信息,須要你們自行去查閱資料了,本文篇幅有限,不作過多的講解了。
那麼可想而知,重定向的request做用域的變量是會失效的,而轉發則不會
還記得咱們是如何配置Spring MVC的嗎, 咱們會在web.xml中配置以下代碼
<servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
這樣就配置了Spring MVC;你們能夠留意下DispatcherServlet,去看他的類圖會發現,他就是一個Servlet的實現,也就是說Sprinv MVC就是基於Servlet的拓展。
咱們在Spring MVC基礎上進行開發的時候,將數據綁定到做用域的時候,通常用的是SpringMVC的數據模型:Model或者ModelMap,例如這樣
@RequestMapping("/showPerson") public String showPersons(Model model){ List<Person> persons = personService.loadPersons(); model.addAttribute("persons", persons); // 綁定數據到視圖 return "showperson"; }
而不是顯示的直接綁定到Servlet四大做用域,數據難道沒有綁定到四大做用域? 咱們說過,EL表達式只能在四大做用域中取值,不然取不到,因此SpringMVC中的數據綁定最終仍是會到四大做用域的某一箇中,至因而什麼時候、何地、如何將Model中的屬性綁定到哪一個做用域,這個不是本文要說的了,篇幅太大了,有興趣的能夠去看看這篇博客:springmvc的工做原理,咱們來看看其源碼實現。這裏給個結論:在默認狀況下,Model中的屬性做用域是request級別。
有些小夥伴會抱怨了:上面嗶嗶了那麼多,怎麼就是不講答案,淨說一些沒用的
若是你們堅持看到這了,再堅持會,答案立刻揭曉,上面鋪墊了那麼多,絕對是有用的。
咱們回到問題:當咱們請求http://localhost:端口/工程名/personController/showPerson時,數據正常顯示,而當咱們直接請求jsp時,只有title卻沒有數據,這是爲何?title是靜態頁面內容,這個不用管,那爲何直接請求jsp爲何沒有數據庫的person列表呢? jsp源代碼以下
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>person list</title>
</head>
<body>
<table>
<tr>
<th>姓名</th>
<th>年齡</th>
</tr>
<c:forEach items="${persons}" var="person">
<tr>
<td>${person.name }</td>
<td>${person.age }</td>
</tr>
</c:forEach>
</table>
</body>
</html>
裏面用到了<c:forEach>和EL表達式,解釋下這個流程:EL表達式先從四大做用域獲取名爲persons的集合,而後<c:forEach>遍歷該集合,每次遍歷的結果放到page做用域,並取名叫person,最後經過EL表達式輸出person的name和age到頁面。那麼請問:直接訪問JSP,四大做用域中有名叫persons的屬性嗎?很顯然沒有,persons不存在,遍歷它會有結果輸出嗎?這就是爲何直接訪問jsp沒有數據的答案。
咱們再回到Controller層
@RequestMapping("/showPerson") public String showPersons(Model model){ List<Person> persons = personService.loadPersons(); // 從數據獲取person列表,並存放到了persons集合中 model.addAttribute("persons", persons); // 將persons集合添加到model的persons屬性中 return "showperson"; // 轉發到showperson.jsp }
代碼也很是簡單,先從數據庫獲取person集合,而後將該集合設置到了model的屬性persons中,咱們知道model的屬性默認狀況下會設置到request做用域;而後將請求轉發到showperson.jsp,轉發過程當中,request做用域的變量仍然有效,因此jsp中EL表達式可以讀取到persons變量,因此就有數據輸出到頁面了。
一、Servlet與Servlet容器的關係比較曖昧,二者相互做用,實現web服務;簡單點說,咱們自定義的Servlet就是對Servlet容器的業務拓展,而Servlet容器是對Servlet的支撐;
二、JSP的出現時爲了簡化靜態頁面的開發,EL表達式與JSTL的出現則是爲了簡化JSP頁面的Java代碼;JSP本質仍是Servlet,在第一次被訪問的時候會被Servlet容器解析成Servlet、編譯Servlet,最終仍是有Servlet將頁面內容out.write到瀏覽器;
三、Spring MVC本質仍是Servlet,它的出現是爲了簡化web開發,同時能夠與spring無縫對接,享受spring帶來的好處;Spring MVC的數據綁定,依託的仍是Servlet的的四大做用域,只是中間存在轉換過程;
四、EL表達式的取值必須存在於四大做用域中,在jsp中用EL表達式時,必定要保證數據正確地添加到了四大做用域中,否則,EL表達式會取不到值;
《深刻分析JavaWeb技術內幕》
《Tomcat 系統架構與模式設計分析》