JavaWeb基础之Servlet全解析

Servlet 是在服务器上运行的小程序,其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类。

本文内容:


1.Servlet开发步骤

学习Servlet,我们先看一下Servlet程序怎么开发,大体上可以分为五步,如下所示:

1)编写java类,继承HttpServlet类,重写doGet和doPost方法

public class FirstServlet extends HttpServlet{  
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse response)
            throws ServletException, IOException {
        //向浏览器输出内容
        response.getWriter().write("this is first servlet!");
    }
}

2)在web.xml文件中进行配置

<!-- 配置一个servlet -->
  <!-- servlet的配置 -->
  <servlet>
    <!-- servlet的内部名称,自定义。尽量有意义 -->
    <servlet-name>FirstServlet</servlet-name>
    <!-- servlet的类全名: 包名+简单类名 -->
    <servlet-class>cn.acamy.FirstServlet</servlet-class>
  </servlet>
  
  
  <!-- servlet的映射配置 -->
  <servlet-mapping>
    <!-- servlet的内部名称,一定要和上面的内部名称保持一致!! -->
    <servlet-name>FirstServlet</servlet-name>
    <!-- servlet的映射路径(访问servlet的名称) -->
    <url-pattern>/first</url-pattern>
  </servlet-mapping>

3)部署,servlet程序的class码拷贝到WEB-INF/classes目录
4)启动tomcat服务器
5)通过URL访问
http://localhost:8080/demo/first

http:// :http协议
localhost: :到本地的hosts文件中查找是否存在该域名对应的IP地址(127.0.0.1)
8080 : 找到tomcat服务器
/demo : 在tomcat的webapps目录下找 demo的目录
/first: 资源名称。
1)在demo的web.xml中查找是否有匹配的url-pattern的内容(/first)
2)如果找到匹配的url-pattern,则使用当前servlet-name的名称到web.xml文件中查询是否相同名称的servlet配置
3)如果找到,则取出对应的servlet配置信息中的servlet-class内容:
字符串: cn.acamy.FirstServlet
通过反射:
a)构造FirstServlet的对象
b)然后调用FirstServlet里面的方法

2.本文涉及到的相关类

2.1 HttpServlet继承体系

我们写的Servlet程序都是要直接继续HttpServlet,由下面的类图可以看出该类有service方法的重写和重载,以及doGet,doPost...方法,继承于抽象类GenericServlet,GenericServlet实现了接口Servlet和ServletConfig,Servlet接口里面定义了init,service,destroy,getServletConfig,getServletInfo方法,ServletConfig里面定义了获取配置信息的相关方法。

2.2 HttpServletRequest与HttpServletResponse

HttpServletRequest继承于ServletRequest,封装了Servlet程序的请求信息,HttpServletResponse继承于ServletResponse,封装了Servlet程序的响应信息。

2.3 ServletContext

ServletContext是Servlet上下文对象,封装了当前的web应用环境。

3. Servlet的生命周期

Servlet程序的生命周期是由tomcat服务器控制的!

我们先来看一个Demo:

public class LifeDemo extends HttpServlet {

    /**
     * 1.构造方法
     */
    public LifeDemo(){
        System.out.println("1.servlet对象被创建了。");
    }

    /**
     * 2.init方法
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("2.init方法被调用");
    }
    
    /**
     * 3.service方法
     */
    @Override           
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException {
        System.out.println("3.service方法被调用");
    }
    
    /**
     * 4.destroy方法
     */
    @Override
    public void destroy() {
        System.out.println("4.servlet对象销毁了");
    }
}

效果如下:


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

Servlet生命周期如下图所示:

4.HttpServlet的service方法源码

从上面的生命周期可以看出,service方法在浏览器每发送一次请求就被调用一次。但我如果要调用doGet,doPost,doHead等方法时,又该怎么实现呢?先看一下HttpServlet里面与该方法相关的源码:

    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";

    //该方法是重写Servlet接口中的方法,可以看到先把ServletRequest和
    //ServletResponse转化为其子类HttpServletRequest,HttpServletResponse
    //然后调用重载的service(HttpServletRequest req, HttpServletResponse resp)
    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }
 
    //注意这个方法是上面service方法的重载,并且是protected面不是public修饰符修
    //饰,说明该方法只能被自身或子类来调用。
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
       
        //首先得到方法名
        String method = req.getMethod();
        
        // 然后看该方法名与实现的哪个方法相对应,就执行哪个方法
        // 如得到的方法名为POST,则对应到METHOD_POST,
        // 执行doPost(req, resp);
        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //
           //如果没有找到则说明没有该方法的实现,给出错误提示
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

