Servlet开发

一、Servlet概述

  1.sun公司提供的动态web资源开发技术。本质是上一段java小程序,要求这个小程序必须实现Servlet接口,以便服务器能够调用。
    2.开发Servlet的两个步骤
    *实验:Servlet的快速入门
    (1)步骤一:写一个java程序实现Servlet接口(此处直接继承了默认实现类GenericServlet)

 1 package cn.itheima;
 2 import java.io.*;
 3 import javax.servlet.*;
 4                 
 5 public class FirstServlet extends GenericServlet{
 6     public void service(ServletRequest req, ServletResponse res)throws ServletException, java.io.IOException{
 7       res.getOutputStream().write("My FirstServlet!".getBytes());
 8     }
 9                 
10 }


    (2)将编译好的带包的.class放到WEB-INF/classes下以外,还要配置web应用的 web.xml注册Servlet

1 <servlet>
2     <servlet-name>FirstServlet</servlet-name>
3     <servlet-class>cn.itheima.FirstServlet</servlet-class>
4 </servlet>
5 <servlet-mapping>
6     <servlet-name>FirstServlet</servlet-name>
7     <url-pattern>/FirstServlet</url-pattern>
8 </servlet-mapping>

二、Servlet的详述 

  1.生命周期:一件事物什么时候生,什么时候死,在生存期间必然会做的事情,所有这些放在一起就是该事物的声明周期。  

  2.Servlet的调用过程/生命周期:通常情况下,servlet第一次被访问的时候在内存中创建对象,在创建后立即调用init()方法进行初始化。对于每一次请求都掉用service(req,resp)方法处理请求,此时会用Request对象封装请求信息,并用Response对象(最初是空的)代表响应消息,传入到service方法里供使用。当service方法处理完成后,返回服务器服务器根据Response中的信息组织称响应消息返回给浏览器。响应结束后servlet并不销毁,一直驻留在内存中等待下一次请求。直到服务器关闭或web应用被移除出虚拟主机,servlet对象销毁并在销毁前调用destroy()方法做一些善后的事情。  

  3.Servlet接口的继承结构  

  Servlet接口:定义了一个servlet应该具有的方法,所有的Servlet都应该直接或间接实现此接口  

  |  

  |----GenericServlet:对Servlet接口的默认实现,通用Servlet,这是一个抽象类,其中的大部分方法都做了默认实现,只有service方法是一个抽象方法需要继承者自己实现               |     

            |----HttpServlet:对HTTP协议进行了优化的Servlet,继承自GenericServlet类,并且实现了其中的service抽象方法,默认的实现中判断了请求的请求方式,并根据请求方式的不同分别调用不同的doXXX()方法。通常我们直接继承HttpServlet即可     

  4.web.xml注册Servlet的注意事项   

    4.1利用<servlet><servlet-mapping>标签注册一个Servlet 

