JSP 筆記
很久以前的筆記了,翻出來整理下。
有用的資源
在Tomcat的webapps/examples/ 目錄下有很多實用詳細的jsp代碼例子。
JSP的本質
首先,JSP本質上是一個Servlet,jsp編繹器會把jsp文件編繹成一個對應的java類,而這個java類是實際上是一個servlet,從繼承層次就可以看出來。
比如home.jsp,在tomcat下會生成一個home_jsp的類:
class home_jsp extends org.apache.jasper.runtime.HttpJspBase
class HttpJspBase extends javax.servlet.http.HttpServlet
Tomcat7生成為jsp對應的java代碼在work目錄下。如果是在eclipse裏跑的tomcat,那麼在eclipse的workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp1/work/Catalina/ 目錄下。
一些JSP指令實際生成的代碼
jsp:include指令:
<jsp:include page="xxx.jsp"> <jsp:param name="age" value="20"/> </jsp:include>實際生成的Java代碼:
org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "xxx.jsp" + (("xxx.jsp").indexOf('?')>0? '&': '?') + org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("age", request.getCharacterEncoding())+ "=" + org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("20", request.getCharacterEncoding()), out, false);可以看出jsp:include指令和include編譯指令是完全不同的,jsp:include是每次都動態加載,而include編繹指令是把兩個jsp文件合起來,編繹成一個servlet。
jsp:useBean,jsp:setProperty,jsp:getProperty 三個指令的本質:
<jsp:useBean scope="application"></jsp:useBean> <jsp:setProperty name="s1" property="name" value="hello"/>實際生成的Java代碼:
pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); application = pageContext.getServletContext(); synchronized (application) { s1 = (com.test.Student) _jspx_page_context.getAttribute("s1", PageContext.APPLICATION_SCOPE); if (s1 == null){ s1 = new com.test.Student(); _jspx_page_context.setAttribute("s1", s1, PageContext.APPLICATION_SCOPE); } } org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper(_jspx_page_context.findAttribute("s1"), "name", "hello", null, null, false);
可見jsp:useBean實際上得到一個對象,並調用setAttribute函數設置為scope的一個屬性。比如scope="application",則實際和下麵的代碼差不多:
com.test.Student s1 = application.getAttribute("s1"); synchronized (application) { if(s1 == null){ s1 = new com.test.Student(); application.setAttribute("s1", s1); } }
值得注意的是PageContext,即application對象在setAttribute時,要加上sync同步,因為servlet不是線程安全的。但是實際上有很多人手寫的代碼都沒有注意到這點。
Tomcat7中session的實現
Tomcat7中session是通過cookie來實現的。實際上隻有當調用request.getSession()函數時,才會設置cookie來支持session。如:
HttpSession session = req.getSession(); resp.getWriter().println("session id:" + session.getId());
當設置瀏覽器禁止cookie時,可以發現每一次請求,打印的session id都是不一樣的,即都會有一個新的cookie值。並且這時如果調用session.setAttribute函數設置一些屬性,則會發現屬性設置是無效的。
實際上通過查看Tomcat7的源代碼可以發現,是用一個HashMap來保存session id和session的關係。如果請求中沒有session id,則當調用getSession()函數時,會生成一個新的id和一個新的session。
Tomcat7源代碼中相關部分如下:
//ManagerBase.java protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>(); //Request.java protected Session doGetSession(boolean create) { // There cannot be a session if no context has been assigned yet if (context == null) { return (null); } // Return the current session if it exists and is valid if ((session != null) && !session.isValid()) { session = null; } if (session != null) { return (session); } // Return the requested session if it exists and is valid Manager manager = null; if (context != null) { manager = context.getManager(); } if (manager == null) { return (null); // Sessions are not supported } if (requestedSessionId != null) { try { session = manager.findSession(requestedSessionId); } catch (IOException e) { session = null; } if ((session != null) && !session.isValid()) { session = null; } if (session != null) { session.access(); return (session); } } // Create a new session if requested and the response is not committed if (!create) { return (null); } if ((context != null) && (response != null) && context.getServletContext().getEffectiveSessionTrackingModes(). contains(SessionTrackingMode.COOKIE) && response.getResponse().isCommitted()) { throw new IllegalStateException (sm.getString("coyoteRequest.sessionCreateCommitted")); } // Attempt to reuse session id if one was submitted in a cookie // Do not reuse the session id if it is from a URL, to prevent possible // phishing attacks // Use the SSL session ID if one is present. if (("/".equals(context.getSessionCookiePath()) && isRequestedSessionIdFromCookie()) || requestedSessionSSL ) { session = manager.createSession(getRequestedSessionId()); } else { session = manager.createSession(null); } // Creating a new session cookie based on that session if ((session != null) && (getContext() != null) && getContext().getServletContext(). getEffectiveSessionTrackingModes().contains( SessionTrackingMode.COOKIE)) { Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie( context, session.getIdInternal(), isSecure()); response.addSessionCookieInternal(cookie); } if (session == null) { return null; } session.access(); return session; }
Tomcat7的生成session id的算法
另外,還有一點很有意思的地方,Tomcat7的生成session id的算法:
可以看到裏麵的隨機數用了SecureRandom。這個很多人都沒有注意到這個,實際上很多安全相關的隨機數生成器應該用SecureRandom,如果用Random,有可能會遭到攻擊。
private void getRandomBytes(byte bytes[]) { SecureRandom random = randoms.poll(); if (random == null) { random = createSecureRandom(); } random.nextBytes(bytes); randoms.add(random); } /** * Generate and return a new session identifier. */ public String generateSessionId() { byte random[] = new byte[16]; // Render the result as a String of hexadecimal digits StringBuilder buffer = new StringBuilder(); int resultLenBytes = 0; while (resultLenBytes < sessionIdLength) { getRandomBytes(random); for (int j = 0; j < random.length && resultLenBytes < sessionIdLength; j++) { byte b1 = (byte) ((random[j] & 0xf0) >> 4); byte b2 = (byte) (random[j] & 0x0f); if (b1 < 10) buffer.append((char) ('0' + b1)); else buffer.append((char) ('A' + (b1 - 10))); if (b2 < 10) buffer.append((char) ('0' + b2)); else buffer.append((char) ('A' + (b2 - 10))); resultLenBytes++; } } if (jvmRoute != null && jvmRoute.length() > 0) { buffer.append('.').append(jvmRoute); } return buffer.toString(); }
最後更新:2017-04-03 14:54:18