首页 > 代码库 > servlet回顾

servlet回顾

  • servlet
    • genericServlet抽象类
    • HttpServlet抽象类
    • ServletRequest接口
    • HttpServletRequest接口
    • ServletResponse接口
    • HttpServletResponse接口
    • ServletConfig接口
    • ServletContext接口
  • javaweb应用的生命周期
  • Servlet的生命周期
  • servletcontext共享数据
  • servletcontextlistener 监听器
  • 防止页面被缓存
  • 文件下载
  • 上传文件
  • 生成动态图像
  • 读写Cookie
  • 请求转发
  • 包含
  • 请求范围
  • 重定向
  • 避免并发问题

servlet

首先明确,下面各种方法和接口由容器提供商实现。我们自己只需要重写部分方法。
技术分享?
技术分享?

5个方法,三个方法由容器调用。

  • init(ServletConfig config)初始化对象。
  • service(ServletRequest req,ServletResponsr res)响应客户请求
  • destroy() 结束Servlet对象

代码可调用下两个方法。

  • getServletConfig() 返回一个包含Servlet初始化参数信息的对象
  • getServletInfo 返回包含servlet创建者 版权的字符串

genericServlet抽象类

为Servlet接口提供了通用实现,与网络层协议无关,还实现了servletconfig接口和 serializable接口

service()是其中唯一的抽象方法。
该类使用了装饰设计模式

HttpServlet抽象类

针对不同的请求方式,实现了相应的方法。但是默认的实现,都会返回一个错误。
为什么是抽象类?
先要明确,有抽象方法一定是抽象类,但是抽象类不一定有抽象方法。二,只是强调该类在开发者没有自定义实现的情况下无法被初始化而已,这叫适配器 。可参考 里氏替换原则

ServletRequest接口

  • 获得客户机信息
    getRequestURL方法返回客户端发出请求时的完整URL。
    getRequestURI方法返回请求行中的资源名部分。
    getQueryString 方法返回请求行中的参数部分。
    getRemoteAddr方法返回发出请求的客户机的IP地址
    getRemoteHost方法返回发出请求的客户机的完整主机名
    getRemotePort方法返回客户机所使用的网络端口号
    getLocalAddr方法返回WEB服务器的IP地址。
    getLocalName方法返回WEB服务器的主机名
    getMethod得到客户机请求方式

  • 获得客户机请求头
    getHeader(string name)方法
    getHeaders(String name)方法
    getHeaderNames方法

  • 获得客户机请求参数(客户端提交的数据)
    getParameter(name)方法 一个数据时使用,多个数据时使用下一个GPV方法
    getParameterValues(String name)方法
    getParameterNames方法
    getParameterMap方法

HttpServletRequest接口

getScheme()方法返回请求的计划,比如http,https或者ftp.
getServerName()方法返回被发送请求的服务器的主机名
getServerPort()方法返回被发送请求的端口号。
getContextPath()返回请求地址的根目录,以"/"开关,但不是以"/"结尾。
一个常用的获得服务器地址的连接字符串是:
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

getCookies() 取得cookie
getMethod() 取得请求方法,如get,post或put
getRequestURL() 取得请求URL(统一资源定位符)
getRequestURI() 取得请求URI(统一资源标识符)
getSession() 取得对应session
getQueryString() 取得请求中的查询字符串,即URL中?后面的内容

ServletResponse接口

setCharacterEncoding()设置响应正文的字符编码
setContenLength() 设置响应正文长度
setContentType() 设置响应正文MIME类型
setBufferSize() 设置响应正文的缓冲区的大小
reset()清空缓冲区内正文数据,并且清空响应状态代码和响应头
resetBuffer() 仅仅清空缓冲区正文数据,不清空状态代码和响应头
flushBuffer() 强制把缓冲区的响应正文发送到客户端
isCommitted() 判断数据是否已经发送到了客户端
getOutputStream() 返回一个对象,用来输出二进制的正文数据
getWriter() 返回一个对象,用它来输出字符串形式的正文数据