1 <servlet>
2     <servlet-name>FirstServlet</servlet-name>
3     <servlet-class>cn.itheima.FirstServlet</servlet-class>  
4 </servlet>
5 <servlet-mapping>
6     <servlet-name>FirstServlet</servlet-name>
7     <url-pattern>/FirstServlet</url-pattern>
8 </servlet-mapping>

    4.2一个<servlet>可以对应多个<servlet-mapping>       

    4.3可以用*匹配符配置<serlvet-mapping>,但是要注意,必须是*.do或者/开头的以/*结束的路径。 

      由于匹配符的引入有可能一个虚拟路径会对应多个servlet-mapping,此时哪个最像找哪个servlet,并且*.do级别最低。       

    4.4可以为<servlet>配置<load-on-startup>子标签,指定servlet随着服务器的启动而加载,其中配置的数值指定启动的顺序  

1 <servlet>
2     <servlet-name>invoker</servlet-name>
3     <servlet-class>org.apache.catalina.servlets.InvokerServlet</servlet-class>
4     <load-on-startup>2</load-on-startup>
5 </servlet>

    4.5缺省servlet:如果一个servlet的对外访问路径被设置为/,则该servlet就是一个缺省servlet,其他servlet不处理的请求都由它来处理     ~在conf/web.xml中配置了缺省servlet,对静态资源的访问和错误页面的输出就是由这个缺省servlet来处理的。如果我们自己写一个缺省servlet把爸爸web.xml中的缺省servlet覆盖的话,会导致静态web资源无法访问。所以不推荐配置。   

    4.6servlet的线程安全问题   

      4.6.1由于通常情况下,一个servlet在内存只有一个实例处理请求,当多个请求发送过来的时候就会有多个线程操作该servlet对象,此时可能导致线程安全问题。

       (1)serlvet的成员变量可能存在线程安全问题    

      *实验:定义一个成员变量 int i = 0;在doXXX()方法中进行i++操作并输出i值到客户端,此时由于延迟可能导致线程安全问题  

       (2)serlvet操作资源文件时,多个线程操作同一文件引发线程安全问题    

      *实验:请求带着一个参数过来,servlet将请求参数写入到一个文件,再读取该文件,将读取到的值打印到客户端上,有可能有线程安全问题           

      4.6.2解决方法   

      (1)利用同步代码块解决问题。缺陷是,同一时间同步代码块只能处理一个 请求,效率很低下,所以同步代码块中尽量只包含核心的导致线程安全问题的代码。   

      (2)为该servlet实现SingleThreadModel接口,此为一个标记接口,被标记的servlet将会在内存中保存一个servlet池,如果一个线程来了而池中没有servlet对象处理,则创建一个新的。如果池中有空闲的servlet则直接使用。这并不能真的解决线程安全问题。此接口已经被废弃。   

      (3)两种解决方案都不够完美,所以尽量不要在servlet中出现成员变量。

三、ServletConfig -- 代表当前Servlet在web.xml中的配置信息

  1.方法
 String getServletName()  -- 获取当前Servlet在web.xml中配置的名字
        String getInitParameter(String name) -- 获取当前Servlet指定名称的初始化参数的值
        Enumeration getInitParameterNames()  -- 获取当前Servlet所有初始化参数的名字组成的枚举
        ServletContext getServletContext()  -- 获取代表当前web应用的ServletContext对象
  2.代表servlet配置的对象,可以在web.xml中<servlet>中配置

1 <servlet>
2      <servlet-name>Demo5Servlet</servlet-name>
3      <servlet-class>cn.itheima.Demo5Servlet</servlet-class>
4      <init-param>
5           <param-name>data1</param-name>
6           <param-value>value1</param-value>
7      </init-param>
8  </servlet>

  然后在servlet中利用this.getServletConfig()获取ServletConfig对象,该对象提供了getInitParameter()和getInitParameterNames()方法,可以遍历出配置中的配置项。不想在servlet中写死的内容可以配置到此处。

四、ServletContext -- 代表当前web应用

  1.代表当前web应用的对象。    

  2.做为域对象可以在整个web应用范围内共享数据   

    域对象:在一个可以被看见的范围内共享数据用到对象   

    作用范围:整个web应用范围内共享数据   

    生命周期:当web应用被加载进容器时创建代表整个web应用的ServletContext对象,当服务器关闭或web应用被移除出容器时,ServletContext对象跟着销毁。   

    void setAttribute(String,Object);          Object getAttribute(String);          void removeAttribute(String);

     *域:一个域就理解为一个框,这里面可以放置数据,一个域既然称作域,他就有一个可以被看见的范围,这个范围内都可以对这个域中的数据进行操作,那这样的对象就叫做域对象。    

  3.在web.xml可以配置整个web应用的初始化参数,利用ServletContext去获得  

  请求参数 parameter --- 浏览器发送过来的请求中的参数信息       

  初始化参数 initparameter --- 在web.xml中为Servlet或ServletContext配置的初始化时带有的基本参数      

  域属性 attribute --- 四大作用域中存取的键值对  

1 <context-param>
2         <param-name>param1</param-name>
3         <param-value>pvalue1</param-value>
4 </context-param>
5 this.getServletContext().getInitParameter("param1")
6 this.getServletContext().getInitParameterNames()

  4.实现Servlet的转发  

     重定向 : 302+Location          

    请求转发 : 服务器内不进行资源流转          

    *请求转发是一次请求一次响应实现资源流转.请求重定向两次请求两次响应.

    this.getServletContext().getRequestDispatcher("/servlet/Demo10Servlet").forward(request, response);   方法执行结束,service就会返回到服务器,再有服务器去调用目标servlet,其中request会重新创建,并将之前的request的数据拷贝进去。      

  5.读取资源文件    

    5.1由于相对路径默认相对的是java虚拟机启动的目录,所以我们直接写相对路径将会是相对于tomcat/bin目录,所以是拿不到资源的。如果写成绝对路径,当项目发布到其他环境时,绝对路径就错了。    

    5.2为了解决这个问题ServletContext提供了this.getServletContext().getRealPath("/1.properties"),给进一个资源的虚拟路径,将会返回该资源在当前环境下的真实路径。this.getServletContext().getResourceAsStream("/1.properties"),给一个资源的虚拟路径返回到该资源真实路径的流。    

    5.3当在非servlet下获取资源文件时,就没有ServletContext对象用了,此时只能用类加载器classLoader.getResourceAsStream("../../1.properties"),此方法利用类加载器直接将资源加载到内存中,有更新延迟的问题,以及如果文件太大,占用内存过大。classLoader.getResource("../1.properties").getPath(),直接返回资源的真实路径,没有更新延迟的问题。

五、Response 

  1.Resonse的继承结构:    ServletResponse--HttpServletResponse

  2.Response代表响应,于是响应消息中的 状态码、响应头、实体内容都可以由它进行操作,由此引伸出如下实验:

  3.利用Response输出数据到客户端:   

    response.getOutputStream().write("中文".getBytes())输出数据,这是一个字节流,是什么字节输出什么字节,   而浏览器默认用平台字节码打开服务器发送的数据,如果服务器端使用了非平台码去输出字符的字节数据就需要明确的指定浏览器编码时所用的码表,   以防止乱码问题。  

    response.addHeader("Content-type","text/html;charset=gb2312")   

  例:   response.setContentTye("text/html;charset=utf-8");       

     response.getOutputStream().write("".getBytes("utf-8"));

      response.getWriter().write(“中文”);输出数据,这是一个字符流,response会将此字符进行转码操作后输出到浏览器,这个过程默认使用ISO8859-1码表,而ISO8859-1中没有中文,于是转码过程中用?代替了中文,导致乱码问题。可以指定response在转码过程中使用的目标码表,防止乱码。

   response.setCharcterEncoding("gb2312");   

  例:   response.setCharacterEncoding("utf-8");       

     response.setContentType("text/html;charset=utf-8");     

     response.getWriter().write("");

    ***其实response还提供了setContentType("text/html;charset=gb2312")方法,此方法会设置content-type响应头,通知浏览器打开的码表,   同时设置response的转码用码表,从而一行代码解决乱码。

  4.利用Response 设置 content-disposition头实现文件下载:   设置响应头content-disposition为“attachment;filename=xxx.xxx”   利用流将文件读取进来,再利用Response获取响应流输出。如果文件名为中文,一定要进行URL编码,编码所用的码表一定要是utf-8   

  例1:   response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode("美女.jpg", "utf-8"));           

  5.refresh头控制定时刷新:   设置响应头Refresh为一个数值,指定多少秒后刷新当前页面   设置响应头Refresh为 3;url=/Day05/index.jsp,指定多少秒后刷新到哪个页面   可以用来实现注册后“注册成功,3秒后跳转到主页”的功能   在HTML可以利用<meta http-equiv= "" content="">标签模拟响应头的功能。   

  例1:response.setHeader("refresh", "3;url=/Day04/index.jsp");

  6.控制是否缓存资源:  利用response设置expires、Cache-Control、Pragma实现浏览器是否缓存资源,这三个头都可以实现,但是由于历史原因,不同浏览器实现不同,所以一般配合这三个头使用   

    6.1控制浏览器不要缓存(验证码图片不缓存)设置expires为0或-1设置Cache-Control为no-cache、Pragma为no-cache   

    6.2控制浏览器缓存资源。即使不明确指定浏览器也会缓存资源,这种缓存没有截至日期。当在地址栏重新输入地址时会用缓存,但是当刷新或重新开浏览器访问时会重新获得资源。如果明确指定缓存时间,浏览器缓存是,会有一个截至日期,在截至日期到期之前,当在地址栏重新输入地址或重新开浏览器访问时都会用缓存,而当刷新时会重新获得资源。   

    例:response.setDateHeader("Expires", System.currentTimeMillis()+1000l*3600*24*30);  

    例2: response.setIntHeader("Expires", -1);    

       response.setHeader("Cache-Control", "no-cache");   

       response.setHeader("Pragma", "no-cache");   

  7.Response实现请求重定向   

    7.1古老方法:response.setStatus(302);response.addHeader("Location","URL");   

    7.2快捷方式:response.sendRedirect("URL");

  *8.输出验证码图片

  

 1 public class ValiImg extends HttpServlet {
 2 
 3     public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 4         response.setDateHeader("Expires", -1);
 5         response.setHeader("Cache-Control", "no-cache");
 6         response.setHeader("Pragma", "no-cache");
 7         // 1.在内存中构建出一张图片
 8         int height = 30;
 9         int width = 120;
10         int xpyl = 5;
11         int ypyl = 22;
12         int bang = 20;
13         BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
14         // 2.获取图像上的画布
15         Graphics2D g = (Graphics2D) img.getGraphics();
16         // 3.设置背景色
17         g.setColor(Color.LIGHT_GRAY);
18         g.fillRect(0, 0, width, height);
19         // 4.设置边框
20         g.setColor(Color.BLUE);
21         g.drawRect(0, 0, width - 1, height - 1);
22         // 5.画干扰线
23         for (int i = 0; i < 5; i++) {
24             g.setColor(Color.RED);
25             g.drawLine(randNum(0, width), randNum(0, height), randNum(0, width), randNum(0, height));
26         }
27         // 6.写字
28         String base = "u7684u4e00u4e86u662fu6211u4e0du5728u4ebau4eecu6709u6765u4ed6u8fd9u4e0au7740u4e2au5730u5230u5927u91ccu8bf4u5c31u53bbu5b50u5f97u4e5fu548cu90a3u8981u4e0bu770bu5929u65f6u8fc7u51fau5c0fu4e48u8d77u4f60u90fdu628au597du8fd8u591au6ca1u4e3au53c8u53efu5bb6u5b66u53eau4ee5u4e3bu4f1au6837u5e74u60f3u751fu540cu8001u4e2du5341u4eceu81eau9762u524du5934u9053u5b83u540eu7136u8d70u5f88u50cfu89c1u4e24u7528u5979u56fdu52a8u8fdbu6210u56deu4ec0u8fb9u4f5cu5bf9u5f00u800cu5df1u4e9bu73b0u5c71u6c11u5019u7ecfu53d1u5de5u5411u4e8bu547du7ed9u957fu6c34u51e0u4e49u4e09u58f0u4e8eu9ad8u624bu77e5u7406u773cu5fd7u70b9u5fc3u6218u4e8cu95eeu4f46u8eabu65b9u5b9eu5403u505au53ebu5f53u4f4fu542cu9769u6253u5462u771fu5168u624du56dbu5df2u6240u654cu4e4bu6700u5149u4ea7u60c5u8defu5206u603bu6761u767du8bddu4e1cu5e2du6b21u4eb2u5982u88abu82b1u53e3u653eu513fu5e38u6c14u4e94u7b2cu4f7fu5199u519bu5427u6587u8fd0u518du679cu600eu5b9au8bb8u5febu660eu884cu56e0u522bu98deu5916u6811u7269u6d3bu90e8u95e8u65e0u5f80u8239u671bu65b0u5e26u961fu5148u529bu5b8cu5374u7ad9u4ee3u5458u673au66f4u4e5du60a8u6bcfu98ceu7ea7u8ddfu7b11u554au5b69u4e07u5c11u76f4u610fu591cu6bd4u9636u8fdeu8f66u91cdu4fbfu6597u9a6cu54eau5316u592au6307u53d8u793eu4f3cu58ebu8005u5e72u77f3u6ee1u65e5u51b3u767eu539fu62ffu7fa4u7a76u5404u516du672cu601du89e3u7acbu6cb3u6751u516bu96beu65e9u8bbau5417u6839u5171u8ba9u76f8u7814u4ecau5176u4e66u5750u63a5u5e94u5173u4fe1u89c9u6b65u53cdu5904u8bb0u5c06u5343u627eu4e89u9886u6216u5e08u7ed3u5757u8dd1u8c01u8349u8d8au5b57u52a0u811au7d27u7231u7b49u4e60u9635u6015u6708u9752u534au706bu6cd5u9898u5efau8d76u4f4du5531u6d77u4e03u5973u4efbu4ef6u611fu51c6u5f20u56e2u5c4bu79bbu8272u8138u7247u79d1u5012u775bu5229u4e16u521au4e14u7531u9001u5207u661fu5bfcu665au8868u591fu6574u8ba4u54cdu96eau6d41u672au573au8be5u5e76u5e95u6df1u523bu5e73u4f1fu5fd9u63d0u786eu8fd1u4eaeu8f7bu8bb2u519cu53e4u9ed1u544au754cu62c9u540du5440u571fu6e05u9633u7167u529eu53f2u6539u5386u8f6cu753bu9020u5634u6b64u6cbbu5317u5fc5u670du96e8u7a7fu5185u8bc6u9a8cu4f20u4e1au83dcu722cu7761u5174u5f62u91cfu54b1u89c2u82e6u4f53u4f17u901au51b2u5408u7834u53cbu5ea6u672fu996du516cu65c1u623fu6781u5357u67aau8bfbu6c99u5c81u7ebfu91ceu575au7a7au6536u7b97u81f3u653fu57ceu52b3u843du94b1u7279u56f4u5f1fu80dcu6559u70edu5c55u5305u6b4cu7c7bu6e10u5f3au6570u4e61u547cu6027u97f3u7b54u54e5u9645u65e7u795eu5ea7u7ae0u5e2eu5566u53d7u7cfbu4ee4u8df3u975eu4f55u725bu53d6u5165u5cb8u6562u6389u5ffdu79cdu88c5u9876u6025u6797u505cu606fu53e5u533au8863u822cu62a5u53f6u538bu6162u53d4u80ccu7ec6";
29         for (int i = 0; i < 4; i++) {
30             g.setColor(new Color(randNum(0, 255), randNum(0, 255), randNum(0, 255)));
31             g.setFont(new Font("黑体", Font.BOLD, bang));
32             int r = randNum(-45, 45);
33             g.rotate(1.0 * r / 180 * Math.PI, xpyl + (i * 30), ypyl);
34             g.drawString(base.charAt(randNum(0, base.length() - 1)) + "", xpyl + (i * 30), ypyl);
35             g.rotate(1.0 * -r / 180 * Math.PI, xpyl + (i * 30), ypyl);
36         }
37         // 将图片输出到浏览器
38         ImageIO.write(img, "jpg", response.getOutputStream());
39     }
40 
41     private Random rand = new Random();
42 
43     private int randNum(int begin, int end) {
44         return rand.nextInt(end - begin) + begin;
45     }
46 
47     public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
48         doGet(request, response);
49     }
50 
51 }

  9.Response注意的内容:
    9.1对于一次请求,Response的getOutputStream方法和getWriter方法是互斥,只能调用其一,特别注意forward后也不要违反这一规则。
    9.2利用Response输出数据的时候,并不是直接将数据写给浏览器,而是写到了Response的缓冲区中,等到整个service方法返回后,由服务器拿出response中的信息组成HTTP响应消息返回给浏览器。
    9.3service方法返回后,服务器会自己检查Response获取的OutputStream或者Writer是否关闭,如果没有关闭,服务器自动帮你关闭,一般情况下不要自己关闭这两个流。

六、Request:Request代表请求对象,其中封装了对请求中具有请求行、请求头、实体内容的操作的方法

  ServletRequest -- 通用request,提供一个request应该具有的最基本的方法         

     |        

     |--HttpServletRequest -- ServletRequest的孩子,针对http协议进行了进一步的增强          

  1.获取客户机信息   

    getRequestURL方法返回客户端发出请求完整URL   

    getRequestURI方法返回请求行中的资源名部分,在权限控制中常用   

    getQueryString 方法返回请求行中的参数部分   

    getRemoteAddr方法返回发出请求的客户机的IP地址   

    getMethod得到客户机请求方式   

    getContextPath 获得当前web应用虚拟目录名称,特别重要!!!,工程中所有的路径请不要写死,其中的web应用名要以此方法去获得。

  2.获取请求头信息   

    getHeader(name)方法 --- String ,获取指定名称的请求头的值   

    getHeaders(String name)方法 --- Enumeration<String> ,获取指定名称的请求头的值的集合,因为可能出现多个重名的请求头   

    getHeaderNames方法 --- Enumeration<String> ,获取所有请求头名称组成的集合   

    getIntHeader(name)方法  --- int ,获取int类型的请求头的值   

    getDateHeader(name)方法 --- long(日期对应毫秒) ,获取一个日期型的请求头的值,返回的是一个long值,从1970年1月1日0时开始的毫秒值      

  *实验:通过referer信息防盗链 

1 String ref = request.getHeader("Referer");
2 if (ref == null || ref == "" || !ref.startsWith("http://localhost")) {
3     response.sendRedirect(request.getContextPath() + "/homePage.html");
4 } else {
5     this.getServletContext().getRequestDispatcher("/WEB-INF/fengjie.html").forward(request, response);
6 }

  3.获取请求参数   

    getParameter(name) --- String 通过name获得值   

    getParameterValues(name)  --- String[ ] 通过name获得多值 checkbox   

    getParameterNames  --- Enumeration<String> 获得所有请求参数名称组成的枚举   

    getParameterMap  --- Map<String,String[ ]> 获取所有请求参数的组成的Map集合,注意,其中的键为String,值为String[]      

  获取请求参数时乱码问题:    浏览器发送的请求参数使用什么编码呢?当初浏览器打开网页时使用什么编码,发送就用什么编码。服务器端获取到发过来的请求参数默认使用ISO8859-1进行解码操作,中文一定有乱码问题    

  对于Post方式提交的数据,可以设置request.setCharacterEncoding("gb2312");来明确指定获取请求参数时使用编码。但是此种方式只对Post方式提交有效。    

  对于Get方式提交的数据,就只能手动解决乱码:String newName = new String(name.getBytes("ISO8859-1"),"gb2312");此种方法对Post方式同样有效。    

  在tomcat的server.xml中可以配置http连接器的URIEncoding可以指定服务器在获取请求参数时默认使用的编码,从而一劳永逸的决绝获取请求参数时的乱码问题。也可以指定useBodyEncodingForURI参数,令request.setCharacterEncoding也对GET方式的请求起作用,但是这俩属性都不推荐使用,因为发布环境往往不允许修改此属性。           

  4.利用请求域传递对象   request对象同时也是一个域对象,开发人员通过request对象在实现转发时,把数据通过request对象带给其它web资源处理   方法:setAttribute方法 getAttribute方法  removeAttribute方法 getAttributeNames方法

    生命周期:在service方法调用之前由服务器创建,传入service方法。整个请求结束,request生命结束。   

    作用范围:整个请求链。   

    作用:在整个请求链中共享数据,最常用的:在Servlet中处理好的数据要交给Jsp显示,此时参数就可以放置在Request域中带过去。     

  5.request实现请求转发   ServletContext可以实现请求转发,request也可以。   

    在forward之前输入到response缓冲区中的数据,如果已经被发送到了客户端,forward将失败,抛出异常。   

    在forward之前输入到response缓冲区中的数据,但是还没有发送到客户端,forward可以执行,但是缓冲区将被清空,之前的数据丢失。注意丢失的只是请求体中的内容,头内容仍然有效。   

    在一个Servlet中进行多次forward也是不行的,因为第一次forward结束,response已经被提交了,没有机会再forward了。总之,一条原则,一次请求只能有一次响应,响应提交走后,就再没有机会输出数据给浏览器了。     

  6.RequestDispatcher进行include操作:forward没有办法将多个servlet的输出组成一个输出,因此RequestDispatcher提供了include方法,可以将多个Servlet的输出组成一个输出返回个浏览器    

    request.getRequestDispatcher("/servlet/Demo17Servlet").include(request, response);  

    response.getWriter().write("from Demo16");  

    request.getRequestDispatcher("/servlet/Demo18Servlet").include(request, response);   

   常用在页面的固定部分单独写入一个文件,在多个页面中include进来简化代码量。

七、会话技术

  1.会话技术:从浏览器开始访问服务器,到关闭浏览器,这期间发生了许多次请求和响应,这个过程就叫做一次会话。
  2.问题:如何在一次会话中保存会话相关的数据。
  3.Cookie:将会话相关的数据保存到浏览器中,并且在每次访问服务器时都带过去。
    3.1javax.servlet.http.Cookie,可以直接利用此类的构造方法创建一个Cookie,创建出来的Cookie需要设置一个名称和值
    3.2response身上具有addCookie的方法,可以将创建出来的组织成响应消息中的set-cookie头,通知浏览器保存该cookie
    3.3request身上具有getCookies方法,可以获取浏览器带过来的所有Cookie
    3.4Cookie方法:注意,浏览器是根据cookie的名称加上cookie的path来区分是否是同一个cookie的,如果需要覆盖之前的cookie,除了保证名称相同外还要保证path也相同。
    public Cookie(String name,String value)利用构造方法创建一个Cookie对象,在创建的时候就要指定该Cookie的名和值
    setValue与getValue方法  设置或者获取Cookie的值
    setMaxAge与getMaxAge方法  如果不设置cookie的MaxAge(或将其值设置为负值),则默认情况下浏览器会将cookie保存在浏览器的内存中,会随着浏览器关闭而消失。如果设置为一个正值,则代表该Cookie要保存的以秒为单位的时间值,如此,该cookie将会被浏览器保存到硬盘中去。如果将MaxAge设置为0,则是通知浏览器去删除该Cookie。
    setPath与getPath方法   用来指定访问哪个ULR及其子URL时带上此cookie,如果不设置此值,则浏览器默认会将发送该cookie的servlet所在的路径作为path使用。
    例如:
      setPath("/Day06")则/Day06/.../...的路径都会带上该Cookie
      如果发送该Cookie的Servlet是 /Day06/servlet/Demo1Servlet,并且未设置setPat,则浏览器在访问/Day06/servlet/..时会带上该cookie

    setDomain与getDomain方法  设置cookie对应的域名,此方法一旦调用,则浏览器会认为该cookie是一个第三方cookie而拒收
    getName方法 获取该cookie的名字,注意没有setName方法,一个Cookie一旦创建出来就不能修改名字了
   
    3.5浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
示例: cookie显示最近浏览的商品

  1 Book.java
  2 public class Book implements Serializable {
  3     private String id;
  4     private String name;
  5     private String price;
  6     private String auth;
  7     private String publish;
  8     private String description;
  9     
 10     public Book() {
 11     }
 12     
 13     public Book(String id, String name, String price, String auth,
 14             String publish, String description) {
 15         super();
 16         this.id = id;
 17         this.name = name;
 18         this.price = price;
 19         this.auth = auth;
 20         this.publish = publish;
 21         this.description = description;
 22     }
 23 
 24     public String getDescription() {
 25         return description;
 26     }
 27 
 28     public void setDescription(String description) {
 29         this.description = description;
 30     }
 31 
 32     public String getId() {
 33         return id;
 34     }
 35     public void setId(String id) {
 36         this.id = id;
 37     }
 38     public String getName() {
 39         return name;
 40     }
 41     public void setName(String name) {
 42         this.name = name;
 43     }
 44     public String getPrice() {
 45         return price;
 46     }
 47     public void setPrice(String price) {
 48         this.price = price;
 49     }
 50     public String getAuth() {
 51         return auth;
 52     }
 53     public void setAuth(String auth) {
 54         this.auth = auth;
 55     }
 56     public String getPublish() {
 57         return publish;
 58     }
 59     public void setPublish(String publish) {
 60         this.publish = publish;
 61     }
 62 
 63 }
 64 
 65 BookDao.java
 66 public class BookDao {
 67     private static Map<String,Book> bookMap = new LinkedHashMap<String, Book>();
 68     
 69     private BookDao() {
 70     }
 71     static{
 72         bookMap.put("1", new Book("1","三国演义","99.0","朴乾","黑马出版社","一群男人纠结不清的故事...."));
 73         bookMap.put("2", new Book("2","西游记","10.0","曹睿","传智出版社","一个和尚一个猴子一头猪和一个秃子去西天的故事..."));
 74         bookMap.put("3", new Book("3","水浒传","2.0","奥巴马","人民教育出版社","105个男人和3个女人闯荡江湖的故事"));
 75         bookMap.put("4", new Book("4","哈利波特","200.0","哈利波特","科技出版社","混乱不堪的故事..."));
 76     }
 77     
 78     public static Map<String,Book> getBooks(){
 79         return bookMap;
 80     }
 81     
 82     public static Book getBook(String id){
 83         return bookMap.get(id);
 84     }
 85 }
 86 
 87 BookListServlet.java
 88 public class BookListServlet extends HttpServlet {
 89 
 90     /**
 91      * 
 92      */
 93     private static final long serialVersionUID = -1270524418156452134L;
 94 
 95     public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 96         
 97         response.setContentType("text/html;charset=utf-8");
 98         
 99         // 1.查询数据库中所有的书展示
