一、Request,Response 对象的设计模式

  1. 外观模式,属于结构型的模式。解决的问题:当我们要使用的系统或者某个类的功能很复杂,会造成使用的复杂度;另外需要使用的类中有一些功能属于系统级的功能,不建议或者不允许开发者使用。为了解决以上问题,可以采用外观模式对合适的类进行一层包装,把包装的类提供给开发者使用,包装类中的所有方法就是开发者可以使用的方法,开发者使用这些方法足以完成所有的任务。也称为门面模式。
  2. 在容器中,请求、响应、会话、应用上下文对象都具有以上的问题,它们都很复杂,有一些方法是容器自己使用的,不能提供给外部使用。
  • 以下结合请求对象来进行分析。

(1)org .apache.coyote.Request 类,它就是最原始的请求对象类型,它并未实现 ServletRequest 接口。

(2)org.apache.catalina.connector.Request 类,它是基于以上 Request 的功能创建的一个类,它实现了 SerletRequest 接口。它内部有一个成员变量是 protected org.apache.coyote.Request coyoteRequest,说明第二个请求对象的某些功能实现要依靠第一个请求对象的功能。这种在一个对象中包含另一个对象,还利用该对象的功能来实现自已的功能,这种设计方式称为组合。

(3)Servlet 的开发者可以直接使用第二个请求对象来使用 HttpServletRequest 接口的所有功能,但是,该对象包含了一些不应该由开发者使用的功能,这些功能是 Catalina 容器的功能属于系统的功能,如果开发者使用这些不该使用的功能,可能会造成系统的破坏。

(4)为了解决以上的问题,javaEE提供了public class RequestFacade implements HttpServletRequest 类,该类采用了外观模式进行设计。与外观模式相符的特征如下:

protected Request request= null;//组合了第二个Request对象
//凡是属于接口方法的功能实现都是通过使用组合的请求对象来实现的。
public void setAttribute (String name, Object o) {
	if (request == null) {
		request. setAttribute(name, o);
}

(5)开发者直接使用 RequestFacade 这个门面类就可以使用所有接口中的方法,也就可以完成所有需要完成的功能。那么 RequestFacade 采用的就是外观模式。

二、请求对象的功能及用法

  • 请求对象是 JavaWeb 程序中最常用的对象之一,几乎所有功能的实现都与该对象有关系。它实现了 HttpServletRequest 接口。
  • 开发者在使用请求对象时,其实是面向 HttpServletRequest 接口来使用。请求对象中封装了所有与请求有关的信息。
  1. 提供了多个 getXXX() 方法来得到各种信息。多数都会用到。
  2. 提供了两个 setXXX() 方法,其中 setAttribute(String s,Object o) 是最常用的方法之一,用来向请求对象封装属性值;setCharacterEncoding(String s) 方法用来指定请求内容的编码格式。
  3. 请求对象的 get 和 set 方法是重点方法。

三、请求对象的使用重点

  1. 用请求对象接收请求参数
String id = request.getParameter("id");//用参数名作为方法的参数,返回参数值,返回的是字符串类型。
  • 通过地址栏及超链接发出的请求都是 GET 请求,请求参数放在 URL 后面,用 ? 分割,参数之间用 & 分隔。例如:myservleturl?id=123&name=abc
  • 通过表单发 get 请求,表单控件的 method 取值为 get,action 属性的值就是表单的提交对象(一般是 servlet),servlet 可以接收表单上用户录入的数据。action 的属性不要使用 ?,因为表单提交时,会自动的把控件的值放到请求的后面。
  1. 关于接收汉字的乱码问题(GET请求不用处理,POST需要)
  • 当请求参数中有汉字时,在后面接收到的参数值可能是乱码。解决的方式就是对原值进行转码,转码为本身的字符编码。
  • name = new String(name.getBytes(“iso-8859-1”),“utf-8”);
  • 以上对字符串的转换使用了两个编码名称参数,含义是把一一个用 iso-8859-1 编码的字符串转换为字节数组,然后再把该字节数组转换为 utf-8 的字符串。原因在于,发给 tomcat 的所有的请求参数都会被 tomcat 转换为 iso-8859-1 编码的二进制数据,所以我们通过请求对象获取时,如果是汉字,二进制的数据与汉字的编码集 utf-8,gbk 不相符,因此出现乱码。
  1. 关于其他接收参数的方法。
request.getParameterValues();//返回字符串数组,主要用在表单的多个控件同名的情况。最典型的就是复选框。
Map<String, String[]> parameterMap = request.getParameterMap();//返回所有请求参数的键值对。类型是Map,主要由框架的设计者使用,可以把结果Map封装到一个javaBean中,前提是使用该方法。
request.getParameterNames();//得到所有请求参数的名称,返回数组。很少用,也是框架再用。

四、使用 getParameterMap() 方法给 JavaBean 的应用实现参数接收的通用方法

  • 如果表单的参数非常多,使用 getParameter() 方法会造成代码的臃肿,如果用 JavaBean 来封装所有的参数,一次性读取参数值,显然会方便很多,springmvc 框架也是这样做的。
  1. 什么是 JavaBean?
  • 如果一个类具有无参的构造,私有成员变量,针对变量提供公共的 get,set 方法,这种类就是 JavaBean。它是封装的一种特例。它的作用一般用来封装数据,并在不同的类之间传递数据。
  • jdk 具有一个针对 JavaBean 处理的包,是 java.beans 包,里面提供了针对 JavaBean 内部属性及方法的处理类型。
  1. JavaBean 的 get,set 方法名称是规定的写法,由 get/set + 属性名大写开头的单词组成,所以属性名是由 get,set 方法的命名来决定的,而不是变量名决定。
  2. 实现用 JavaBean 来自动接收参数:实现了一个 Bean 工具类,提供一个静态方法接收Map和Bean类型的对象,然后对 Bean 对象进行赋值。在 Servlet 中调用以上方法,可以把多个参数封装到 JavaBean 对象中去。
package com.zhong;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

/**
 * @author 华韵流风
 */
public class Person {
    private Integer id;
    private String name;
    private Date date;
    private String[] goods;

    public String[] getGoods() {
        return goods;
    }

    public void setGoods(String[] goods) {
        this.goods = goods;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = new String(name.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name=" + name +
                ", date=" + new SimpleDateFormat("yyyy-MM-dd").format(date) +
                ", goods=" + Arrays.toString(goods) +
                '}';
    }
}
package com.zhong;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * @author 华韵流风
 */
public class BeanUtil {

    public  static <T> T toBean(Map<String,String[]> map, Class<T> clazz){
        try {
            //实例化Bean类型的对象
            T obj = clazz.newInstance();
            //得到Bean类型的所有属性
            PropertyDescriptor[] pds = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
            //把map中的参数名与bean中的属性名进行匹配,如果匹配上了,就把参数值封装到bean中
            for (PropertyDescriptor pd : pds) {
                String pName = pd.getName();
                //检查map中是否有与属性名相同的key。
                System.out.println(pd.getName()+"/"+pd.getPropertyType());
                if(map.containsKey(pName)){
                    //进行数据的封装,依据属性的类型分别进行处理
                    //因为map中所有的value都是String
                    if(pd.getPropertyType() == Integer.class){
                        //调用Bean的set方法把数据写到bean中
                        pd.getWriteMethod().invoke(obj,Integer.valueOf(map.get(pName)[0]));
                    }else if(pd.getPropertyType() == Long.class){
                        pd.getWriteMethod().invoke(obj,Long.valueOf(map.get(pName)[0]));
                    }else if(pd.getPropertyType() == String.class){
                        pd.getWriteMethod().invoke(obj,map.get(pName)[0]);
                    }else if(pd.getPropertyType() == Date.class){
                        pd.getWriteMethod().invoke(obj,new SimpleDateFormat("yyyy-MM-dd").parse(map.get(pName)[0]));
                    }else if(pd.getPropertyType() == String[].class){
                        pd.getWriteMethod().invoke(obj, (Object) map.get(pName));
                    }
                }
            }
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
package com.zhong;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;

/**
 * @author 华韵流风
 */
@WebServlet(name = "MyServlet")
public class MyServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("servlet 被初始化");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     
        //调用BeanUtil中的方法把请求参数转换为一个Bean的对象
        Person person = BeanUtil.toBean(request.getParameterMap(), Person.class);
        System.out.println(person);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    @Override
    public void destroy() {
        System.out.println("servlet 被销毁");
    }
}

五、请求的转发

  • 当请求到达 servlet,当前的 servlet 如果不能够完整的对请求进行处理(甚至于根本无法处理),就会把当前的请求转发到另一个可以处理该请求的 servlet 进行处理。实现该步骤技术就是请求转发。
  1. RequestDispatch 对象,请求转发对象,只有得到此对象才能实现请求转发。
  • RequestDispatcher dispatcher = request.getRequestDispatcher(请求的路径);
  1. 利用请求转发对象转发请求,把当前的请求和响应对象传递过去。
  • dispatcher.forward(request, response);
  • 注意,当请求转发时,源 servlet 的请求对象会做一个转换,转换为另一种类型的请求对象。响应对象直接传递。当请求对象的类型发生变化时,请求对象的功能肯定是不同的,说明在‘’目标 servlet 中的请求对象与源 servlet 中的请求对象是不同的。
  1. 当发生请求转发时,最常见的一种操作就是把源 Servlet 中的一些数据传递到目标 Servlet 中,可以理解为 Servlet 间的一种通信。因此多个 Servlet 就可以结合在一起共同完成一项任务。
  2. 在项目中,通常通过请求转发的操作把 Servlet 向域对象中存放的数据传递到 jsp 页面中,由页面来渲染数据。jsp 本身就是一个 Servlet。
  3. 什么是域对象?
  • 在 JavaWeb 项目的进程中,提供了几个由容器创建的域对象,比如请求对象,会话对象,应用上下文对象,页对象。
  • 域对象有三个典型的特征,第一:它们由容器创建;第二:它们都有自己的作用范围;第三:它们内部都包含一个 Map,它们都是通过 setAttribute() 和 getAttribute() 方法来向其中的 map 存入属性值及取出属性值,对应于 map 操作的 put() 和 get() 的使用。
  • 请求对象的作用域范围(从出生到死亡所经历的过程),从创建请求开始,凡是可使用该请求的对象,都是有效的,直到响应发出后就消失了。因此每一次请求动作都会创建一个新的请求对象,它的生命持续时间与中间处理过程消耗的时间有关系,过程越长它的生命就越长,一般它的生命周期只有毫秒级。
  1. 请求转发注意的问题
  • 在请求转发之前,不要使用响应对象向客户端输出任何信息,就算输出了信息,输出缓冲区也会清空,这种操作是无效的。

六、响应对象

  • 它与请求对象同时被创建,也同时被传入 Servlet,当请求的过程结束时,它们同时被销毁。
  • 响应对象的用途:
  1. 提供了两种输出流对象,分别是字节输出流和字符输出流。
    (1)字节输出流
  • 一般情形下,通过它向客户端发送二进制数据。
ServletOutputStream sos = response.getOutputStream();
sos.write("张三".getBytes("gb2312"));
  • 利用字节输出流向客户端发送二进制文件,比如在客户端展示图片,需要客户端下载文件
  • 如果展示图片,只需要向输出流中输出图片的二进制数据
ServletContext context = request.getServletContext();
String realPath = context.getRealPath("img/img.jpg");
ServletOutputStream sos = response.getOutputStream();
FileInputStream fis = new FileInputStream(realPath);
//如果需要下载文件,需要特别指定相应的响应头。
response.setHeader("Content-Disposition", "attachment;filename=img.jpg");
int len;
byte[] b = new byte[1024];
while ((len = fis.read(b)) != -1) {
    sos.write(b, 0, len);
}
sos.flush();
fis.close();
sos.close();
  • 如果需要下载文件,需要特别指定相关的响应头: response.setHeader( ”Content-Disposition”,”attachment;filename=img.jpg”);
  • 注意:通过输出流的输出,输出的内容并不是直接到达客户端,而是先由容器进行一次格式化, 通过格式化后整个输出的数据格式就符合 http 协议的要求,这个过程中会生成响应状态行,也会产生响应消息头,把输出的数据放在响应正文中,再通过底层的 tcp 协议向客户端进行发送。

(2)字符输出流

  • 向客户端发送字符数据,比如一段字符串,比如一段完整的网页内容。
    PrintWriter pw = response.getWriter();
    pw.print(“欢迎”);
  • 出现中文乱码的原因:当输出内容交给容器后,容器依然认为是 iso-8859-1 的编码,此时就要明确的告诉客户端文字的编码集。
    response.setContentType(“text/html;charset=utf-8”);
  • 怎样知道客户端是用什么编码集:首先看浏览器指定的编码集是什么,另外看网页中指定的是哪种编码。
  • 以上两种输出流不能够同时使用,否则会抛异常。

七、利用响应对象实现请求重定向

  • 当一个 Servlet 接收到请求后,它认为自己无法处理该请求,但是此时又不需要传递数据,那么它就会让浏览器重新发出一个请求到另一个 servlet。
    response.sendRedirect(request.getContextPath() + “/otherSvl”);
  • 请求转发与请求重定向的区别
  1. 请求转发并不产生新的请求,只是把当前的请求对象转移到目标 Servlet,此时浏览器的地址不发生变化(不会变成目标 servlet 的地址)。
  2. 请求重定向会结束当前的请求过程,让浏览器重新发出一个新的请求到目标 servlet,此时浏览器的地址会发生变化,会变成目标 servlet 的地址。
  3. 在项目设计中,如果源 servlet 需要向目标 servlet 发送数据(无论什么类型),就使用请求转发,否则使用请求重定向。
  4. 请求转发是服务器内部的操作,与浏览器没有关系;请求重定向是服务器通过响应消息头向浏览器发送消息,此份消息中就包含了重定向的地址,因此它是服务器与客户端的一种通讯。
  5. 请求转发要使用请求转发对象,请求重定向使用响应对象。
  • 在 servlet 中,如果已经执行了请求重定向,那么不能再执行请求转发,否则抛异常。在请求的处理过程中,最后要么进行请求转发,要么进行请求重定向。
  • 响应对象不是域对象,因此内部没有存放属性值的 Map。

八、ServletConfig 对象

  • 该对象由容器创建,在 Servlet 初始时传入 servlet 中,而 Servlet 中有一个成员变量就是 private transient ServletConfig config;它称为 Servlet 的配置对象,该对象的作用域及生命周期与请求对象是一样的,它不是域对象。
  1. 通过 getServletConfig() 方法得到该对象。
  2. 它与 Servlet 的配置参数有紧密关系,我们可以指定 Servlet 的配置参数,然后通过该对象来读取。
String encoding = getServletConfig().getInitParameter("encoding");
  • 可以从它得到 ServletContext 对象
ServletContext servletContext = getServletConfig().getServletContext();

九、ServletContext 对象

  • 当项目被容器加载,容器会为每个项目都创建一个 ServletContext 对象,任何项目中有且只有一个该对象,该对象在本项目内所有的 Servlet 中都可以使用。
  • 作用如下:
  1. 它本身属于域对象,它的作用域范围是整个应用,它的持续时间从项目被加载一直到项目被销毁。所有它所包含的一些属性值可以在整个项目中进行共用。
  2. 比如统计项目的访问次数:
ServletContext context = request.getServletContext();
        //记录总的访问次数
        Integer count = (Integer) context.getAttribute("count");
        if (count == null) {
            context.setAttribute("count", 1);
        } else {
            context.setAttribute("count", count + 1);
        }

        System.out.println("总的访问次数是:"+context.getAttribute("count"));
  • 可以得到项目内资源的绝对路径
String path = context.getRealPath("相对路径");
  1. 读取全局的配置参数,在 web.xml 中增加相应配置
<!--  全局配置参数  -->
    <context-param>
        <param-name>param</param-name>
        <param-value>1000</param-value>
    </context-param>

十、会话

  • 会话是浏览器与服务器之间保持一种特定状态的机制。http 协议是一种无状态的协议,体现在于每一次的请求,服务器都不知道它与上一次的某个请求是否来自于同一个浏览器。
  1. 对于协议无状态的问题,对项目的实现会造成很大影响,无法记住每个用户的确定信息。为了解决此问题,提供了两种技术手段。
  2. JavaWeb 提供了服务器端的会话对象,浏览器提供了客户端的 Cookie 对象。
  3. Session 对象
  • 一般称为会话对象,主要作用可以在规定的时间内保存与客户端相关的一些数据,最典型的应用就是购物车。它的特点是:第一,保存在服务器的内存中;第二,它有存活期;第三,它可以存放数据。针对特定的客户端(浏览器)在服务器中最多存在一个会话对象,如果给客户端分配了会话对象,服务器在会话的生存期内就可以知道每一个请求对应哪个客户端。

(1)创建 Session 对象

  • 会话对象不由设计者创建,由请求对象在应用需要会话对象时才创建。
    HttpSession session = request.getSession();
session.getCreationTime();//得到创建时间

该方法执行后,如果当前客户端没有对应的会话对象,就会创建一个会话对象,放在服务器的内存中;如果客户端已经有了会话对象则不会创建,而是把这个会话交给 session 变量引用。

session.isNew();//返回当前会话对象是否是刚创建的,在同一请求过程中创建的会话对象就是刚创建的。

(2)使用 Session 对象

  • 如果我们需要为客户端保存一些数据,而且还要把这些数据保存在服务器上,就会考虑使用会话对象。
  • 会话对象也是域对象,所以提供了 set 和 get 属性的方法。通过 set 方法可以把数据存放在会话中,对于对应的客户端在浏览器未关闭前可以持续的使用这份数据。
HttpSession session = request.getSession();//相当于有参的方法参数为 true的情形。
//System.out.println(session.isNew());
//存放客户端最后一次访问的时间
if (session.getAttribute("lastTime") == null) {
    session.setAttribute("lastTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime()));
    System.out.println("第一次访问");
 } else {
    System.out.println("最后一次访问时间是:" + session.getAttribute("lastTime"));
}
session.removeAttribute("");//删除属性

(3)Session 对象的生存期

  • 在 tomcat 中有如下配置
<!-- ==================== Default Session Configuration ================= -->
     <!-- You can set the default session timeout (in minutes) for all newly   -->
     <!-- created sessions by modifying the value below.                       -->
     
       <session-config>
           <session-timeout>30</session-timeout>
       </session-config>

如果不指定会话的生存期,它的生存期就是 30 分钟。

默认情况下,当浏览器关闭,服务器上对应的 session 就会被清除,以上配置的超时时间是在默认情况下(浏览器未关闭),两次请求的间隔时间超过30分钟,session 会被清除。

session.setMaxInactiveInterval(10);//单位是分钟
   session.getMaxInactiveInterval();//得到存活期

如果调用 session.invalidate(); 方法,session 对象会立刻被清除。

(4)服务器上的每个会话都有一个唯一的 id 值,该值用来识别不同的会话。

session.getId();//得到id值
  1. Cookie 对象
  • 为客户端的会话对象,把与客户相关的数据存放在客户端。一般存放在对应浏览器的安装目录下。
  1. 使用 Cookie:客户的数据如果存放在服务器会有两个问题,第一会占用服务器的内容;第二不利于长时间的保留。相应的解决手段就是使用 Cookie 。
  • Cookie 可以长时间的存放在客户端,对服务器没有任何影响;因此可以存放更大的数据。如果重装系统或浏览器 Cookie 就随之消失。
  1. 创建 Cookie
  • 在服务器端创建,一个键值对就是一个 Cookie 对象。
Cookie cookie = new Cookie("lastTime", new SimpleDateFormat("yyyy-MM-dd//HH:mm:ss").format(Calendar.getInstance().getTime()));//值只能是字符串,且其中不能有空格,否则报错
  1. 把 Cookie 写到客户端
response.addCookie(cookie);//写到客户端去的Cookie就不是一个对象,只能称为一段信息,在后续的请求中会被传递到服务器。
  • 提示:服务器可以为每个客户端创建多个 Cookie。
  1. 在服务器上得到客户端的 Cookie:
Cookie[] cookies = request.getCookies();
if (cookies != null) {
    for (Cookie cookie1 : cookies) {
        System.out.println(cookie1.getName() + ":" + cookie1.getValue());
    }
}
  1. 在 Cookie 中保存汉字:在老版本的 tomcat 中,汉字不能写入 Cookie 中。
  2. Cookie 的生存期
  • 默认情况下,浏览器关闭时,Cookie 就会被清除。但是可以指定它的生存期,cookie.setMaxAge(24 * 60 * 60);//单位是秒,设置为0则Cookie会被清除此时生存期与浏览器的关闭无关。
cookie.setMaxAge(24 * 60 * 60);//单位是秒,设置为0则Cookie会被清除
  1. 指定 Cookie 与应用或应用中的路径的关系
cookie.setPath("/");//默认情况下,属于本应用的任何请求都会把 Cookie 传递到服务器,使用最多。
cookie.setPath("/jsp");//路径以jsp开头的请求才会把Cookie发送到服务器。
  1. Cookie 与 Session 的关系
  • Cookie 与 Session 是一一对应的,如果服务器为客户端创建了 Session,必须在客户端写入一份 Cookie,这份 Cookie 中保存了该份 Session 的 id,以后的每次请求都会把这份 Cookie 发送到服务器,服务器就会取出该 id,然后与 Session 的 id 进行比对,比对上的 Session 就是当前客户端的 Session。