注:

  • ServletResponse默认MIME类型是纯文本
    HttpServletResponse默认MIME类型是html

    编码方式:以下三种方式是等价的:
    技术分享?

HttpServletResponse接口

addHeader(String name,String value)向HTTP响应头加入一项内容
sendError(int) 向客户端发一个代表错误的Http响应状态码
sendError(int src,String msg) 向客户端发一个错误代码和一个具体错误消息
addCookie(Cookie cookie) 向HTTP响应中加入一个cookie

ServletConfig接口

作为servlet接口init(ServletConfig config)的参数,与servlet产生关联。servletconfig对象与servletcontext对象关联。

  • getInitParameter(String name) 根据给定的初始化参数名,返回匹配的初始化参数值(静态方法,可以直接调用, String color = getInitParmeter("color"))
  • getInitParameterNames()返回一个枚举对象,包含所有的参数名
  • getServletContext() (静态方法)返回当前servletContext对象

在web.xml中,

<init-param>
<param-name>color</param-name>
<param-value>red</param-value>
</init-param>

ServletContext接口

本接口是Servlet与Servlet容器之间直接通信的接口。容器在启动web应用的时候,会创建一个ServletContext对象,每个web应用都有唯一的对象。

  1. 在web应用范围内存取共享数据的方法。

  • setAttribute(String name, java.lang.Object object)把一个对象和参数名绑定,存入到ServletContext中。
  • getAttribute(String name) 返回相应的属性值
  • etc

  1. 访问当前Web应用的资源
  2. getContentPath() 返回当前web应用的URL入口
  3. getInitParameter(String name) 根据给定参数名,返回初始化参数值(web.xml)中配置的
  4. 访问容器中的其他web应用
  5. 访问容器的相关信息
  6. 访问服务器端端文件系统资源
  7. 输出日志
  8. log(String msg) 向Servlet的日志文件中写日志
  9. log(String message,java.lang.Throwable throwable)向日志文件中写错误日志以及异常的堆栈信息
<context-param>
    <param-name>email</param-name>
    <param-value>hello@163.com</param-value>
</context-param>

