项目相关知识点javaJavaEE

理解Servlet

2017-01-18  本文已影响334人  maxwellyue

前言

这篇文章的出发点是为了整理Servlet相关知识点,以免在相关概念混淆或分不清的时候到处查阅资料。

一、什么是Servlet

Servlet是什么.png

说明:假如两个浏览器同时发送a请求,并不会创建两个相同的Servlet,这两个请求使用同一Servlet实例:Servlet是线程不安全的

二、Servlet生命周期

Servlet的生命周期要从Servlet接口来认识,Servlet接口一共5个方法,其中前三个是生命周期方法:

(1) void init(ServletConfig)
(2) void service(ServletRequest,ServletResponse)
(3) void destory()
(4) ServletConfig getServletConfig()
(5) String getServletInfo()
出生:服务器创建Servlet
(1)在<servlet>元素中配置<load-on-startup>元素可以让服务器在启动时就创建该Servlet

(2)假如多个<servlet>元素都欧配置了<load-on-startup>元素,那么就会按照数字大小顺序来启动,数字越小,启动越早!

(3)<load-on-startup>元素的值必须是大于等于0的整数。

<servlet>
        <servlet-name>hello2</servlet-name>
        <servlet-class>cn.itcast.servlet.Hello2Servlet</servlet-class>
        <load-on-startup>1</load-on-startup>
</servlet>

服务器默认是在servlet第一次被请求时创建Servlet实例,如果希望服务器启动时就创建Servlet实现需要在web.xml中配置。

长大:服务器初始化Servlet

工作:服务器使用Servlet处理请求

死亡:服务器销毁Servlet

三、实现Servlet的方式

1、实现Servlet有下面三种方式
  • 实现javax.servlet.Servlet接口;
public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {......}

public abstract class HttpServlet extends GenericServlet {......}
2、Servlet接口
public interface Servlet {
    public void init(ServletConfig config) throws ServletException;
    public ServletConfig getServletConfig();
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;
    public String getServletInfo();
    public void destroy();
String getServletName()
获取Servlet在web.xml文件中的配置名称,即<servlet-name>指定的名称;

ServletContext getServletContext()
用来获取ServletContext对象

String getInitParameter(String name)
用来获取在web.xml中配置的初始化参数,通过参数名来获取参数值;

Enumeration getInitParameterNames()
用来获取在web.xml中配置的所有初始化参数名称;

例如web.xml有如下servlet元素:

  <servlet>
    <servlet-name>Hello</servlet-name>
    <servlet-class>com.example.servlet.HelloServlet</servlet-class>
    <init-param>
        <param-name>paramName1</param-name>
            <param-value>paramValue1</param-value>
    </init-param>
    <init-param>
            <param-name>paramName2</param-name>
            <param-value>paramValue2</param-value>
    </init-param>
  </servlet>

那么,可以通过以下方法来获取相应的信息:

public class Hello implements Servlet {
    public void init(ServletConfig config) throws ServletException {
        config.getServletName();//将得到Hello
        config.getInitParameter("paramName1");//将得到paramValue1
        config.getInitParameter("paramName2");//将得到paramValue2
        ...
    }
     ...
}
3、GenericServlet
public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;
    public GenericServlet() {}
    @Override
    public void destroy() {}
    @Override
    public String getInitParameter(String name) {
        return getServletConfig().getInitParameter(name);
    }
    @Override
    public Enumeration<String> getInitParameterNames() {
        return getServletConfig().getInitParameterNames();
    }
    @Override
    public ServletConfig getServletConfig() {
        return config;
    }
    @Override
    public ServletContext getServletContext() {
        return getServletConfig().getServletContext();
    }
    @Override
    public String getServletInfo() {
        return "";
    }
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    public void init() throws ServletException {}
    public void log(String msg) {
        getServletContext().log(getServletName() + ": " + msg);
    }
    public void log(String message, Throwable t) {
        getServletContext().log(getServletName() + ": " + message, t);
    }
    @Override
    public abstract void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;
    @Override
    public String getServletName() {
        return config.getServletName();
    }
}

从代码中可以看出:

(1)GenericServlet定义了一个ServletConfig config实例变量,并在init(ServletConfig)方法中把参数ServletConfig赋给了实例变量,然后在该类的很多方法中使用了实例变量config。子类不能覆盖init(ServletConfig config),而应该去覆盖GenericServlet提供的init()方法,它是没有参数的init()方法。
(2)GenericServlet实现了ServletConfig接口,所以可以直接调用getInitParameter()getServletContext()ServletConfig的方法。

4、HttpServlet
public interface HttpServletRequest extends ServletRequest {......}

HttpServletRequest的方法:

String getParameter(String paramName)
获取指定请求参数的值;

String getMethod()
:获取请求方法,例如GET或POST;

String getHeader(String name)
:获取指定请求头的值;

void setCharacterEncoding(String encoding)
:设置请求体的编码。
因为`GET`请求没有请求体,所以这个方法只只对POST请求有效。
当调用`request.setCharacterEncoding(“utf-8”)`之后,
再通过`getParameter()`方法获取参数值时,
那么参数值都已经通过了转码,即转换成了UTF-8编码。
所以,这个方法必须在调用getParameter()方法之前调用!
public interface HttpServletResponse extends ServletResponse{......}

HttpServletResponse方法:

PrintWriter getWriter()
获取字符响应流,使用该流可以向客户端输出响应信息。
例如response.getWriter().print(“<h1>Hello JavaWeb!</h1>”);

