Servlet相关内容

14

Web

啥是Web?

  • World Wide Web的简写,或者WWW。
  • 万维网,简单理解就是网站,用来表示Internet主机上供外界访问的资源。

供外界访问的资源分为两大类:

  • 静态资源:HTML、CSS、JS
  • 动态资源:Servlet、JSP、其他ASP,net、php、python

在java中,动态Web资源开发技术我们统称为Java Web。

Web服务器

Web服务器:

  • Web服务器是运行及发布Web应用的容器。
  • 只有将开发的Web项目放置到容器中,才能使网络中的用户通过浏览器进行访问。

常见服务器:

开源:OpenSource(1.开放源代码 2.免费)

  • Tomcat(主流Web服务器之一)
  • Jetty
  • Resin

收费:

  • WebLogic(Oracle)
  • WebSphere(IBM)
  • 提供相应的服务与支持、安全、可靠性更高

Tomcat服务器

  • Tomcat是Apache软件基金会的Jakarta项目中的一个核心项目。
  • 免费开源,并支持Servlet和JSP规范。

目录结构:

  • bin
    • 可执行脚本 startup.bat启动Tomcat shutdown.bat停止tomcat
  • conf
    • server.xml: 配置整个服务器信息 web.xml:项目部署描述符文件
  • lib
    • 存放 运行时所需的jar文件
  • logs
    • 存放日志文件
  • temp
    • tomcat的临时文件
  • webapps
    • 存放web项目,每个文件夹都是一个项目
    • ROOT地址栏中没有给出项目目录时,对应的就是ROOT目录
  • work 运行时生成的文件,最终运行的文件都在这里。
    • 当用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,生成的java和class文件都会存放到这个目录下。

启动:进入tomcat目录bin下,双击startup.bat启动程序。

访问:打开浏览器,输入http://localhost:8080。

[wppay]

停止:

  • 双击shutdown.bat即可关闭Tomcat启动窗口
  • 直接强制关闭。

修改端口号:

Tomcat默认端口号为8080,可以通过conf/server.xml文件修改。

修改后要重启tomcat才能生效。

项目部署到Tomcat

  • 项目部署及资源访问:
    • Tomcat是Web服务器,项目应用是部署在webapps下,然后通过特定的URL访问。
  • 创建项目:
    • 在webapps中建立myweb文件夹(项目应用)
      • 创建WEB-INF文件夹,用于存放项目的核心内容。不能直接访问。
        • 创建classes文件夹,存放.class文件
        • 创建lib文件夹,存放相关jar文件。
        • 创建web.xml,项目配置文件。
      • 将静态资源hello.html复制到myweb文件夹中,与WEB-INF在同一级目录。
  • 资源访问
    • URL:Uniform Resource Locator 统一资源定位符(网址)
    • 浏览器地址中输入URL:http://localhost:8080/myweb/hello.html
    • URL组成:协议、主机、端口号、资源路径
    • http端口 80、https 443

Tomcat访问过程:客户端请求,服务端响应。

常见错误:

  • 访问资源不存在,出现404错误。检查路径以及资源名称。
  • 500服务器错误,程序有异常。

虚拟目录

  • 在webapps目录,没有这个目录,可以通过虚拟目录指向真实项目位置
    • server.xml文件中Host节点
    • 配置<Context docBase="真实目录" path="/虚拟目录" />
    • 需要重新启动tomcat
  • $CATALINA BASE/conf/catalina/localhost/文件夹下创建一个xml文件,任意文件名都可以,但是此文件名是web应用发布后的虚拟目录。
    • 比如创建一个test.xml,在文件中添加
    • 不需要重启服务器,只需要在浏览器中输入 http://localhost:8080/test/1.html即可访问C:\a\1.html。
    • 注意:这种方法不能指向webapps中的路径。

Servlet

概念

  • Server Applet的简称,是服务器端的程序(代码、功能实现)。
  • 可交互式的处理客户端发送到服务端的请求,并完成操作响应。
  • 动态网页技术。
  • JavaWeb程序开发的基础,JavaEE规范(一套接口)的一个组成部分。

作用:

  • 接收客户端请求,动态生成网页。
  • 调用业务逻辑层。
  • 转发、重定向。

手工创建Servlet

编写Servlet:

  • 实现javax.servlet.Servlet。
  • 重写5个主要方法。
  • 在核心的service()方法中编写输出语句,打印访问结果。

编译

部署:将生成的.class文件放在WEB-INF/calsses文件中

配置:编写项目配置文件web.xml。

工具创建Servlet