100         Map<String, Book> map = BookDao.getBooks();
101         for (Map.Entry<String, Book> entry : map.entrySet()) {
102             Book book = entry.getValue();
103             response.getWriter().write("<a href='" + request.getContextPath() + "/servlet/BookInfoServlet?id="
104                     + book.getId() + "'>" + book.getName() + "</a><br>");
105         }
106         response.getWriter().write("<hr>");
107 
108         // 2.显示之前看过的书
109         Cookie[] cs = request.getCookies();
110         Cookie findC = null;
111         if (cs != null) {
112             for (Cookie c : cs) {
113                 if ("last".equals(c.getName())) {
114                     findC = c;
115                 }
116             }
117         }
118         if (findC == null) {
119             response.getWriter().write("没有看过任何书!");
120         } else {
121             response.getWriter().write("您曾经浏览过的书:<br>");
122             String[] ids = findC.getValue().split(",");
123             for (String id : ids) {
124                 Book book = BookDao.getBook(id);
125                 response.getWriter().write(book.getName() + "<br>");
126             }
127         }
128     }
129 
130     public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
131         doGet(request, response);
132     }
133 
134 }
135 
136 BookInfoServlet.java
137 public class BookInfoServlet extends HttpServlet {
138     /**
139      * 
140      */
141     private static final long serialVersionUID = -4530178673682834862L;
142 
143     public void doGet(HttpServletRequest request, HttpServletResponse response)
144             throws ServletException, IOException {
145         response.setContentType("text/html;charset=utf-8");
146         //1.获取要看的书的id,查询数据库找出书,输出书的详细信息
147         String id = request.getParameter("id");
148         Book book = BookDao.getBook(id);
149         if(book==null){
150             response.getWriter().write("找不到这本书!");
151             return;
152         }else{
153             response.getWriter().write("<h1>书名:"+book.getName()+"</h1>");
154             response.getWriter().write("<h3>作者:"+book.getAuth()+"</h3>");
155             response.getWriter().write("<h3>售价:"+book.getPrice()+"</h3>");
156             response.getWriter().write("<h3>出版社:"+book.getPublish()+"</h3>");
157             response.getWriter().write("<h3>描述信息:"+book.getDescription()+"</h3>");
158         }
159         
160         //2.发送cookie保存最后看过的书
161         // --- 1 --> 1
162         // 1 --2,1 --> 2,1
163         // 2,1--3,2,1 --> 3,2,1
164         // 3,2,1 -- 4,3,2 --> 4,3,2
165         // 4,3,2 --3,4,2 --> 3,4,2
166         String ids = "";
167         
168         Cookie [] cs = request.getCookies();
169         Cookie findC = null;
170         if(cs!=null){
171             for(Cookie c : cs){
172                 if("last".equals(c.getName())){
173                     findC = c;
174                 }
175             }
176         }
177         
178         if(findC == null){
179             //说明之前没有看过书的记录
180             ids += book.getId();
181         }else{
182             //说明之前有历史看过的书的记录,需要根据历史记录算一个新的记录出来
183             String [] olds = findC.getValue().split(","); 
184             StringBuffer buffer = new StringBuffer();
185             buffer.append(book.getId()+",");
186             for(int i = 0;i<olds.length && buffer.toString().split(",").length<3 ;i++){
187                 String old = olds[i];
188                 if(!old.equals(book.getId())){
189                     buffer.append(old+",");
190                 }
191             }
192             ids = buffer.substring(0, buffer.length()-1);
193         }
194         
195         
196         
197         Cookie lastC = new Cookie("last",ids);
198         lastC.setMaxAge(3600*24*30);
199         lastC.setPath(request.getContextPath());
200         response.addCookie(lastC);
201     }
202 
203     public void doPost(HttpServletRequest request, HttpServletResponse response)
204             throws ServletException, IOException {
205         doGet(request, response);
206     }
207 
208 }
View Code

  4.Session:在服务器中,为浏览器创建独一无二的内存空间,在其中保存会话相关的信息。
    4.1session作为域使用:他是j2ee中四大域对象之一,作用范围为整个会话。
    4.2session的生命周期:在第一次调用reqeust.getSession()方法的时候,服务器会检查是已经有对应的session,如果没有就在内存中创建一个session并返回。
    当一段时间内session没有被使用,一般为30分钟(此值可以在web.xml中配置<session-config>来配置,也可以使用TomcatManager进行配置),则服务器会销毁该session
         当服务器强行关闭时,没有到期的session也会跟着销毁。
         如果调用session提供的invalidate(),可以立即销毁session。
    4.3session的原理:在服务器第一次调用request.getSession()方法的时候,会在内存中创建一个session对象,此对象具有一个独一无二的id值,此id值将会以cookie(JSESSIONID)的形式发送给浏览器,浏览器以后每次访问都会带着此cookie,服务器就利用此cookie区分浏览器找到对应的session空间。
    4.4同一电脑内的不同浏览器使用同一session:JSESSIONID这个cookie默认是保存在浏览器内存中的,我们可以自己创建一个同名同path的Cookie,并设置maxage值,使其被保存在硬盘中,从而实现统一电脑中不同浏览器公用一个JSESSIONID从而使用同一个session。
    4.5使禁用Cookie的浏览器也可以使用session:由于session是基于cookie运行的,如果禁用了cookie则会导致session不可用,我们可以将提供给这种浏览器的所有的URL进行重写,在所有的URL后跟上JSEESIONID,从而保证即使禁用了Cookie也能以URL的形式带回JSESSIONID,从而可以使用session。要重写所有的URL是一项成本很高的工作,一般我们不会这么做。
    response. encodeRedirectURL(java.lang.String?url)如果此url是作为重定向操作的地址时使用此方法
    response. encodeURL(java.lang.String?url)如果此url是普通连接则使用此方法