ServletOutputStream getOutputStream()
获取字节响应流,当需要向客户端响应字节数据时,需要使用这个流;

void setCharacterEncoding(String encoding)
用来设置字符响应流的编码,
例如在调用setCharacterEncoding(“utf-8”);之后,
再response.getWriter()获取字符响应流对象,这时的响应流的编码为utf-8,
使用response.getWriter()输出的中文都会转换成utf-8编码后发送给客户端;

void setHeader(String name, String value)
向客户端添加响应头信息,
例如setHeader(“Refresh”, “3;url=http://www.example.cn”),
表示3秒后自动刷新到http://www.example.cn;

void setContentType(String contentType)
该方法是setHeader(“content-type”, “xxx”)的简便方法,
即用来添加名为content-type响应头的方法。
content-type响应头用来设置响应数据的`MIME`类型;

void sendError(int code, String errorMsg)
向客户端发送状态码,以及错误消息。
例如给客户端发送404:response(404, “您要查找的资源不存在!”)。

四、Servlet与线程安全

因为一个类型的Servlet只有一个实例对象,那么就有可能会出现一个Servlet同时处理多个请求的情况,那么Servlet是否为线程安全的呢?答案是:“不是线程安全的”。这说明Servlet的工作效率很高,但也存在线程安全问题:当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。
所以我们不应该在Servlet中随意创建成员变量,因为可能会存在一个线程对这个成员变量进行写操作,另一个线程对这个成员变量进行读操作。

所以:

Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet 时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该 Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行。
有助理解的一个例子:

public class Concurrent extends HttpServlet {
    PrintWriter output;
    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String username;
        response.setContentType("text/html; charset=gb2312");
        username = request.getParameter("username");
        output = response.getWriter();
        try {
            Thread.sleep(5000); // 为了突出并发问题,在这设置一个延时
        } catch (Exception e) {
        }
        output.println("用户名:" + username + "<BR>");
    }
}

该Servlet中定义了一个实例变量output(servlet实例的成员变量),在service方法将其赋值为用户的输出。当一个用户访问该Servlet时,程序会正常的运行;但当多个用户并发访问时,就可能会出现其它用户的信息显示在另外一些用户的浏览器上的问题。
解决方法:

将前面的Concurrent Test类的类头定义更改为:

Public class Concurrent Test extends HttpServlet implements SingleThreadModel {
    ……
}
public class Concurrent extends HttpServlet {
    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        PrintWriter output;
        String username;
        
        response.setContentType("text/html; charset=gb2312");
        username = request.getParameter("username");
        output = response.getWriter();
        try {
            Thread.sleep(5000); // 为了突出并发问题,在这设置一个延时
        } catch (Exception e) {
        }
        output.println("用户名:" + username + "<BR>");
    }
}
public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String username;
        response.setContentType("text/html; charset=gb2312");
        username = request.getParameter("username");
        
        synchronized(this){
            output = response.getWriter();
            try {
                Thread.sleep(5000); // 为了突出并发问题,在这设置一个延时
            } catch (Exception e) {
            }
            output.println("用户名:" + username + "<BR>");
        }
    }

五、<url-pattern>

<servlet-mapping>
    <servlet-name>AServlet</servlet-name>
    <url-pattern>/AServlet</url-pattern>
    <url-pattern>/BServlet</url-pattern>
  </servlet-mapping>

说明一个Servlet绑定了两个URL,无论访问/AServlet还是/BServlet,访问的都是AServlet。

(1)所谓通配符就是星号*,使用通配符可以命名一个Servlet绑定一组URL。
(2)通配符要么为前缀,要么为后缀,不能出现在URL中间位置,也不能只有通配符。
(3)通配符是一种模糊匹配URL的方式,如果存在更具体的<url-pattern>,那么访问路径会去匹配具体的<url-pattern>

六、ServletContext

6.1 ServletContext概述
6.2 获取ServletContext

void init(ServletConfig config)中:ServletContext context = config.getServletContext();ServletConfig类的getServletContext()方法可以用来获取ServletContext对象;

直接使用this.getServletContext()来获取;

6.3 ServletContext对象的作用
void setAttribute(String name, Object value)
用来存储一个对象,也可以称之为存储一个域属性,
例如:servletContext.setAttribute(“xxx”, “XXX”),在ServletContext中保存了一个域属性,域属性名称为xxx,域属性的值为XXX。
请注意,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值,这一特性与Map相同;

Object getAttribute(String name)
用来获取ServletContext中的数据,当前在获取之前需要先去存储才行,
例如:String value = (String)servletContext.getAttribute(“xxx”);,获取名为xxx的域属性;

void removeAttribute(String name)
用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;

Enumeration getAttributeNames()
获取所有域属性的名称;

(1)Servlet也可以获取初始化参数,但它是局部的参数;也就是说,一个Servlet只能获取自己的初始化参数,不能获取别人的,即初始化参数只为一个Servlet准备!
(2)可以配置公共的初始化参数,为所有Servlet而用!这需要使用ServletContext才能使用!
(3)还可以使用ServletContext来获取在web.xml文件中配置的应用初始化参数!注意,应用初始化参数与Servlet初始化参数不同。

<web-app ...>
  ...
  <context-param>
    <param-name>paramName1</param-name>
    <param-value>paramValue1</param-value>      
  </context-param>
  <context-param>
    <param-name>paramName2</param-name>
    <param-value>paramValue2</param-value>      
  </context-param>
</web-app>
上一篇 下一篇

猜你喜欢

热点阅读