Servlet详解(一)

什么是Servlet

Servlet(Server Applet),全称Java Servlet。一个Java Servlet就是一个小型Java应用程序,它可以继承HttpServlet实现,运行在Web服务器中。Servlet 会接收并响应来自浏览器的请求,通常是基于Http协议的请求。

如下图:

创建Servlet

实现Servlet或者继承Servlet的实现类:

方法一:实现Servlet接口

1> 实现接口Servlet并实现以下几个 生命周期 方法:

  • init(ServletConfig config):当Servlet被创建时,将会使用此方法对自己初始化

  • service(ServletRequest req,ServletResponse res):当浏览器对服务器发出请求后,servlet会使用该方法处理请求

  • destory():当Servlet处理完请求后,在销毁前会调用此方法,然后GC会将它回收掉

生命周期:

当client访问Servlet时,创建并初始化Servlet, 在服务器中只有一个Servlet实例,当服务器被关闭时,Servlet才会被销毁。

package com.feathers.servletdemo;
import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet implements Servlet {

    private ServletConfig config;

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("init(): servlet 初始化");
        this.config = config;
    }

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        System.out.println("service():servlet 收到请求了,开始取出请求的数据(request)并作出处理,\n 并将处理后的结果放置到响应对象(response)中");
        // 响应对象向客户端写东西
        response.getWriter().write("Hello Client, I'm Servlet");
    }

    @Override
    public void destroy() {
        System.out.println("destory(): servlet被销毁");
    }

    @Override // 获取配置信息,键值对的方式
    public ServletConfig getServletConfig() {
        return config;
    }

    @Override  // 返回servlet信息,比如servlet的author version copyright等
    public String getServletInfo() {
        return null;
        // return "Feather著作 Feathers 版权所有";
    }
}

2> 在 web.xml 中注册Servlet,告诉服务器有这么一个Servlet

<!-- 注册servlet到服务器 -->
  <servlet>
    <servlet-name>HelloServlet</servlet-name><!-- 注册名称 -->
    <servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class><!-- java字节码路径 -->
  </servlet>
  <!-- 给注册过的Servlet配置路径 -->
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name><!-- 给哪一个Servlet配置路径 -->
    <url-pattern>/HelloServlet</url-pattern><!-- 要配置的路径,servle访问路径  / 代表项目路径 -->
  </servlet-mapping>

什么?太麻烦了?还好我们有注解 @WebServlet

使用java注解注册Servlet

@WebServlet("/FirstServlet") // 后面是Servlet的访问路径

@WebServlet(
    name="Hello",   // Servlet名称
    urlPatterns={"/hello.view"}, // 访问路径
    loadOnStartup=1 // 加载级别
)
public class HelloServlet extends HttpServlet {

启动服务器,访问此Servlet:

在tomcat中的项目结构:

我们可以看到,Java编译的class文件全部被eclipse放置到WEB-INF—> classes目录下了。

输出:

生命周期时序图:

服务器是如何查找Servlet的?

我们看到,通过 <url-pattern> 访问servlet,最后访问到他的class文件。

首先根据 <url-pattern> 找到它的name,然后根据name找到 <servlet> <name> 相同的servlet标签,根据name找到 <servlet-class> class路径,使用找到的class生成servlet实例到服务器内存中。

ServletConfig接口:

servlet容器使用servlet配置对象,该对象在初始化期间将信息传递给ServletConfig。

  • String getInitParamter(String name):根据初始化参数名称获取初始化参数的值,如果没有此参数,返回null

  • Enumeration getInitParamterNames():获取所有初始化参数名称,以枚举方式返回,如果没有参数,返回一个空的枚举

web.xml 中初始化参数:

  <servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class>
    <!-- 配置两个初始化参数 -->
    <init-param>
        <param-name>name</param-name><!-- key -->
        <param-value>Feathers</param-value><!-- value -->
    </init-param>
    <init-param>
        <param-name>age</param-name>
        <param-value>100</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/HelloServlet</url-pattern>
  </servlet-mapping>

在Servlet service()中对初始化参数进行处理:

package com.feathers.servletdemo;
import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet implements Servlet {

    private ServletConfig config;
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
    }

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 响应对象向客户端写东西
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("Hello Client, I'm Servlet");
        // 获取初始化参数 作出处理
        Enumeration<String> enums = getServletConfig().getInitParameterNames();
        while (enums.hasMoreElements()){
            String key = enums.nextElement();
            String value = getServletConfig().getInitParameter(key);
            response.getWriter().write(key+":"+value + "<br/>");
        }
    }

    @Override
    public void destroy() {
    }

    @Override
    public ServletConfig getServletConfig() {
        return config;
    }

    @Override
    public String getServletInfo() {
        return null;
    }

}

