深入理解Servlet技术

1. 什么是servlet

servlet Servlet是javax.servlet.Servlet包中定义的一个接口.它声明了servlet生命周期中不可少的三个方法init,service和destory(),每个servlet必须实现这三个方法,而且服务器在特定的时刻调用.

2.生命周期

容器加载 -> 初始化 init (仅一次) -> 进入服务 service (Get/Post 请求)-> 销毁 destroy -> 容器卸载

执行过程

  • 客户端发出请求http://localhost:8080/hello
  • 根据web.xml文件的配置,找到<url-pattern>子元素的值“/hello”的<servlet-mapping>元素
    读取<servlet-mapping>元素的<servlet-name>子元素的值,由此确定Servlet的名字为”HelloServlet”
    找到<servlet-name>值为HelloServlet的<servlet>元素
    读取<servlet>元素的<servlet-class>子元素的值,由此确定Servlet的类名为com.kaishengit.web.HelloServlet。
    到Tomcat安装目录/webapps/Demo1/WEB-INF/classes/cn/itcast目录下查找到HelloServlet.class文件

客户端发出请求,容器产生request和response对象,容器根据url找到合适的servlet并分配线程进行访问,service根据请求头调用doXX方法,servlet使用相应对象通过容器对客户端做出响应,service方法执行结束,然后调用destory()方法,访问线程和request、response对象被销毁。


Servlet的执行过程.jpg

细节

  • 由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
  • <servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名。
    一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>和<url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。例如:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  
  
  <!-- 
       配置Servlet
     servlet-name 名字自定义
     servlet-class Servlet类的完全限定名
  -->
  <servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.kaishengit.web.HelloServlet</servlet-class>
  </servlet>
  <!-- 
    servlet-name 名字自定义,但是必须和<servlet>节点中的<servlet-name>值相同
    url-pattern 客户端请求的路径名称,必须以/开头
   -->
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
  
  <servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>com.kaishengit.web.LoginServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/login</url-pattern>
  </servlet-mapping>
  
  
  
  <!-- 
    重要提示!!!修改web.xml文件必须要重启容器,才能生效
    
    欢迎页面配置,优先级是从上倒下越来越低,如果都找不到,则显示404错误页面
    
    常见的HTTP状态码:
    200 正常响应
    404 访问的资源不存在
    500 服务器异常 
  -->
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>main.jsp</welcome-file>
    <welcome-file>home.jsp</welcome-file>
  </welcome-file-list>
  
  
  
</web-app>

Servlet重要的四个生命周期方法

  • 构造方法: 创建servlet对象的时候调用。默认情况下,第一次访问servlet的时候创建servlet对象 只调用1次。证明servlet对象在tomcat是单实例的。
  • init方法: 创建完servlet对象的时候调用。只调用1次。
  • service方法: 每次发出请求时调用。调用n次。
  • destroy方法: 销毁servlet对象的时候调用。停止服务器或者重新部署web应用时销毁servlet对象。
    只调用1次。

伪代码演示servlet的生命周期

Tomtcat内部代码运行:
```
        1)通过映射找到到servlet-class的内容,字符串: gz.itcast.a_servlet.FirstServlet
        2)通过反射构造FirstServlet对象
                2.1 得到字节码对象
                Class clazz = class.forName("gz.itcast.a_servlet.FirstServlet");
                2.2 调用无参数的构造方法来构造对象
                Object obj = clazz.newInstance();     ---1.servlet的构造方法被调用
        3)创建ServletConfig对象,通过反射调用init方法
                3.1 得到方法对象
                Method m = clazz.getDeclareMethod("init",ServletConfig.class);
                3.2 调用方法
                m.invoke(obj,config);             --2.servlet的init方法被调用
        4)创建request,response对象,通过反射调用service方法
                4.1 得到方法对象
                Methodm m =clazz.getDeclareMethod("service",HttpServletRequest.class,HttpServletResponse.class);
                4.2 调用方法
                m.invoke(obj,request,response);  --3.servlet的service方法被调用
        5)当tomcat服务器停止或web应用重新部署,通过反射调用destroy方法
                5.1 得到方法对象
                Method m = clazz.getDeclareMethod("destroy",null);
                5.2 调用方法
                m.invoke(obj,null);            --4.servlet的destroy方法被调用
            

```

(重点图)用时序图来演示servlet的生命周期

image.png

常用类

HttpServletRequest
  • 是JSP的内置对象之一,jsp中叫做request
  • 用于接受客户端请求,可以获取客户端一些数据
  • getParameter(String name) 获取URL或者form表单中的值
  • setAttribute(String name,Object value)向跳转目标对象传值
  • getAttribute(String name) 获取传值
  • getRequestDispatcher(String path) 获取RequestDispatcher对象,使用RequestDispatcher进
    行请求转发跳转
HttpservletResponse
  • 给客户端做出响应
  • sendRedirect(String urlName) ,以重定向方式,跳转指定路径中


    image.png