IDEA开发Servlet:

  • IDEA关联Tomcat
  • 创建Web项目
  • 创建Servlet
  • 配置Servlet
  • 部署Web项目

web.xml

项目首页配置

配置网站错误页面

其他操作

  • 导出war包
    • 项目完成后,可以打成war包直接放在tomcat的webapps目录下,启动tomcat后自动解压,即可访问。

然后Build一下,就会在out中生成一个war包。

  • 关联第三方jar包
    • 在WEB-INF目录下新建lib目录
    • 复制jar包到lib目录
    • 右击lib目录,选择Add as Library

HTTP协议

概念:

  • 超文本传输协议(HyperText Transfer Protocol)。
  • 是互联网上应用最为广泛的一种网络协议。
  • 是一个基于请求与响应模式的、无状态的、应用层的协议,运行于TCP协议基础之上。

特点:

  • 支持客户端(浏览器)/服务器模式。
  • 简单快速。
  • 灵活。无状态。

通信流程

  • 客户端与服务器建立连接(三次握手)。
  • 客户端向服务器发送请求。
  • 服务器接受请求,并根据请求返回相应的文件作为应答。
  • 客户端与服务器关闭连接(四次挥手)。

HTTP 1.0:

  • 连接过程是短暂的
  • 每次连接只处理一个请求和响应。
  • 每一个页面的访问,浏览器与WEB服务器都要建立一次单独的连接。
  • 所有通讯都是完全兜里分开的请求和响应。

HTTP 2.0:

  • 在一个TCP连接上可以传送多个HTTP请求和响应
  • 多个请求和响应过程可以重叠进行
  • 增加了更多的请求头和响应头
  • Connection报头来控制

面试题

有一个页面,页面中包含四张图片,问浏览器向服务器发送几次请求?

  • 5次请求
  • HTTP1.1 协议中建立一次TCP连接

HTTP协议

HTTP请求报文:

  • 当浏览器向Web服务器发送请求时,向服务器传递了一个数据块,就是请求报文。

HTTP响应报文:

当Web服务器收到浏览器的请求后,服务器要对报文作出响应,就是响应报文。

常见状态码:

抓包工具的使用:

Fiddler。下载下来,打开软件就会自动抓取你发出的请求或响应。点击一个右侧会有一些选择,原始数据是Raw,没有做任何的处理。

Servlet核心

概念:Server Applet 服务器小程序

Servlet核心的接口和类: 除了实现Servlet接口,还可以通过继承GenericServlet或HttpServlet类。

Servlet接口:

  • 所有Servlet都会直接或间接的与该接口发送联系。
  • 核心方法
    • init(ServletConfig config)
    • ServletConfig getServletConfig()
    • service(ServletRequest req,ServletResponse res)
    • String getServletInfo()
    • destory()
  • 实现Servlet接口
    • 该方式较为麻烦,需要实现接口中所有的方法
public class FirstServlet implements Servlet {

  //init:初始化servlet,只执行一次。第一次请求时初始化的。
  @Override
  public void init(ServletConfig servletConfig) throws ServletException {
    System.out.println("FirstServlet初始化了...init:" + this.hashCode());
    //读取参数
    String name = servletConfig.getInitParameter("name");
    String age = servletConfig.getInitParameter("age");
    System.out.println(name + "..." + age);
  }

  //getServletConfig:获取Servlet配置。
  @Override
  public ServletConfig getServletConfig() {
    System.out.println("获取Servlet配置...getServletConfig" + this.hashCode());
    return null;
  }

  //service:服务方法:提供响应的方法, 每次请求都会执行一次。
  @Override
  public void service(ServletRequest request, ServletResponse response)
      throws ServletException, IOException {
    System.out.println("服务方法执行了...service" + this.hashCode());
    PrintWriter out = response.getWriter();
    out.println("hello welcome use servlet!!!");
  }

  //getServletInfo: 获取servlet基本信息的方法
  @Override
  public String getServletInfo() {
    System.out.println("获取基本信息....getServletInfo" + this.hashCode());
    return null;
  }

  //destroy:销毁方法,只执行一次。
  @Override
  public void destroy() {
    System.out.println("销毁了...destroy" + this.hashCode());
  }
}

web.xml

  <servlet>
    <servlet-name>FirstServlet</servlet-name>
    <servlet-class>com.qf.servlet2.FirstServlet</servlet-class>
  </servlet>
  • 继承HttpServlet(推荐)
    • 重写doGet方法和doPost方法
  • HttpServlet抽象类
    • HttpServlet是在继承 GenericServlet的基础上做了进一步的扩展。
    • 专注于HTTP请求。
    • 对应HTTP请求方法
