Java学习资源来到JavaEE

JavaWeb基础之Servlet全解析

2017-06-05  本文已影响149人  Acamy丶

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域对象进行数据共享,只能用转发技术!!!

上一篇下一篇

猜你喜欢

热点阅读