重定向和请求转发的区别
  • 重定向跳转是使用url重写的方式进行值的传递,值显示在url的地址栏中
  • 请求转发使用HttpServletRequest对象的setAttribute方法进行值传递,值不会显示在地址
    栏中
  • 重定向传值方式传递适合不敏感数据以及简单的字符串、数字等基本类型
  • 请求转发跳转传值方式适合传递敏感数据以及对象、数组、集合等类型的数据
  • 重定向跳转后浏览器的地址栏中显示的是跳转目标的URL(地址栏会发生改变)
  • 请求转发跳转后地址栏不会显示跳转目标的URL(地址栏不会发生改变)
  • 重定向跳转不会引起表单的重复提交
  • 请求转发跳转会引起表单的重复提交
  • 重定向跳转本质上是服务器产生302响应,客户端再次向服务器发出二次请求


    image.png

会话技术

  • Cookie 技术: 会话数据保存在浏览器客户端
  • Session技术: 会话数据保存在服务端

Cookie核心技术

Cookie类:用于存储会话数据
  1. 构造cookie对象
Cookie(java.lang.String name, java.lang.String value)
  1. 设置cookie
void setPath(java.lang.String uri)   :设置cookie的有效访问路径
                   void setMaxAge(int expiry) : 设置cookie的有效时间
                   void setValue(java.lang.String newValue) :设置cookie的值

  1. 发送cookie到浏览器端保存
void response.addCookie(Cookie cookie)  : 发送cookie
  1. 服务器接受cookie
Cookie[] request.getCookies()  : 接收cookie

设置
package com.kaishengit.web;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SetCookieServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie cookie = new Cookie("playId","1002");
        cookie.setDomain("localhost");
        cookie.setPath("/");
        cookie.setMaxAge(60 * 60 * 24 * 7);
        cookie.setHttpOnly(true); //
        resp.addCookie(cookie);

        Cookie cookie2 = new Cookie("productId","2908");
        cookie2.setDomain("localhost");
        cookie2.setPath("/");
        cookie2.setMaxAge(60 * 60 * 24 * 7);
        resp.addCookie(cookie2);

        System.out.println("set cookie success!");
    }
}

获取Cookie

package com.kaishengit.web;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class GetCookieServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      Cookie[] cookies = req.getCookies();
      if(cookies != null) {
          for(Cookie cookie : cookies) {
              System.out.println(cookie.getName() + " -> " + cookie.getValue());
          }
      }

      System.out.println("get cookie success!");
  }
}


JQuery Cookie插件:

Cookie插件:
Cookie原理
  1. 服务器创建cookie对象,把会话数据存储到cookie对象中
new Cookie("name","value");

2.服务器发送cookie信息到浏览器

    response.addCookie(cookie);
    举例: set-cookie: name=eric  (隐藏发送了一个set-cookie名称的响应头)
  1. 浏览器得到服务器发送的cookie,然后保存在浏览器端。
  2. 浏览器在下次访问服务器时,会带着cookie信息
 举例: cookie: name=eric  (隐藏带着一个叫cookie名称的请求头)
  1. 服务器接收到浏览器带来的cookie信息
request.getCookies();

记住账号

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" href="/static/css/bootstrap.min.css" />
</head>
<body>

    <div class="container">
        
        
        <c:if test="${not empty param.callback}">
            <div class="alert alert-danger">请登录再继续</div>
        </c:if>
        
        <c:if test="${not empty message }">
            <div class="alert alert-danger">${message }</div>
        </c:if>
        
        
        <form method="post" id="loginForm">
            <div class="form-group">
                <label>账号</label>
                <input type="text" name="username" id="username" class="form-control" value="${username }"/>
            </div>
            
            <div class="form-group">
                <label>账号</label>
                <input type="password" name="password" class="form-control"/>
            </div>
            <div class="checkbox">
                <label>
                    <input type="checkbox" name="remeberme" value="remeberme" id="remeberme" />记住账号
                </label>
            </div>
            <div>
                <button type="button" id="loginBtn" class="btn btn-success">登录</button>
            </div>
        </form>
    
    </div>
    <script src="/static/js/jquery-1.11.3.min.js"></script>
    <script src="/static/js/jquery.cookie.js"></script>
    <script>
    
        $(function(){
            
            //$("#username").val($.cookie("username"));
            
            
            $("#loginBtn").click(function(){
                /* if($("#remeberme")[0].checked) {
                    $.cookie("username",$("#username").val(),{ expires: 7, path: '/' });
                } */
                $("#loginForm").submit();
            });
            
        });
    
    
    
    </script>
</body>
</html>

服务端记住账号

package com.kaishengit.web;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;

import com.kaishengit.entity.Admin;
import com.kaishengit.exception.ServiceException;
import com.kaishengit.service.AdminService;