public class SecondServlet extends HttpServlet {

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

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    System.out.println("接受到请求了...." + this.hashCode());
    PrintWriter out = resp.getWriter();
    out.println("welecome use HttpServlet");
  }
}

web.xml

  <servlet>
    <servlet-name>SecondServlet</servlet-name>
    <servlet-class>com.qf.servlet2.SecondServlet</servlet-class>
  </servlet>
  • GenericServlet抽象类:(一般不用)
    • 提供init和destory方法的简单实现,只需要重写抽象service方法即可

Servlet配置方式(一)

  • 配置web.xml:提供<servlet>标签和<servlet-mapping>
  • 访问路径url-pattern属性:
  <servlet-mapping>
    <servlet-name>SecondServlet</servlet-name>
    <!--精确路径匹配-->
    <url-pattern>/secondservlet</url-pattern>
    <url-pattern>/secondservlet2</url-pattern>
    <!--后缀匹配-->
    <!--<url-pattern>*.do</url-pattern>-->
    <!--通配符匹配-->
    <!--/* : 表示匹配任意路径-->
    <!--<url-pattern>/*</url-pattern>-->
    <!--/ :匹配任意路径,包含服务器的所有资源,不包括.jsp-->
    <!-- <url-pattern>/</url-pattern>-->
    <!--其他的正确写法-->
    <!--1 /aaa/*-->
    <!--   <url-pattern>/aaa/*</url-pattern>-->
    <!--2 /aaa/*/bbb -->
    <!--<url-pattern>/aaa/*/bbb</url-pattern>-->
  </servlet-mapping>
  • 加载属性
  • 配置Servlet参数:
  <servlet>
    <servlet-name>FirstServlet</servlet-name>
    <servlet-class>com.qf.servlet2.FirstServlet</servlet-class>
    <!--servlet参数-->
    <init-param>
      <param-name>name</param-name>
      <param-value>zhangsan</param-value>
    </init-param>
    <init-param>
      <param-name>age</param-name>
      <param-value>20</param-value>
    </init-param>
    <!--load-on-startup 设置servlet创建的时机 默认-1 第一次访问时初始化。-->
    <load-on-startup>0</load-on-startup>
  </servlet>

Servlet配置方式(二)

注解:

  • Servlet3.0后支持。推荐使用
  • @WebServlet

配置属性:

@WebServlet(name = "FirstServlet", urlPatterns = {"/firstservlet",
    "/first"}, loadOnStartup = 0, initParams = {
    @WebInitParam(name = "username", value = "zhangsan"),
    @WebInitParam(name = "age", value = "20")})
public class FirstServlet extends HttpServlet {

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

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    System.out.println("使用注解方式配置Servlet");
    PrintWriter out = resp.getWriter();
    out.println("hello  welcome use servlet");
    //获取参数
    String username = this.getInitParameter("username");
    String age = this.getInitParameter("age");
    System.out.println(username + "..." + age);
  }
}

Servlet生命周期

概念:Servlet从创建到销毁的整个过程。

Servlet特性

线程安全:

  • Servlet在访问之后,会创建一个Servlet对象。
  • Tomcat容器可以同时多个线程并发访问同一个Servlet。
  • 如果在方法中对成员变量做修改操作,就会有线程安全的问题。

如何保证线程安全?

  • synchronized。
  • 实现SingleThreadModel接口。废弃。
  • 尽可能使用局部变量。

Request对象

概念:

  • 在Servlet中用来处理客户端请求需要用request对象。
  • request对象包含了客户端请求的所有内容。

常见方法:

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {}
public interface HttpServletRequest extends ServletRequest {}
public interface ServletRequest {
  Object getAttribute(String var1);

  Enumeration<String> getAttributeNames();

  String getCharacterEncoding();

  void setCharacterEncoding(String var1) throws UnsupportedEncodingException;

  int getContentLength();

  long getContentLengthLong();

  String getContentType();

  ServletInputStream getInputStream() throws IOException;

  String getParameter(String var1);

  Enumeration<String> getParameterNames();

  String[] getParameterValues(String var1);

  Map<String, String[]> getParameterMap();

  String getProtocol();

  String getScheme();

  String getServerName();

  int getServerPort();

  BufferedReader getReader() throws IOException;

  String getRemoteAddr();

  String getRemoteHost();

  void setAttribute(String var1, Object var2);

  void removeAttribute(String var1);

  Locale getLocale();

  Enumeration<Locale> getLocales();

  boolean isSecure();

  RequestDispatcher getRequestDispatcher(String var1);

  /** @deprecated */
  String getRealPath(String var1);

  int getRemotePort();

  String getLocalName();

  String getLocalAddr();

  int getLocalPort();

  ServletContext getServletContext();

  AsyncContext startAsync() throws IllegalStateException;

  AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws IllegalStateException;

  boolean isAsyncStarted();

  boolean isAsyncSupported();

  AsyncContext getAsyncContext();

  DispatcherType getDispatcherType();
}

