Head First Servlets and JSP笔记(一)
第一章 前言
GET和POST
GET是一个简单请求,总字符数是有限的,发送的数据会追加在url后面;GET是幂等的,不会修改服务器上的任何内容;
POST可以发送用户数据;POST不是幂等的,POST体中提交的数据可能用于不可逆转的事务;
如果表单没有指明"method=“POST”,会默认为HTTPGET请求;
TCP端口
端口是16位数据,标识服务器硬件上特定的软件程序;
从0~1023的端口号已经被保留,定制的服务器程序不要使用这些端口;
URL
统一资源定位符:如果URL对应一个GET请求,那么它可能包含一个可选的查询串;
JSP
JSP页面就像是一个HTML页面,可以把java和java有关的东西插入到该页面中;
第二章 高层概述:Web应用体系
Tomcat
Servlet没有main()方法,它们受控于另一个java应用——容器;
容器提供:通信支持、生命周期管理、多线程、声明方式实现安全、JSP支持;
部署描述文件DD
将Servlet部署到Web容器时,会创建一个简单的XML文档,称为部署描述文件(DD)
模型-视图-控制器(MVC)
MVC把业务逻辑从Servlet中抽出来,放在一个“模型”——一个可重用的java类中,模型是业务数据和出力该类数据的方法的组合;
J2EE
Tomcat只是一个Web容器,而不是完整的J2EE应用服务器(因为没有EJB容器);
第三章 MVC实战
BUGreport(1):
在编写第二版servlet时,通过javac编译BeerSelect.java出错,未找到对应目录下的文件;
Solution:
直接编译,手动部署目录;
BUGreport(2)
在重启tomcat之后,访问http://localhost:8080/Beer-v1/form.html网页下submit之后资源响应报错;
Solution
反复排查之后发现使用javac调用servlet-api.jar的路径应该是tomcat/lib路径下的包,而不是JDK/lib路径下的包,更改之后,响应结果正确;
(调用模型实例过程略)
第四章 作为Servlet:请求和响应
Servlet受容器的控制
与http有关的都在javax.servlet.http包中,其余的通用类接口都在javax.servlet包中;
容器运行多个线程来处理对一个servlet的多个请求;
init()在第一个service调用之前完成;
ServletConfig:用于向servlet传递部署时信息;用于访问ServletContext;参数在部署描述文件中描述;
ServletContext:用于访问web应用参数,相当于公告栏,应用的其他部分可以访问这些消息;
常见ServletRequest和HttpServletRequest的API:
// 客户的平台和浏览器信息
String client=request.GetHeader("User-Agent");
//与请求相关的cookie
Cookie cookies[]=request.getCookies();
//与客户相关的会话
HttpSession session=request.getSession();
//请求的HTTP方法
String themethod=request.getMethod();
//请求的输入流——只包含请求体而不包含首部
InputStream input=request.getInputStream();
大多数情况,使用响应只是为了向客户发回数据,会对响应调用两个方法:
setContentType() 和 getWriter() 此后只需要I/O将HTML写至流;
内容类型是HTTP响应中必须有的一个HTTP响应首部,常指MIME(Multipurpose Internet Mail Extensions)类型;
ServletOutputStream 用于输出字节,PrintWriter 用于输出字符数据;
//样例
PrintWriter writer=response.getWriter();
writer.println("some text and html");
ServletOutputStream out=response.getOutputStream();
out.wirte(aByteArray);
PrintWriter有ServletOutputStream的引用,会把调用委托给ServletOutputStream;
setHeader()会覆盖现有的值;addHeader()会增加另一个值;
Servlet重定向让浏览器完成工作;
if(worksforme)
{//handle the request
}
else{
response.sendRedirect("new http location");
}
sendRedirect() 使用相对URLs的两种用法(取的是一个String,而不是一个url对象)——斜线开头与非斜线开头;
第五章 作为Web应用:属性和监听者
servlet初始化参数
在每个特定的servlet的<servlet>元素中,只对配置了<init-param>的相应servlet可用;
在DD文件中(web.xml)中:
<init-param>
<param-name>adminEmail</param-name>
<param-value>whatever@bullshit.com</param-value>
</init-param>
在servlet代码中:
out.println(getServletConfig().getInitParameter("adminEmail"));
容器建立servlet只初始化一次,读取DD,为ServletConfig创建名/值对;
上下文初始化参数
在<web-app>元素中,而不在具体的<servlet>元素内;针对整个web应用,而不是一个servlet;
在DD文件(web.xml)中
<context-param>
<param-name>adminEmail</param-name>
<param-value>whatever@bullshit.com</param-value>
<context-param>
在servlet代码中
out.println(getServletContext().getInitParameter("admminEmail"));
web应用初始化
- 容器读DD,对应每个<context-param>创建名\值String对;
- 容器创建ServletContext的一个新实例;
- 容器为ServletContext提供上下文初始化参数各个名\值对的引用;
- 在一个Web应用中部署的各个servlet和JSP都能访问同一个的ServletContext
常见
ServletContext方法 |
---|
getInitParameter(String) |
getAttribute(String) |
setAttribute(String,Object) |
removeAttribute(String) |
ServletContextListener
Dog例子:
- 监听者对象向ServletContextEvent对象请求应用ServletContext对象的一个引用
- 监听者使用这个ServletContext引用得到"breed"的上下文初始化参数,这是一个String
- 监听者使用这个String构造一个一个Dog对象
- 监听者使用ServletContext引用在ServletContext中设置Dog属性
- Web应用的测试servlet从ServletContext中得到Dog对象,并调用这个Dog的getBreed()方法
编写监听者类:
package com.example;
import javax.servlet.*;
public class MyServletContextListener implements SerletContextListener{
public void contextInitialized(ServletContextEvent event){
ServeltContext sc=event.getServletContext();
String dogBreed=sc.getInitParameter("breed");
Dog d=new Dog(dogBreed);
sc.setAttribute("dog",d);
}
public void contextDestroyed(ServletContextEvent event){
//nothing to do here
8个监听者:
属性不同于参数:
属性的三个作用域:上下文、请求、会话;
上下文作用域不是线程安全的;同步服务方法会防止同一个servlet中的其他线程访问上下文属性,但是不能阻止另外一个不同的servlet的访问;** 解决办法** 对上下文对象对象本身同步:
public void doGet(HttpServletRequest request,HttpServletResponse response)
throws IOException,ServletException{
response.setContentType("text/html");
//etc.
synchronized(getServletContext()){
//对属性的操作
}
}
对HttpSession同步来保护会话属性;
SingleThreadModel设计用来保护实例变量,确保servlet一次只处理一个请求;
只有请求属性和局部变量是线程安全的;
//code in doGet()
BeerExpert be=new BeerExpert();
ArrayList result=be.getBrands(c);
request.setAttribute("styles",result);
RequestDispatcher view=request.getRequestDispatcher("result.jsp");
view.forward(request,response);
让组件的其他部分接管请求使用RequestDispatcher;
RequestDispatcher只有forward()和include()两个方法取请求和响应对象为参数;
如果已经使用的响应(os.flush())则不能再使用请求,容器会抛出IlleagalStateException;
第六章 会话状态:会话管理
- 编写servlet代码,将对象保存到一个会话对象中,以及从会话对象获取对象
- 给定一个场景,描述访问会话对象使用的API,解释创建和撤销会话对象的机制
- 使用会话的监听者,编写代码对会话的有关事件作出响应,包括向会话增加一个对象,以及会话对象从一个VM迁移到另一个VM
- 给定一个场景,说明Web容器可以采用哪些会话管理机制,如何使用cookie来管理会话,如何使用URL重写管理会话
HttpSession对象可以保存跨同一个客户多个请求的会话状态;
HTTP协议使用的是无状态连接,对容器而言,每个请求都来自于新的客户——客户需要一个唯一的会话ID——通过cookie交换这个会话ID的信息;
session.isNew()方法判断会话是否已经存在;
禁用cookie的客户会忽略"Set-Cookie"首部;如果客户不接受cookie,可以通过URL重写取得置于cookie中的会话ID;
public void doGet(HttpServletRequest request,HttpServletResponse response)throws IOException{
response.setContentType("text/html");
PrintWriter out=response.getWriter();
//得到一个会话
HttpSession session=request.getSession();
out.println("<html><body>");
//向这个URL增加额外的会话ID信息;对一个URL编码,需要调用response;
out.println("<a href=\""+response.encodeURL("/BeerTest.do")+"\">click me</a>");
out.println("</body></html>");
关键的HttpSession方法 | 作用 |
---|---|
getCreationTime() | 返回第一次创建会话的时间 |
getLastAccessedTime() | 返回容器最后一次得到包含这个会话ID的请求后过去了多长时间(ms) |
setMaxInactiveInterval() | 指定对于这个会话客户请求的最大间隔时间(s) |
getMaxInactiveInterval() | 返回对于这个会话客户请求的最大间隔时间(s) |
invalidate() | 结束会话,这个会话中的所有会话属性也会解除绑定 |
会话有三种死法:超时;你在会话对象上调用invalidate();应用结束;
可以使用cookie在服务器和客户之间交换名/值String对;服务器把cookie发送给客户,客户再在以后的每个请求中发回这个cookie;客户的浏览器退出时,会话cookie就会消失,但是可以使cookie存活时间更长一些;
简单的定制cookie示例:
//创建和设置cookie的Servlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class CookieTest extends HttppServlet{
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException{
response.setContentType("text/html");
//得到表单中提交的用户名
String name=request.getParameter("username");
Cookie cookie=new Cookie("username",name);
cookie.setMaxAge(30*60);//在客户端上存活30分钟
response.addCookie(cookie);//将此cookie增加为"Set-Cookie"响应首部
//让jsp建立响应页面
RequestDispatcher view=request.getRequestDispatcher("cookieresult.jsp");
view.forward(request,response);
下面这个JSP呈现以上servlet生成的视图:
<html><body>
<a herf="checkcookie.do">click here</a>
</body></html>
构建CheckCookie类:
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class CheckCookie extends HttpServlet{
public void doGet*HttpServletRequest request,HttpServletResponse response) throws
IOException,ServletException{
response.setConetentType("text/html");
PrintWriter out=response.getWriter();
Cookie[] cookies=request.getCookies();
if(cookies!=null){
for(int i=0;i<cookies.length;i++){
Cookie cookies[i];
if(Cookie.getName().equals("username")){
String userName=cookie.getValue();
out.println("Hello"+userName);
break;
}
}
}
}
}
HttpSession的重要里程碑;
绑定到会话的属性可以由属于同一个ServletContext而且处理同一会话中某个请求的其他servlet访问;
HttpSessionListener,HttpSessionAttributeListener和HttpSessionActivationListener必须在DD中注册,它们与会话本身相关,而不是与会话中放置的单个属性相关;
只有HttpSession对象会从一个VM移到另一个VM;
Listener例子:(监听者同时也是一个属性类)
package com.example;
import javax.servlet.http.*;
public class BeerSessionCounter implements HttpSessionListener{
//监听者允许跟踪Web应用中活动会话的个数
static private int activeSessions;
public static int getActiveSessions(){
return activeSessions;
}
public void sessionCreated(HttpSessionEvent event){
activeSessions++;
}
public void sessionDestroyed(HttpSessionEvent event){
activeSessions--;
}
}
在DD中配置监听者
<web-app...>
<listener>
<listener-class>
com.example.BeerSeeeionCounter
</listener-class>
</listener>
</web-app>
属性监听者
package com.example;
import javax.servlet.http.*;
public class BeerAttributeListener implements HttpSessionAttributeListener{
public void attributedAdded(HttpSessionBindingEvent event){
String name=event.getName();
Object value=event.getValue();
System.out.println("Attribute added:"+name+":"+value);
}
public void attributeRemoved(HttpSessionBindingEvent event){
String name=event.getName();
Object value=event.getValue();
System.out.println("Attribute removed:"+name+":"+value);
}
public void attributeReplaced(HttpSessionBindingEvent event){
//and so on
类似地,在DD中配置监听者;