public class LoginServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = "";
        
        Cookie[] cookies = req.getCookies();
        if(cookies != null) {
            for(Cookie cookie : cookies) {
                if(cookie.getName().equals("username")) {
                    username = cookie.getValue();
                    break;
                }
            }
        }
        req.setAttribute("username", username);
        
        req.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(req, resp);
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String callback = req.getParameter("callback");
        String remeberme = req.getParameter("remeberme");
        
        AdminService adminService = new AdminService();
        
        try {
            Admin admin = adminService.login(username, password);
            
            //判断是否选中了[记住账号]框
            if(StringUtils.isNotEmpty(remeberme)) {
                Cookie cookie = new Cookie("username",username);
                cookie.setDomain("localhost");
                cookie.setPath("/");
                cookie.setMaxAge(60 * 60 * 24 * 365 * 100);
                cookie.setHttpOnly(true);
                
                resp.addCookie(cookie);
            }
            
            
            
            //获取HttpSession
            HttpSession session = req.getSession();
            session.setAttribute("admin", admin);
            if(StringUtils.isEmpty(callback)) {
                resp.sendRedirect("/list");
            } else {
                resp.sendRedirect(callback);
            }
            
            
            
        } catch (ServiceException e) {
            req.setAttribute("message", e.getMessage());
            req.setAttribute("username", username);
            
            req.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(req, resp);
        }
        
        
        
    }
    
    

}


Session原理
问题: 服务器能够识别不同的浏览者!!!
前提: 在哪个session域对象保存数据,就必须从哪个域对象取出!!!!
浏览器1:(给s1分配一个唯一的标记:s001,把s001发送给浏览器)
                    1)创建session对象,保存会话数据
                            HttpSession session = request.getSession();   --保存会话数据 s1
            浏览器1    的新窗口(带着s001的标记到服务器查询,s001->s1,返回s1) 
                    1)得到session对象的会话数据
                            HttpSession session = request.getSession();   --可以取出  s1

            新的浏览器1:(没有带s001,不能返回s1)
                    1)得到session对象的会话数据
                            HttpSession session = request.getSession();   --不可以取出  s2

            浏览器2:(没有带s001,不能返回s1)
                    1)得到session对象的会话数据
                            HttpSession session = request.getSession();  --不可以取出  s3


代码解读:HttpSession session = request.getSession();
  1. 第一次访问创建session对象,给session对象分配一个唯一的ID,叫JSESSIONID
    new HttpSession();
  1. 把JSESSIONID作为Cookie的值发送给浏览器保存
Cookie cookie = new Cookie("JSESSIONID", sessionID);
                    response.addCookie(cookie);
  1. 第二次访问的时候,浏览器带着JSESSIONID的cookie访问服务器
  2. 服务器得到JSESSIONID,在服务器的内存中搜索是否存放对应编号的session对象。
if(找到){
                        return map.get(sessionID);
                    }
                    Map<String,HttpSession>]


                    <"s001", s1>
                    <"s001,"s2>
  1. 如果找到对应编号的session对象,直接返回该对象
  2. 如果找不到对应编号的session对象,创建新的session对象,继续走1的流程

结论:通过JSESSION的cookie值在服务器找session对象!!!!!


总结:
        1)会话管理: 浏览器和服务器会话过程中的产生的会话数据的管理。

                2)Cookie技术:
                        new Cookie("name","value")
                        response.addCookie(coookie)
                        request.getCookies()
                3)Session技术
                        request.getSession();
                        
                        setAttrbute("name","会话数据");
                        getAttribute("会话数据")

监听器

web.ml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <!-- 登录过滤器 -->
    <filter>
        <filter-name>ValidateFilter</filter-name>
        <filter-class>com.kaishengit.web.filter.ValidateServlet</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ValidateFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


</web-app>
适配器模式:AbstractFilter
package com.kaishengit.web.filter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

/**
 * @author Wgs
 * @version 1.0
 * @create:2018/05/26
 */
public class FilterServlet implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("FilterServlet.init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("FilterServlet.doFilter");
    }

    @Override
    public void destroy() {

    }
}

登陆拦截器
package com.kaishengit.web.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.kaishengit.entity.Admin;

public class ValidateFilter extends AbstractFilter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        
        //1. 获取用户访问的资源地址
        String uri = request.getRequestURI();
        System.out.println(uri);
        
        if("/".equals(uri) || "/index.jsp".equals(uri) || "/login".equals(uri)
                || uri.startsWith("/static/")) {
            filterChain.doFilter(request, response);
        } else {
            
            HttpSession session = request.getSession();
            Admin admin = (Admin) session.getAttribute("admin");
            
            if(admin != null) {
                filterChain.doFilter(request, response);
            } else {
                response.sendRedirect("/login?callback="+uri);
            }
            
        }

    }

}

监听器

web.xml
    <!--监听器的配置-->
    <listener>
        <listener-class>com.kaishengit.web.listener.MyServletContextListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.kaishengit.web.listener.MyHttSessionListener</listener-class>
    </listener>

    <context-param>
        <param-name>userName</param-name>
        <param-value>jack</param-value>
    </context-param>

HttpSessionListener
package com.kaishengit.web.listener;

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class MyHttSessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        System.out.println("session create...");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        System.out.println("session destory...");
    }
}


ServletContextListener
package com.kaishengit.web.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class MyServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContextListener init....");

        //通过ServletContextEvent对象来获取ServletContext对象
        ServletContext servletContext = servletContextEvent.getServletContext();
        String userName = servletContext.getInitParameter("userName");
        System.out.println(userName);
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContextListener destroy....");
    }
}

异步验证

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342