结论:无论我们需要调用的是GET,POST还是其它方法,我们首先通过调用service(ServletRequest req, ServletResponse res),然后指向service(HttpServletRequest req, HttpServletResponse resp),最终通过方法名来进行匹配,实现转向,调用想doGET或POST或其它的方法。

5.Servlet的路径问题与自动加载

5.1 Servlet 的映射路径

<servlet-mapping>
    <!-- servlet的内部名称,一定要和上面的内部名称保持一致!! -->
    <servlet-name>FirstServlet</servlet-name>
    <!-- servlet的映射路径(访问servlet的名称) -->
    <url-pattern>/first</url-pattern>
</servlet-mapping>

Servlet的映射路径分为精确匹配和模糊匹配

注意:

1)url-pattern要么以 / 开头,要么以*开头。  例如, itcast是非法路径。
2)不能同时使用两种模糊匹配,例如 /demo/*.do是非法路径
3)当有输入的URL有多个servlet同时被匹配的情况下:
3.1 精确匹配优先。(长的最像优先被匹配)
3.2 以后缀名结尾的模糊url-pattern优先级最低!!!

5.2 Servlet 的缺省路径

servlet的缺省路径(<url-pattern>/</url-pattern>)是在tomcat服务器内置的一个路径。该路径对应的是一个DefaultServlet(缺省Servlet)。这个缺省的Servlet的作用是用于解析web应用的静态资源文件。

问题: URL输入http://localhost:8080/demo/index.html 如何读取文件?
1)到当前demo应用下的web.xml文件查找是否有匹配的url-pattern。
2)如果没有匹配的url-pattern,则交给tomcat的内置的DefaultServlet处理
3)DefaultServlet程序到demo应用的根目录下查找是存在一个名称为index.html的静态文件。
4)如果找到该文件,则读取该文件内容,返回给浏览器。
5)如果找不到该文件,则返回404错误页面。
结论: 先找动态资源,再找静态资源。

5.3 Servlet 的自动加载

默认情况下,第一次访问servlet的时候创建servlet对象。如果servlet的构造方法或init方法中执行了比较多的逻辑代码,那么导致用户第一次访问sevrlet的时候比较慢。
改变servlet创建对象的时机: 提前到加载web应用的时候!
在servlet的配置信息中,加上一个<load-on-startup>即可!

<servlet>
    <servlet-name>LifeDemo</servlet-name>
    <servlet-class>gz.itcast.c_life.LifeDemo</servlet-class>
    <!-- 让servlet对象自动加载 -->
    <load-on-startup>1</load-on-startup>  注意: 整数值越大,创建优先级越低!!
</servlet>

自动加载效果如下图所示,可以看到Servlet在Tomcat服务器启动时就执行了构造方法和init方法:


6. Servlet的有参和无参init方法

有参数的init方法是servlet的生命周期方法,一定会被tomcat服务器调用。如果要编写初始代码,不需要覆盖。无参数的init方法是servlet的编写初始化代码的方法。是Sun公司设计出来专门给开发者进行覆盖,然后在里面编写servlet的初始逻辑代码的方法。

public class InitDemo extends HttpServlet {

    /*
     * @Override public void init(ServletConfig config) throws ServletException
     * { System.out.println("有参数的init方法"); }
     */

    @Override
    public void init() throws ServletException {
        // System.out.println("无参数的init方法");
    }

}

7.Servlet的多线程并发问题

servlet对象在tomcat服务器是单实例多线程的。因为servlet是多线程的,所以当多个servlet的线程同时访问了servlet的共享数据,如成员变量,可能会引发线程安全问题。

解决办法:
1)把使用到共享数据的代码块进行同步(使用synchronized关键字进行同步)
2)建议在servlet类中尽量不要使用成员变量。如果确实要使用成员,必须同步。而且尽量缩小同步代码块的范围。(哪里使用到了成员变量,就同步哪里!!),以避免因为同步而导致并发效率降低。

public class TheradDemo extends HttpServlet {
    