javaweb应用的生命周期

  • 启动 把web.xml加载到内存 创建servletcontext对象 对所有的Filter初始化 对需要在启动时就被初始化的Servlet进行初始化(创建servletconfig对象,把web.xml中的初始化值包含进来,调用init(servletconfig对象)
  • 运行 如果servlet已经初始化了,就调用service() 如果还没被初始化,就先初始化,再调用service()方法
  • 终止 销毁所有servlet 销毁Filter 销毁相关对象 如ServletContext

Servlet的生命周期

初始化阶段
1.容器加载Servlet类,将.class文件的数据读入到内存中
2.容器创建ServletConfig对象,容器使其与当前web应用的ServletContext对象关联
3.创建Servlet对象
4.调用servlet对象init(ServletConfig config)
下面两种情况,servlet会进入初始化
1.第一次被访问
2.web.xml配置了

运行阶段

销毁阶段

一个web应用一个context,一个servlet一个config,config可调用静态的getContext得到该对象

servletcontext共享数据

统计访问次数,就用serveletcontext设置一个属性(计数器)

servletcontextlistener 监听器

上述的统计方式会导致web应用重启后数据消失,由监听器监控web生命周期,当周期结束时,把计数写入数据库。

防止页面被缓存

设置响应头
技术分享?

第一种用于HTTP1.0的浏览器 第二种用于HTTP1.1 第三种都可以 表示过期时间

文件下载

public class DownloadServlet extends HttpServlet {
  public void doGet(HttpServletRequest request,HttpServletResponse response)
         throws ServletException, IOException {
    OutputStream out; //输出响应正文的输出流
    InputStream in;  //读取本地文件的输入流
    //获得filename请求参数 
    String filename=request.getParameter("filename");
     
    if(filename==null){
      out=response.getOutputStream(); 
      out.write("Please input filename.".getBytes());
      out.close();
      return;
    }
    
    //创建读取本地文件的输入流
    in= getServletContext().getResourceAsStream("/store/"+filename);
    int length=in.available();
    //设置响应正文的MIME类型
    response.setContentType("application/force-download"); 
    response.setHeader("Content-Length",String.valueOf(length));
    response.setHeader("Content-Disposition", "attachment;filename=\""+filename +"\" "); 
    
    /** 把本地文件中的数据发送给客户 */
    out=response.getOutputStream(); 
    int bytesRead = 0;
    byte[] buffer = new byte[512];
    while ((bytesRead = in.read(buffer)) != -1){
      out.write(buffer, 0, bytesRead);
    } 
       
    in.close();
    out.close();
  }
}

上传文件

在html中,在

标签中,使用anctype="MULTIPART/FORM-DATA"
容器将表单包装成httpservletrequest对象,解析出复杂表单的个部分很复杂。所以利用Apache的两个软件包简化过程。

  • commons-fileupload-1.2.1.jar 文件上传包
  • commons-io-1.4.jar 输入输出包

主要是用fileupload上传文件,该包依赖于io包
技术分享?

对于每一个“multipart/form-data”的请求,该包把复合表单的每一部分看作一个FileItem对象。该对象分为两种:
1.formField 普通表单类型
2.非formFiled:上传文件类型,文件域就是这种类型。

DiskFileItemFactory类,对象的工厂
DiskFileItem类,把上传的文件数据保存到硬盘上

public class UploadServlet extends HttpServlet {
  private String filePath; //存放上传文件的目录
  private String tempFilePath; //存放临时文件的目录

  public void init(ServletConfig config)throws ServletException {
    super.init(config);
    filePath=config.getInitParameter("filePath");
    tempFilePath=config.getInitParameter("tempFilePath");
    filePath=getServletContext().getRealPath(filePath);
    tempFilePath=getServletContext().getRealPath(tempFilePath);
  }
  public void doPost(HttpServletRequest request,HttpServletResponse response)
         throws ServletException, IOException {
    response.setContentType("text/plain");
    //向客户端发送响应正文
    PrintWriter outNet=response.getWriter(); 
    try{
      //创建一个基于硬盘的FileItem工厂
      DiskFileItemFactory factory = new DiskFileItemFactory();
      //设置向硬盘写数据时所用的缓冲区的大小,此处为4K
      factory.setSizeThreshold(4*1024); 
      //设置临时目录
      factory.setRepository(new File(tempFilePath));

      //创建一个文件上传处理器
      ServletFileUpload upload = new ServletFileUpload(factory);
      //设置允许上传的文件的最大尺寸,此处为4M
      upload.setSizeMax(4*1024*1024); 
    
      List /* FileItem */ items = upload.parseRequest(request);    

      Iterator iter = items.iterator();
      while (iter.hasNext()) {
        FileItem item = (FileItem) iter.next();
        if(item.isFormField()) {
          processFormField(item,outNet); //处理普通的表单域
        }else{
          processUploadedFile(item,outNet); //处理上传文件
        }
      }
      outNet.close();
    }catch(Exception e){
       throw new ServletException(e);
    }
  }

  private void processFormField(FileItem item,PrintWriter outNet){
    String name = item.getFieldName();
    String value = http://www.mamicode.com/item.getString();":"+value+"\r\n");
  }
  
  
  private void processUploadedFile(FileItem item,PrintWriter outNet)throws Exception{
    String filename=item.getName();
    int index=filename.lastIndexOf("\\");
    filename=filename.substring(index+1,filename.length());
    long fileSize=item.getSize();
    
    if(filename.equals("") && fileSize==0)return;

    File uploadedFile = new File(filePath+"/"+filename);
    item.write(uploadedFile);
    outNet.println(filename+" is saved.");
    outNet.println("The size of " +filename+" is "+fileSize+"\r\n");
  }
}


在web.xml中配置filepath和tempFilePath的值

生成动态图像

读写Cookie

cookie基本运行机制
技术分享?

有Cookie类,每个对象都包含一个名字和一个值
Cookie the Cookie = new Cookie("username", "tom")
response.addCookie(the Cookie)
Cookie cookies[] = request.getCookies()
cookie.setMaxge(int)
大于零,表示在客户端硬盘保存的时间
等于零,删除当前cookie
小于零,就不保存到硬盘,仅仅保存在当前浏览器进程中,关闭浏览器,cookie消失
默认为-1

请求转发

一个Servlet对象无法获得另一个servlet对象的引用。
特点:
* 源组件和目标组件处理的都是同一个客户请求,共享一个request和response对象 源组件的响应结果不会发送到客户端(会给目标组件再发送到客户端)
* 如果在请求转发前提交了响应结果(如flushBuffer,close),会抛出异常(会显示源组件内容,日志中会有异常)。如果不提交,不会显示。
* disspatcher.forward(request,response)后的代码也会执行
* 目标组件可以是servlet、jsp、html
* 都依赖于javax.servlet.RequestDispatcher()
这个接口表示请求分发器:
forward() 把自己的东西转发到别的组件
include() 将别人的东西包含到自己中

通过两种方式得到RequestDispathcer对象
* 调用servletcontext的getrequestDispatcher(String path)
* 调用servletRequset的getrequestdispatcher(String path)
path为目标组件的路径。两种区别在于,前者要求绝对路径,后者可以为绝对路径也可以是相对路径

包含

特点:
源组件和被包含的目标组件的输出数据都会添加到响应结果
目标组件中对响应状态代码或者响应头的操作都会被忽略。

请求范围

web的应用范围是指web应用的生命周期,与servletcontext对象的生命周期对应。
请求范围是指服务器端响应一次客户请求的过程,从一个servlet接受到请求开始,到返回响应结果结束。与servletrequest对象和servletresponse对象的生命周期对应。

重定向

Response.sendRedirect(String location)
1.用户输入特定的URL,请求访问服务器端的某个组件
2.服务器端组件返回一个302的响应结果,含义为让浏览器再请求访问另外一个web组件,在响应结果中包含另外一个web组件的URL,这个组件不一定在同一个服务器上
3.浏览器接受到这种响应结果后,立即自动请求访问另一个组件
4.浏览器收到另一个组件的响应结果
源组件
request.setAttribute("msg",message)
response.sendRedirect("/helloapp/output1?msg="+message)
output1组件:
String message = (String)request.getAttribute("msg")

注意:

  • 源组件的响应结果不会发送到浏览器中,如果在sernRedirect之前已经提交了响应结果,会抛出异常。
  • 源组件调用重定向方法后到代码也会执行
  • 不共享数据(request或者response)

避免并发问题

多个客户访问同一个servlet,容器会创建多个线程来访问同一个对象,而不是为每个访问创建一个对象。这样就会导致一些并发问题。
解决方案:
1. 合理决定在Servlet中定义的变量的作用域
2. 对于多线程同时访问数据导致的并发问题的情况,使用同步机制进行同步
3. 不要使用被废弃的 javax.servlet.singthreadmodel

对于方案1:
一个请求对应一个工作线程,一个线程对应一个username变量。
局部变量在一个方法中定义。当一个线程执行这个方法时就会创建这个局部变量。如果有多个线程执行该方法,每个线程都会有自己的局部变量。
实例变量在类中定义,每个实例都有自己的实例变量。如果多个线程执行一个实例方法,都会访问同一个实例变量。
容器对于每一个servlet只会创建一个servlet对象。
不要使用实例变量,要把变量定义在service方法中

对于方案2:
使用synchronise()同步代码块,避免同时操作同一个数据。

对于方案3:
如果servlet实现了这个接口,会有两种方式来运行servlet
1.任意时刻,只允许一个工作线程执行servlet的service方法。
2.创建一个servlet对象池,不同的客户操作不同的servlet对象。
Tomcat是第二种方式。

servlet回顾