GET请求:

  • GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连。
  • GET方式明文传递,数据量小,不安全。
  • 效率高,浏览器默认请求方式为GET请求。
  • 对应的Servlet的方法是doGet。

POST请求:

  • POST方法是把提交的数据放在HTTP包的Body中。
  • 密文传递数据,数据量大,安全。
  • 效率相对没有GET高。
  • 对应的Servlet的方法是doPost。

Post请求乱码

  • Post请求收参产生乱码:
    • 客户端以浏览器设置的编码将表单数据传输到服务器端。
    • 服务器默认是以ISO-8859-1编码接收。
  • 解决方案:
    • 使用request的setCharacterEncoding(charset)方法进行统一的编码设置。

测试代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>用户注册</title>
</head>
<body>
<h1>用户注册</h1>
<form action="registservlet" method="post/get" enctype="application/x-www-form-urlencoded">
  <table>
    <tr>
      <td>用户名</td>
      <td>
        <input type="text" name="username">
      </td>
    </tr>
    <tr>
      <td>密码</td>
      <td>
        <input type="password" name="pwd">
      </td>
    </tr>
    <tr>
      <td>确认密码</td>
      <td>
        <input type="password" name="repwd">
      </td>
    </tr>
    <tr>
      <td>性别</td>
      <td>
        <input type="radio" name="gender" value="male">男
        <input type="radio" name="gender" value="female">女
      </td>
    </tr>
    <tr>
      <td>爱好</td>
      <td>
        <input type="checkbox" name="hobby" value="code">写代码
        <input type="checkbox" name="hobby" value="music">听歌曲
        <input type="checkbox" name="hobby" value="foot">按脚
      </td>
    </tr>
    <tr>
      <td>城市</td>
      <td>
        <select name="city">
          <option value="beijing">北京</option>
          <option value="shanghai">上海</option>
          <option value="guangzhou">广州</option>
        </select>
      </td>
    </tr>
    <tr>
      <td>个人介绍</td>
      <td>
        <textarea name="introduce"></textarea>
      </td>
    </tr>
    <tr>
      <td colspan="2">
        <input type="submit" value="提交">
        <input type="reset" value="重填">
      </td>
    </tr>
  </table>
</form>
</body>
</html>
@WebServlet(name = "RegistServlet", value = "/registservlet")
public class RegistServlet extends HttpServlet {

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    //如果是post请求,要设置编码utf-8,因为服务器的默认的编码是iso8859-1
    System.out.println("设置前" + request.getCharacterEncoding());
    //设置编码
    request.setCharacterEncoding("utf-8");
    System.out.println("设置后" + request.getCharacterEncoding());
    //request 封装了浏览器发送给服务器的数据
    //getParameter 获取请求的参数,只能获取一个数据
    //getParameterValues 获取请求的参数,获取一个数组
    String username = request.getParameter("username");
    String pwd = request.getParameter("pwd");
    String repwd = request.getParameter("repwd");
    String gender = request.getParameter("gender");
    String[] hobbies = request.getParameterValues("hobby");
    String city = request.getParameter("city");
    String introduce = request.getParameter("introduce");
    //打印
    System.out.println(
        username + "..." + pwd + "..." + repwd + "..." + gender + "..." + Arrays.toString(hobbies)
            + "..." + city);
    System.out.println("个人介绍:" + introduce);

    //获取HTTP请求报文的其他数据
    //1请求行
    System.out.println("---------------1请求行-------------");
    //1.1请求方式

    String method = request.getMethod();
    System.out.println("请求方式:" + method);
    //1.2请求地址
    StringBuffer url = request.getRequestURL();  //URL (Uniform Resource Locator 统一资源定位符)
    System.out.println("请求地址url:" + url.toString());
    //获取查询字符串 (url问号后的内容)
    String queryString = request.getQueryString();
    System.out.println("queryString:" + queryString);