示例:利用session实现简单的购物功能,并提供对同一台电脑上的多个浏览器共享session的支持以及对禁用cookie浏览器的支持。

 1 BuyServlet.java
 2 public class BuyServlet extends HttpServlet {
 3 
 4     public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
 5         String prod = request.getParameter("prod");
 6         prod = new String(prod.getBytes("iso8859-1"),"utf-8");
 7         
 8         HttpSession session = request.getSession();
 9         
10         Cookie jc = new Cookie("JSESSIONID",session.getId());
11         jc.setPath(request.getContextPath());
12         jc.setMaxAge(1800);
13         response.addCookie(jc);
14         
15             session.setAttribute("prod", prod);
16         
17     }
18 
19     public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
20         doGet(request, response);
21     }
22 
23 }
24 
25 PayServlet.java
26 public class PayServlet extends HttpServlet {
27 
28     public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
29         response.setContentType("text/html;charset=utf-8");
30         HttpSession session = request.getSession();
31         String prod = (String) session.getAttribute("prod");
32         response.getWriter().write("您购买的是"+prod+"价值99999999999元");
33     }
34 
35     public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
36         doGet(request, response);
37     }
38 
39 }
View Code

示例:使用Session完成用户登陆:当用户登录时在session中保存用户名,在其他页面就可以检查session中是否存在用户名,如果存在则认为已经登录过。注销的过程就是将session杀死的过程。