请求结果如下:

  • getServletName():获取Servlet的名称 <servlet-name>

  • getServletContext():获取ServletContext上下文对象

点击form表单提交,跳转servlet

<!-- 注意action, 这个是Servlet的访问路径,但是不能加/ 不能加/ 不能加/  -->
<form action="FirstServlet" method="get">
        <table>
            <tr>
                <td>用户名:</td>
                <td><input type="text" name="username" value=""/></td>
            </tr>
            <tr>
                <td>密码:</td>
                <td><input type="password" name="password" value=""/></td>
            </tr>
            <tr>
                <td><input type="checkbox" name="remember" value=“yes”/>记住用户名</td>
            </tr>
            <tr>
                <td><input type="submit" value="提交"/></td>
            </tr>
        </table>
    </form>

方法二、继承GenericServlet

所谓的GenericServlet只是一个Servlet的实现类,因为用户每次使用Servlet接口自定义一个Servlet都会进行许多不必要的操作,所以就封装了一个GenericServlet以方便程序员使用,

使用方法:在方法service()中进行请求的处理,具体请查看源码。

下面是它的源码:

    package javax.servlet;

    import java.io.IOException;
    import java.util.Enumeration;

    //GenericServlet实现了Servlet、ServletConfig、Serializable三个接口
    public abstract class GenericServlet
        implements Servlet, ServletConfig, java.io.Serializable
    {
        private transient ServletConfig config;

        //无参的构造方法
        public GenericServlet() { }

        /*
        实现接口Servlet接口生命周期初始化init(ServletConfig Config)方法,将ServletConfig对象保存到成员变量中,以扩展他的生命周期,让service方法也能访问
        **/

        public void init(ServletConfig config) throws ServletException {
          this.config = config;
          this.init(); // 用户的初始化方法
        }

        public void init() throws ServletException {
        }

        public void destroy() { }

        //返回ServletConfig对象
        public ServletConfig getServletConfig(){
           return config;
        }

        //该方法实现接口<Servlet>中的ServletInfo,默认返回空字符串
        public String getServletInfo() {
           return "";
        }

       // service方法没有去实现,而是丢给使用者去实现
        public abstract void service(ServletRequest req, ServletResponse res)
     throws ServletException, IOException;

      // 为了方便用户调用,简化操作,实现了ServletConfig接口,这样就不用使用getServletConfig().getServletContext()获取对象了
      // 只需使用getServletContext即可
      // 即实现ServletConfig的目的
        public ServletContext getServletContext() {
           return getServletConfig().getServletContext();
        }

        public String getInitParameter(String name) {
         return getServletConfig().getInitParameter(name);
        }

        public Enumeration getInitParameterNames() {
           return getServletConfig().getInitParameterNames();

        public String getServletName() {
            return config.getServletName();
        }

        public void log(String msg) {
           getServletContext().log(getServletName() + ": "+ msg);
        }

        public void log(String message, Throwable t) {
           getServletContext().log(getServletName() + ": " + message, t);
        }
    }

方法三、继承HttpServlet

HttpServlet继承自GenericServlet,是针对Http协议的Servlet,是进一步的封装,

使用只需要继承HttpServlet,重写doGet或doPost方法即可,doGet用来处理get请求,而doPost方法用来进行post请求处理。

源码如下:

    package javax.servlet.http;
    .....

    public abstract class HttpServlet extends GenericServlet
        implements java.io.Serializable
    {

        private static final String METHOD_GET = "GET";
        private static final String METHOD_POST = "POST";
        ......
        public HttpServlet() { }

        public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException
        {
        // 1. 强转ServletRequest与ServletResponse
            HttpServletRequest        request;
            HttpServletResponse        response;

            try {
                request = (HttpServletRequest) req;
                response = (HttpServletResponse) res;
            } catch (ClassCastException e) {
                throw new ServletException("non-HTTP request or response");
            }
            //调用重载的service()方法
            service(request, response);
        }

        //重载的service方法
        protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
        {
            // 根据操作的不同,作出不同的处理
            // 处理方法有 doGet,doPost,doHead,doDelete...
            // 这些都是http协议中的方法,get请求,post修改,delete删除,put上传等。

            // 获取请求方式
            String method = req.getMethod();

            if (method.equals(METHOD_GET)) {  // get请求
                long lastModified = getLastModified(req);
                if (lastModified == -1) {
                    doGet(req, resp);
                } else {
                    long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                    if (ifModifiedSince < (lastModified / 1000 * 1000)){
                        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)) {   // post请求
                doPost(req, resp);

            } else if (method.equals(METHOD_PUT)) {  // 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 {
                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);
            }
        }

        ......

protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
        {
            String protocol = req.getProtocol();
            String msg = lStrings.getString("http.method_get_not_supported");
            if (protocol.endsWith("1.1")) {
                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
            } else {
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
            }
        }

        protected void doHead(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
        {
            .......
        }
        protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
        {
            String protocol = req.getProtocol();
            String msg = lStrings.getString("http.method_post_not_supported");
            if (protocol.endsWith("1.1")) {
                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
            } else {
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
            }
        }
        protected void doPut(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException  {
            //todo
        }

        protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
           //todo
        }

        protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException   {
          //todo
        }

        protected void doDelete(HttpServletRequest req,
                                HttpServletResponse resp)
            throws ServletException, IOException   {
            //todo
        }

    }

Servlet线程安全问题:

Servlet存在这线程安全的问题,

假设在Servlet中放置一个成员变量name,用来接受用户的表单信息。当多个用户访问Servlet时,会对name进行多次赋值,那么用户得到的结果就很有可能出错,因为多个用户共用一块内存。

解决办法也很简单,让每个用户都有一块内存保存自己的name值,只需要将name值保存在方法中,建立一个局部变量即可。因为java方法在被调用时,每次被调用,都会开辟一个新的方法区。

修改Servlet创建时机

通常情况下,servlet在被用户第一次调用时创建,如果servlet要进行非常耗时的创建操作,用户就会等待很久,影响用户体验,所以,我们可以将servlet设置在服务器启动之后,解决这类问题:

web.xml 中,添加加载时间标签:

<servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.feathers.servletdemo.HelloServlet</servlet-class>
    <!-- 让servlet随项目的启动而启动 value: 0-5  数字越小,启动优先级越高       如果多个servlet优先级相同,则按照配置顺序启动-->
    <load-on-startup>3</load-on-startup>
  </servlet>

Servlet访问路径配置

相对路径:

/ 代表 项目路径

/AServlet => http://localhost:8080/ServletDemo/Aservlet

/File/* => /File/ => http://localhost:8080/ServletDemo/File/sfsdfdsfsdfe 匹配任意

匹配范围越大,优先级越低:

两个配置 /File/AServlet/File/*

访问: http://localhost:8080/ServletDemo/File/AServlet

则会访问AServlet,而不会访问第二个路径,因为第一个优先级高。

后缀名匹配:

*.dohttp://localhost:8080/ServletDemo/sfdsfds.do

该方式不常用,Filter过滤器常用

注意:不能同时使用 后缀名 匹配和 相对路径。例如: /File/*.do

ServletContext

项目级别的对象,一个Web项目,有且只有一个ServletContext对象,在项目启动时创建,到项目关闭时销毁,可以理解为这个类集合了项目所有的功能方法,代表了项目,所以这个类非常强大。

我们使用 ServletConfig.getServletContext() 获得这个对象。

功能如下:

获取项目参数

配置项目初始化参数:

<webapp>
  <context-param>
    <param-name>name</param-name>
    <param-value>tom</param-value>
  </context-param>
</webapp>

获取项目初始化参数:

getServletContext().getInitParamter(String name)

getServletContext().getInitParamterNames()

ServletContext 域

域是服务器两个组件之间的通讯,比如两个Servlet之间通讯。

Application域

在ServletContext中,有一个 Map<String,Object> 集合,用来保存信息,这个Map集合就是ServletContext的域。

AServlet获取ServletContext对象,向域中添加信息,BServlet也可以获取Servlet,然后从ServletContext域中获取AServlet填入的信息。

这样,每个Servlet之间就可以共享信息了。

同样存在线程不安全的问题。

/*AServlet*/
// 1. 获取ServletContext
ServletContex scontext = getServletContext();
// 2. 向域中存放键值对
scontext.setAttribute(key,object);

/*BServlet*/
// 3. 在另一个Servlet中从域中取出键值对
getServletContext().getAttribute(key);
// 4. 删除key
getServletContext().removeAttribute(key);
// 5. 获取所有建
getServletContext().getAttributeNames();//返回枚举

ServletContext获取项目内的资源

获取WebRoot/WebContent下的 user.xml 文件:

InputStream is = getServletContext().getResourceAsStream("/user.xml"); // 获得资源流
String path = getServletContext().getRealPath("/user.xml");// 获取资源绝对路径(tomcat webapp下 项目的部署路径)

// 获取WEB-INFO下的`user.xml`文件
getServletContext().getResourceAsStream("/WEB-INFO/user.xml");

获取class资源:

// 方法1:使用上面的方法
getServletContext().getResourceAsStream("/WEB-INFO/classes/com/feathers/servlet/AServlet.class"); // 麻烦

// 方法2:
// “/” 代表src目录,或者是classes目录,详见getResourceAsStream方法
InputStrean is2 = AServlet.class.getResourceAsStream("/user.xml");

最后更新于