    String uri = request.getRequestURI();  //URL (Uniform Resource Identifier 统一资源标识符 包含URL)
    System.out.println("请求地址uri:" + uri);
    //1.3协议
    String protocol = request.getProtocol();
    System.out.println("协议:" + protocol);
    //2 请求头
    System.out.println("---------------2请求头-------------");
    String accept = request.getHeader("Accept");
    System.out.println("Accpt:" + accept);
    Enumeration<String> headerNames = request.getHeaderNames();
    while (headerNames.hasMoreElements()) {
      System.out.println(headerNames.nextElement());
    }

    //获取TCP协议的信息
    //获取客户端的ip地址和端口号
    String remoteAddr = request.getRemoteAddr();
    int remotePort = request.getRemotePort();
    System.out.println("客户端ip地址:" + remoteAddr + " 端口号:" + remotePort);
   }

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

Response对象

概念:

  • response对象用户响应客户请求并向客户端输出信息
  • response对象包含响应目标的所有内容

主要方法:

Response应用

  • 通过response响应给客户端页面
  • 注意:如果输出内容包含中文,则出现乱码,因为服务器默认采用ISO-8859-1编码响应内容。
  • 解决中文乱码
    • 方案1
      • 设置服务端响应的编码格式
      • 设置客户端响应内容的头内容的文件类型及编码格式
      • response.setCharacterEncoding("UTF-8");
      • 添加meta标签<meta chartset="utf-8">
    • 方案2
      • 同时设置服务端的编码格式和客户端响应的文件类型及响应时的编码格式。
      • response.setContentType("text/html;charset=UTF-8");//推荐
      • response.setHeader("Content-Type" , "text/html;charset=UTF-8");
public class RegistServlet extends HttpServlet {

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    //response对象响应内容
    //设置响应编码
    response.setCharacterEncoding("utf-8");
    //设置响应头
    response.setHeader("haha", "xaweifjawiegawief");
    response.setContentType("text/html;charset=utf-8");
    response.setHeader("Content-Type", "text/html;charset=utf-8");
    PrintWriter out = response.getWriter();
    out.println("<!DOCTYPE html>");
    out.println("<html>");
    out.println("<head>");
    out.println("<meta charset='utf-8'>");
    out.println("<title>提示</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("注册成功...");
    out.println("</body>");
    out.println("</html>");
    out.println("<h3>注册成功...</h3>");
  }

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

转发与重定向

现有问题:

  • 在之前案例中,处理请求和显示结果页面都在同一个Servlet里,存在设计问题。
  • 不符合单一职能原则、各司其职的思想。
  • 不利于后续的维护。

转发

概念:

  • 转发的作用在服务器端。
  • 将请求发送给服务器上的其他资源,以共同完成一次请求的处理。

实现:request.getRequestDispatcher("/目标URL-pattern").forward(request,response);

注意:使用forward跳转时,是在服务器内部跳转,地址栏不发生变化,属于同一次请求。

数据传递:

  • forward表示一次请求。
  • 是在服务器内部跳转,可以共享同一次request作用域中的数据。

request作用域:

  • 拥有存储数据的空间,作用范围是一次请求有效。
  • 存储数据:
    • request.setAttribute(key,value);·
  • 获取数据:
    • request.getAttribute(key):

转发特点:

  • 转发是服务器行为。
  • 转发是浏览器只做了一次访问请求。转发浏览器地址不变。
  • 转发两次跳转之间传输的信息不会丢失,所以可以通过request进行数据的传递。
  • 转发只能将请求转发给同一个Web应用中的其他组件。

重定向

概念:

  • 重定向作用在客户端。
  • 客户端请求服务器,服务器响应给客户端一个新的请求地址,客户端重新发送新请求。

实现:response.sendRedirect("目标URl"");

URl:用来表示服务器中定位一个资源,资源在web项目中的路径(/project/source)。

注意:使用redirect跳转时,是在客户端跳转,地址栏发生变化,属于多次请求。

数据传递问题:

  • sendRedirect跳转时,地址栏改变,代表客户端重新发送的请求,属于两次请求。
  • 两次request请求中的数据无法共享。

实现数据传递:

  • 可以通过URI的拼接进行数据传递("/WebProject/b?username=tom");
  • 获取数据:request.getParameter("username");

特点:

  • 重定向是客户端行为。
  • 浏览器做了至少两次的访问请求。
  • 浏览器地址改变。
  • 两次跳转之间传输的信息会丢失(request范围)。
  • 可以指向任何的资源:
    • 包括当前应用程序中的其他资源、同一个站点上的其他应用程序中的资源、其他站点的资源。

总结:当需要传递数据时,选择forward转发。不建议使用sendRedirect进行数据传递。

状态管理

现有问题:

  • HTTP协议是无状态的,不能保存每次提交的信息。
  • 客户端发送一个新的请求,服务器无法知道它是否与上次的请求有联系。
  • 对于需要多次提交数据才能完成的Web操作,比如登录来说,较为麻烦。

概念:

  • 将浏览器与Web服务器之间多次交互当作一个整体来处理。
  • 将多次交互所涉及的数据(即状态)保存下来。

分类:

  • 客户端状态管理技术:将状态保存在客户端,代表性的是Cookie技术。
  • 服务器状态管理技术:将状态保存在服务器端,代表性的是Session技术。

Cookie

概念:

  • Web服务器在HTTP响应消息头中附带传送给浏览器的一小段数据。
  • 当浏览器保存了Cookie,之后每次访问该服务器时,会通过请求头回传该Cookie数据。
  • Cookie主要由标识该信息的名称(name)和值(value)组成。

Cookie的原理:

@WebServlet(name = "CookieServlet", value = "/cookieservlet")
public class CookieServlet extends HttpServlet {

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    //1创建cookie
    Cookie cookie = new Cookie(URLEncoder.encode("用户名", "utf-8"), URLEncoder.encode("灿灿", "utf-8"));
    //2设置信息
    //2.1 设置有效路径(回传路径)
    cookie.setPath(request.getContextPath());///day39web1"
    //2.2 设置有效期 (重要)
    // -1默认值:表示浏览器窗口关闭,cookie失效。0 表示删除cookie 。>0 表示有效时间 单位是秒
    cookie.setMaxAge(60 * 60 * 24);
    //2.3 设置httpOnly
    cookie.setHttpOnly(true);
    //3添加response中
    response.addCookie(cookie);
    System.out.println("创建了cookie");
  }

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

服务器端获取Cookie:

@WebServlet(name = "ReadCookieServlet", value = "/readcookie")
public class ReadCookieServlet extends HttpServlet {

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    //读取浏览器回传给服务器的cookie
    String cookieString = request.getHeader("Cookie");
    System.out.println(cookieString);
    System.out.println("--------getCookies()---------");
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
      for (Cookie cookie : cookies) {
        //System.out.println(cookie.getName()+"...."+cookie.getValue());
        System.out.println(URLDecoder.decode(cookie.getName(), "utf-8") + "...." + URLDecoder
            .decode(cookie.getValue(), "utf-8"));
      }
    }

  }

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

修改Cookie:

  • 只需要保证Cookie的名和路径一致即可修改。
@WebServlet(name = "DeleteCookieServlet", value = "/deletecookie")
public class DeleteCookieServlet extends HttpServlet {

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    //删除cookie (1)名字相同 (2)有效路径相同
    Cookie cookie = new Cookie("username", "");
    cookie.setPath(request.getContextPath());
    cookie.setMaxAge(0);//0 表示删除
    //添加到response
    response.addCookie(cookie);
    System.out.println("删除了cookie");
  }

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

Cookie的乱码:

  • Cookie默认不支持中文,只能包含ASCII字符。

编码与解码:

  • 编码:java.net.URLEncoder类的encode(String str,String encoding)方法。
  • 解码: java.net.URLDecoder类的decode(String str,String encoding)方法。

Cookie的优点:·

  • 可配置到期规则。
  • 简单性:Cookie是一种基于文本的轻量结构,包含简单的键值对。
  • 数据持久性: Cookie默认在过期之前是可以一直存在客户端浏览器上的。

Cookie的缺点:

  • 大小受到限制:
  • 大多数浏览器对Cookie的大小有4K、8K字节的限制。
  • 用户配置为禁用:
    • 有些用户禁用了浏览器或客户端设备接收Cookie的能力,因此限制了这一功能。
  • 潜在的安全风险:
    • Cookie可能会被篡改。会对安全性造成潜在风险或者导致依赖于Cookie的应用程序失败。

Session

概念:

  • Session用于记录用户的状态。
  • Session指的是在一段时间内,单个客户端与Web服务器的一连串相关的交互过程。
  • 在一个Session中,客户可能会多次请求访问同一个资源或不同的资源。

Session的原理:

获取Session:

  • Session是由服务端自动创建的,通过request对象获取:
    • HttpSession session=request.getSession()

Session域:

  • 域对象:拥有存储数据的空间,作用范围是一次会话有效。
    • 一次会话是使用同一浏览器发送的多次请求。一旦浏览器关闭,则结束会话。
    • 可以将数据存入Session作用域,在一次会话的任意位置进行获取。
    • 可传递任何数据(基本数据类型、对象、集合、数组)。

Session保存数据:

  • 以键值对形式存储在session作用域中:
    • session.setAttribute("key ",value);

Session获取数据:

  • 通过String类型的key访问Object类型的value:
    • session.getAttribute("key");

Session移除数据:

  • 通过key移除session作用域中的值:
    • session.removeAttribute("key");

Session与Request区别:

  • request是一次请求有效,请求改变,则request改变。
  • session是一次会话有效,浏览器改变,则session改变。

Session的生命周期:

  • 开始:
    • 第一次使用到Session的请求产生,则创建Session。
  • 结束:
    • 浏览器关闭,session则不能使用了。.
    • Session超时,则失效:
      • session.setMaxlnactivelnterval(seconds);
    • 手工销毁,则失效:
      • session.invalidate();
@WebServlet(name = "SessionServlet", value = "/session")
public class SessionServlet extends HttpServlet {

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    //1获取session
    HttpSession session = request.getSession();
    //2打印信息
    System.out.println("sessionId:" + session.getId());
    System.out.println("session有效期:" + session.getMaxInactiveInterval());
    System.out.println("session创建时间:" + session.getCreationTime());
    System.out.println("session最后一次访问时间:" + new Date(session.getLastAccessedTime()));

    //3session作为域(容器)对象使用
    session.setAttribute("username", "灿灿");
    //4手动session失效
    session.invalidate();
    System.out.println("session失效了...");
  }

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

Session应用

带验证码的登录:

登录页面:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>用户登录</title>
  <style type="text/css">
      img, input, button, select, textarea {
          vertical-align: middle;
      }
  </style>
</head>
<body>
<h1>用户登录</h1>
<form action="login" method="post" enctype="application/x-www-form-urlencoded">
  <input type="text" name="username" placeholder="请输入用户名"><br/>
  <input type="password" name="password" placeholder="请输入密码"><br/>
  <input type="text" name="vcode"> <img id="img" src="code" height="25" onclick="changeImg()"><br/>
  <input type="submit" value="提交">
</form>
<script type="text/javascript">
  function changeImg() {
    var img = document.getElementById("img");
    img.src = "code?n=" + Math.random();
  }
</script>
</body>
</html>

验证码生成:

@WebServlet(name = "CodeServlet",value = "/code")
public class CodeServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1 创建ValidateCode对象
        // width:宽度
        // height:高度
        // codeCount:验证码个数
        // lineCount: 干扰线
        ValidateCode validateCode=new ValidateCode(120, 25 , 4 , 30);
        //2 获取验证码
        String code = validateCode.getCode();
        System.out.println("验证码:"+code);
        //存入session中
        request.getSession().setAttribute("code", code);
        //3 把图片响应给浏览器
        validateCode.write(response.getOutputStream());
    }

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

登录校验:

@WebServlet(name = "LoginServlet", value = "/login")
public class LoginServlet extends HttpServlet {

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    request.setCharacterEncoding("utf-8");
    response.setContentType("text/html;charset=utf-8");
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    String vcode = request.getParameter("vcode");
    if (StringUtils.isEmptry(username)) {
      response.getWriter().write("用户名不能为空");
      return;
    }
    if (StringUtils.isEmptry(password)) {
      response.getWriter().write("密码不能为空");
      return;
    }

    if (StringUtils.isEmptry(vcode)) {
      response.getWriter().write("验证不能为空");
      return;
    }
    //校验验证码
    String code = (String) request.getSession().getAttribute("code");
    if (!vcode.equalsIgnoreCase(code)) {
      response.getWriter().write("验证码输入有误");
      return;
    }

    UserService userService = new UserServiceImpl();
    try {
      User user = userService.login(username, password);
      //登录成功后,需要把user存入session
      HttpSession session = request.getSession();
      session.setAttribute("user", user);
      //重定向
      //response.sendRedirect("index.jsp");
      response.sendRedirect("booklist");
    } catch (Exception e) {
      e.printStackTrace();
      response.sendRedirect("message.html");
    }
  }

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

Cookie禁用解决方案

背景:

  • 服务器在默认情况下,会使用Cookie的方式将sessionid发送给浏览器。
  • 如果用户禁止Cookie,则sessionid不会被浏览器保存。

禁用后解决方案:

  • URL重写:
    • 访问服务器上的某个地址时,对地址做重写(即在原来的地址后面加上了sessionid)

实现:response.encodeRedirectURL(String url)生成重写的URL。