查看后期写的完整的用户登录的做法!!!
示例:使用session完成防止表单重复提交:当提供表单页面时,在表单中隐藏一个随机数值,并且将该随机数保存到session中,当表单提交时,检查提交上来到随机数与session中的随机数是否相同,如果相同则允许注册,注册后立即删除session中的随机数,如果不同则认为是表单的重复提交。
    *request.getSession()和request.getSession(false);的不同之处:前面的方法一调用,就会去检查是否有对应的session,没有就创建,有就取回。后面的方法只会去检查,如果有就取回,如果没有也不创建。
  
  5.ServeltContext 、reqeust、session域的比较
    servletContext 的作用域是整个web应用,随着服务器启动而创建,如果应用被移除出主机或服务器关闭则销毁。
    request 的作用域是整个请求链,每一次请求都会创建一个request,当请求结束时request销毁。
    session 的作用于是整个会话,第一次调用reqeust.getSession时创建,当一段时间没有使用或服务器关闭或调用session.invalidate方法时销毁
 
 
    什么时候用ServeltContext什么时候用reqeust什么时候用session?
    如果一个数据只是用来显示的话就用request域
    如果一个数据除了用来显示以外我一会还要用,这时候用session
    如果一个数据除了用来显示以外还要给别人用,这时候用ServletContext域

原文地址:https://www.cnblogs.com/ckysea/p/4628702.html