    int count = 1;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        
        synchronized (TheradDemo.class) {//锁对象必须唯一。建议使用类对象
            response.getWriter().write("你现在是当前网站的第"+count+"个访客");   //线程1执行完  , 线程2执行
        
        //线程1还没有执行count++
        /*try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
            count++;
        }
    }

}

8.ServletConfig对象

ServletConfig对象: 主要是用于加载servlet的初始化参数。在一个web应用可以存在多个ServletConfig对象(一个Servlet对应一个ServletConfig对象)

8.1 对象创建和得到

创建时机: 在创建完servlet对象之后,在调用init方法之前创建。
得到对象: 直接从有参数的init方法中得到!!!

8.2 servlet的初始化参数配置

 <servlet>
    <servlet-name>ConfigDemo</servlet-name>
    <servlet-class>gz.itcast.f_config.ConfigDemo</servlet-class>
    <!-- 初始参数: 这些参数会在加载web应用的时候,封装到ServletConfig对象中 -->
    <init-param>
        <param-name>path</param-name>
        <param-value>e:/b.txt</param-value>
    </init-param>
  </servlet>

** Demo:**

public class ConfigDemo extends HttpServlet {
    /**
     * 以下两段代码GenericServlet已经写了,我们无需编写!!
     */
    /*private ServletConfig config;*/
    
    /**
     *  1)tomcat服务器把这些参数会在加载web应用的时候,封装到ServletConfig对象中 
     *  2)tomcat服务器调用init方法传入ServletConfig对象
     */
    /*@Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
    }*/
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        /**
         * 读取servlet的初始参数
         */
        String path = this.getServletConfig().getInitParameter("path");

        File file = new File(path);
        //读取内容
        BufferedReader br = new BufferedReader(new FileReader(file));
        String str = null;
        while( (str=br.readLine())!=null ){
            System.out.println(str);
        }
        
        //查询当前servlet的所有初始化参数
        Enumeration<String> enums = this.getServletConfig().getInitParameterNames();
        while(enums.hasMoreElements()){
            String paramName = enums.nextElement();
            String paramValue = this.getServletConfig().getInitParameter(paramName);
            System.out.println(paramName+"="+paramValue);
        }
        
        //得到servlet的名称
        String servletName = this.getServletConfig().getServletName();
        System.out.println(servletName);
    }

}

9.ServletContext对象

Servlet的上下文对象。表示一个当前的web应用环境。一个web应用中只有一个ServletContext对象。

9.1 对象创建和得到

创建时机:加载web应用时创建ServletContext对象。
得到对象: 从当前Servlet或者ServletConfig对象的getServletContext方法得到

** Demo:**

public class ContextDemo extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 1.得到ServletContext对象
        // ServletContext context = this.getServletConfig().getServletContext();
        ServletContext context = this.getServletContext(); // (推荐使用)

        // 2.得到web应用路径 
        /**
         * web应用路径:部署到tomcat服务器上运行的web应用名称
         */
        String contextPath = context.getContextPath();

        System.out.println(contextPath);

        System.out.println("参数" + context.getInitParameter("AAA"));

        Enumeration<String> enums = context.getInitParameterNames();
        while (enums.hasMoreElements()) {
            String paramName = enums.nextElement();
            String paramValue = context.getInitParameter(paramName);
            System.out.println(paramName + "=" + paramValue);
        }

        // 尝试得到ConfigDemo中的servlet参数
        String path = this.getServletConfig().getInitParameter("path");
        System.out.println("path=" + path);

        // 2.把数据保存到域对象中
        context.setAttribute("name", "eric");

        // 2.从域对象中取出数据
        // String name = (String) context.getAttribute("name");

        /**
         * 案例:应用到请求重定向
         */
        response.sendRedirect(contextPath + "/index.html");
    }

}

9.2 转发与重定向

转发与重定向都能实现页面的跳转,但在实际应用中大不相同,比较如下;

注意: 如果要使用request域对象进行数据共享,只能用转发技术!!!

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

推荐阅读更多精彩内容

  • 本文包括: Servlet简介关于Servlet的一些类 Servlet生命周期 ServletConfig获得初...
    廖少少阅读 3,840评论 1 67
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,182评论 11 349
  • 0 系列目录# WEB请求处理 WEB请求处理一:浏览器请求发起处理 WEB请求处理二:Nginx请求反向代理 本...
    七寸知架构阅读 13,882评论 22 190
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • Servlet学习的大纲 servlet概念及相关接口简介 servet 执行过程 servlet映射路径 缺省s...
    奋斗的老王阅读 1,183评论 1 51