@WebServlet(name = "URLServlet", value = "/urlservlet")
public class URLServlet extends HttpServlet {

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    HttpSession session = request.getSession();
    response.setContentType("text/html;charset=utf-8");
    PrintWriter out = response.getWriter();
    out.println("<!DOCTYPE html>");
    out.println("<html>");
    out.println("<head>");
    out.println("<title>演示url重写</title>");
    out.println("</head>");
    out.println("<body>");
    String url = "index.jsp";
    //编码 自定在url后面加sessonId
    out.println("<a href='" + response.encodeURL(url) + "'>跳转到首页</a>");
    out.println("</body>");
    out.println("</html>");
  }

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

ServletContext

概念:

  • ServletContext也叫application。
  • 全局对象,拥有作用域,对应一个Tomcat中的Web应用。
  • 当Web服务器启动时,会为每一个Web应用程序创建一块共享的存储区域。
  • ServletContext在Web服务器启动时创建,服务器关闭时销毁。

获取ServletContext:

  • this.getServletContext()方法。
  • request.getServletContext()方法
  • session.getServletContext()方法。

ServletContext作用:

  • 获取项目真实路径:
  • String realpath=servletContext.getRealPath("/");
  • 读取web下面的文件使用servletContext。
  • 读取类路径(src复制来)下面的文件使用类加载器读取。

获取应用上下文路径:

  • System.out.println(servletContext.getContextPath());
  • System.out.println(request.getContextPath0);//推荐。

全局容器:

  • 存储数据:
    • servletContext.setAttribute( "name",value);
  • 获取数据:
    • servletContext.getAttribute("name");
  • 移除数据:
    • servletContext.removeAttribute("name");

特点:

  • 唯一性:一个应用对应一个ServletContext。
  • 生命周期:只要容器不关闭或者应用不卸载,ServletContext就一直存在。
@WebServlet(name = "AppServlet", value = "/appservlet")
public class AppServlet extends HttpServlet {

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    //获取ServletContext对象(application对象),类似于JavaSE Runtime runtime=Runtime.getRuntime();
    //方式1:this.getServletContext();
    ServletContext application = this.getServletContext();
    //方式2:request.getServletContext();
    ServletContext application2 = request.getServletContext();
    //方法3: session.getServletContext();
    ServletContext application3 = request.getSession().getServletContext();
    System.out.println(application.hashCode());
    System.out.println(application2.hashCode());
    System.out.println(application3.hashCode());
    System.out.println("------作用1 获取项目的真实位置(项目在服务器中硬盘的位置)----------");
    // 作用1 获取项目的真实位置(项目在服务器中硬盘的位置)
    //在web中项目中如何读取文件
    //(1)如果在src下面,使用类加载器读取文件
    //(2)如果文件web目录下, 使用ServletContext获取真实路径读取。
    String realPath = application.getRealPath("/hello.txt");
    //System.out.println(realPath);
    BufferedReader br = new BufferedReader(new FileReader(realPath));
    String s = br.readLine();
    System.out.println(s);
    br.close();
    System.out.println("--------作用2: 获取上下文路径(访问配置路径)------");
    //作用2: 获取上下文路径(访问配置路径)
    String contextPath = application.getContextPath();
    System.out.println(contextPath);
    String contextPath1 = request.getContextPath();
    System.out.println(contextPath1);

    //作用3:作为域(容器)对象,保存全局数据
    application.setAttribute("appName", "淘宝网");
    application.setAttribute("version", "5.0");
    application.setAttribute("author", "灿灿");

    //获取
    String appName = (String) application.getAttribute("appName");

    //删除
    application.removeAttribute("appName");
  }

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

点击计数:

@WebServlet(name = "CountServlet", value = "/countservlet")
public class CountServlet extends HttpServlet {

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    //1获取application
    ServletContext application = this.getServletContext();
    response.setContentType("text/html;charset=utf-8");
    Integer count;
    synchronized (this) {
      count = (Integer) application.getAttribute("count");
      if (count == null) {//第一次访问
        count = 1;
      } else {
        count++;
      }
      application.setAttribute("count", count);
    }
    response.getWriter().write("访问次数:" + count);
  }

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

域对象总结

已学域对象:

  • request:
    • —次请求,请求响应之前有效。
  • session:
    • 一次会话开始,浏览器不关闭或不超时之前有效。.
    • application(ServletContext):
      • 服务器启动开始,服务器停止之前有效。

注意:

  • 使用域对象可以传递数据和存储数据。
  • 使用域对象传递数据时,必须掌握作用域对应的生命周期和作用范围。

总结

  • 状态管理:
    • Cookie、Session。
    • ServletContext。
  • 域对象:
    • request、session、app.Cauio

[/wppay]