<?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <servlet> <servlet-name>AllInOneServlet</servlet-name> <servlet-class>com.cdai.web.j2ee.AllInOneServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AllInOneServlet</servlet-name> <url-pattern>/j2ee</url-pattern> </servlet-mapping> <!-- Servlet mappings END --> </web-app>
package com.cdai.web.j2ee; import java.io.DataOutputStream; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @SuppressWarnings("serial") public class AllInOneServlet extends HttpServlet { public AllInOneServlet() { System.out.println("Servlet constructed"); } @Override public void init() { System.out.println("Servlet init"); } @Override public void destroy() { System.out.println("Servlet destory"); } @Override public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("Servlet served"); // 1.Get parameter from HTTP header String goWhere = request.getParameter("goto"); if (goWhere == null || "1".equals(goWhere)) { // 2.Set response header response.setContentType("text/html"); // 3.Get useful info from TCP & HTTP header System.out.println( "Request from: " + request.getRemoteAddr() + ":" + request.getRemotePort() + " by method " + request.getMethod()); // 4.Print html(out is built-in object in JSP) DataOutputStream out = new DataOutputStream(response.getOutputStream()); out.writeUTF("Hello Servlet"); out.writeUTF("<br>"); out.close(); } else if ("2".equals(goWhere)) { RequestDispatcher dispather = request.getRequestDispatcher("/main.jsp?param1=java"); request.setAttribute("param2", "servlet"); dispather.forward(request, response); } else if ("3".equals(goWhere)) { response.sendRedirect("http://www.google.com?newwindow=1&q=java&oq=java"); } } }
<!-- 1.Compile instruction --> <%@page import="java.util.concurrent.atomic.*, com.cdai.web.j2ee.TestBean" contentType="text/html;charset=utf-8" %> <!-- 2.Declaration: member variable and method --> <%! private AtomicInteger count = new AtomicInteger(1); private ThreadLocal<Integer> curCountStorage = new ThreadLocal<Integer>(); private int getCount() { int curCount = count.getAndIncrement(); curCountStorage.set(curCount); return curCount; } %> <!-- 3.JSP code & 4.Built-in object --> <% Object curCount = session.getAttribute("count"); if (curCount == null) { curCount = getCount(); session.setAttribute("count", curCount); } out.println(request.getParameter("param1") + " - " + request.getAttribute("param2")); %> <br> This is main.jsp. You're the <%=curCount%> visitor. <!-- 5.Runtime action --> <jsp:useBean id="testBean" class="com.cdai.web.j2ee.TestBean" scope="page"/> <br>Message in TestBean is: <jsp:getProperty property="message" name="testBean"/>
这是一个很简单的例子,通过http://127.0.0.1:8080/ssh/j2ee可以访问到AllInOneServlet。通过传给它
不同的goto参数,可以控制它是:(1)自己生成一个hello servlet的响应页面(2)转发到main.jsp生成
一个统计访问量的页面(3)重定向到Google首页。通过这个例子,让我们来一起来搞懂Servlet和JSP
这两个J2EE开发中最基础的组件。
一、Servlet基础
1.Servlet的生命周期
根据日志输出,当发送HTTP请求到Servlet时,Tomcat才创建Servlet。首先执行的是Servlet的构造函数,
之后是init()方法,然后才是service()方法。如果没有覆写service(),那么它会根据HTTP请求是Get还是
Post来调用doGet()和doPost()方法。
这一个Servlet会一直存在,被处理各个HTTP请求的线程调用,因此Servlet要尽量含有避免synchronized
代码。最后,当Tomcat移出了Servlet时会调用destory()方法(应用被卸载或Servlet文件发生变化)。
2.读请求头,设置响应头
通过Servlet的API可以很方便的从TCP和HTTP数据包中读出很多有用的信息,底层已经帮我们解析好了。
以后有空会写一个简单的Web服务器,模拟一下J2EE容器的一些基本功能。
3.读URL中参数
这也是Web开发中最常用的方法,通过getParameter()方法可以取出URL中的参数。
4.重定向和转发
很重要的两个概念。重定向一般是返回给浏览器一个外部的URL,让它去那里请求,所以浏览器实际上
请求了两次才得到最终的内容。而转发一般是在当前Web应用内部,从一个组件转发到另一个组件(比如
从Servlet到JSP),主要用于组件间协同工作。可以通过setAttribute来传递一些数据。
二、JSP基础
JSP看似比Servlet内容多而且复杂,其实学习JSP时只要关注两件事:
哪些代码是编译时用的,哪些是运行时执行。
各种标签在编译成Servlet代码后变成了什么样。
1.指令
指令是编译期间执行的代码,常用的有:page、include、taglib。语法是<%@page ... %>。
在这个例子中通过page指令的import设置JSP引用的Java类,这样JSP编译成Servlet时才不会
有编译错误。
2.声明
声明的变量和方法最终编译成Servlet中的成员变量和方法。语法是<%! ... %>。这里的代码
都会生成在service()方法外,所以声明的变量是编译后Servlet的成员变量,而且可以声明方法。
这是声明与后面将要介绍的普通JSP代码的区别。
3.动作
动作是运行期执行的代码。<jsp:include>、<jsp:forward>、<jsp:useBean>等等。<jsp:include>
是动态引入其他文件,如果代码不执行到这里就不会引入,一定要与include指令区分开。
<jsp:forward>与Servlet中的forward方法功能相同,而<jsp:useBean>、<jsp:property>稍后在JSP
转换成的Servlet源文件中会看到它们的真身。
4.JSP代码
编译后会生成到service()方法中,因此在这里声明的变量是局部变量,也就不能在这里声明方法了。
语法是<% ... %>。
5.内置对象
out、page、session、application、config、exception、request、response、pageContext。
可以在JSP中直接使用。这些内置对象没有什么神秘的,在Servlet中都是可以获得到的,只不过
在JSP中它们都有了简短的名字,所以用起来很方便而已。
6.JSP表达式
插入一个简单的Java代码得到一个值,语法是<%= ... %>。
下面就要揭开本例中JSP神秘的面纱了,在Tomcat的work目录中我们可以找到JSP转成的Servlet源文件文件。
package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; import java.util.concurrent.atomic.*; import com.cdai.web.j2ee.TestBean; public final class main_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { private AtomicInteger count = new AtomicInteger(1); private ThreadLocal<Integer> curCountStorage = new ThreadLocal<Integer>(); private int getCount() { int curCount = count.getAndIncrement(); curCountStorage.set(curCount); return curCount; } private static java.util.List _jspx_dependants; public Object getDependants() { return _jspx_dependants; } public void _jspService(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, ServletException { JspFactory _jspxFactory = null; PageContext pageContext = null; HttpSession session = null; ServletContext application = null; ServletConfig config = null; JspWriter out = null; Object page = this; JspWriter _jspx_out = null; PageContext _jspx_page_context = null; try { _jspxFactory = JspFactory.getDefaultFactory(); 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("<!-- 1.Compile instruction -->\r\n"); out.write("\r\n"); out.write("\r\n"); out.write("<!-- 2.Declaration: member variable and method -->\r\n"); out.write("\r\n"); out.write("\r\n"); out.write("<!-- 3.JSP code & 4.Built-in object -->\r\n"); Object curCount = session.getAttribute("count"); if (curCount == null) { curCount = getCount(); session.setAttribute("count", curCount); } out.println(request.getParameter("param1") + " - " + request.getAttribute("param2")); out.write("\r\n"); out.write("\r\n"); out.write("<br> This is main.jsp. You're the "); out.print(curCount); out.write(" visitor.\r\n"); out.write("\r\n"); out.write("<!-- 5.Runtime action -->\r\n"); com.cdai.web.j2ee.TestBean testBean = null; synchronized (_jspx_page_context) { testBean = (com.cdai.web.j2ee.TestBean) _jspx_page_context.getAttribute("testBean", PageContext.PAGE_SCOPE); if (testBean == null){ testBean = new com.cdai.web.j2ee.TestBean(); _jspx_page_context.setAttribute("testBean", testBean, PageContext.PAGE_SCOPE); } } out.write("\r\n"); out.write("<br>Message in TestBean is: "); out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString((((com.cdai.web.j2ee.TestBean)_jspx_page_context.findAttribute("testBean")).getMessage()))); out.write('\r'); out.write('\n'); } catch (Throwable t) { if (!(t instanceof SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) out.clearBuffer(); if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); } } finally { if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context); } } }
怎么样?对照着JSP的源码来看,是不是毫无神秘之处。
三、Cookie和Session
在这个例子中我们使用Session,借助Cookie保存一个Session ID在浏览器端。这种Cookie也叫做会话Cookie。
在同一个Chrome进程的打开多个不同页面访问http://127.0.0.1:8080/ssh/j2ee?goto=2都能够获得Session
中保存的数据,从而达到了使无状态的HTTP看起来好像有状态一样。
四、多线程安全
由于每个Servlet只有一个实例,被所有请求线程共享,所以在Servlet中要尽量避免代码同步、资源竞争,
否则服务器的响应速度会很慢。除了Servlet,还要注意一些会被共享的内置对象,比如在一个用户的所有
请求内被共享的Session对象,也是有可能发生并发问题的。有共享,就会有并发,所以在J2EE各个层的
开发中,Servlet/JSP -> Service -> DAO -> Database都要注意并发问题。