根据Servlet-API源码分析学习Servlet

作者: BigfaceMonster | 来源:发表于2016-08-15 13:45 被阅读3106次

    Servlet

    介于Servlet是Java Web开发的基础,因此好好看了一下Servlet3.0.1的源码,于是有了这篇记录。


    Servlet架构图

    Servlet和JSP是众多java EE定义的技术当中的两种,其他还有JMS,EJB等等,运行JEE程序需要一个JEE容器,如GlassFish、JBOSS、WebLogic等,Servlet\JSP也可以部署在JEE容器中,不过用Servlet/JSP容器已经足够了,而且比JEE容器更加轻量化,Tomcat和Jetty不属于JEE容器,不能运行EJB或JMS。

    Servlet API概述

    首先,下载Servlet-api源码,可以使用IDE,maven等方式,本文使用maven下载,命令如下(或者自行搜索下载源码):

    
    maven dependency:sources
    
    

    servlet API中有4个Java包,包括:

    • javax.servlet 定义Servlet和Servlet容器之间的七月类和接口。

    • javax.servlet.http 定义HTTP Servlet与Serlvet容器之间的契约和接口

    • javax.servlet.annotation 包含对Servlet、Filter和Listener进行标注的注解。还为标注元件指定元数据。

    • javax.servlet.descriptor 包含为Web应用程序的配置星系提供编程式访问的类型。

    javax.servlet包

    不完整截图:

    javax.servlet方法一览

    Servlet包中的主要成员:

    Servlet主要成员

    Servlet接口是核心接口,是所有Serlvet都必须直接或间接实现的一个接口,Servlet接口定义了Servlet与Servlet容器之间的约定,总的来说就是Servlet容器会把Servlet类加载到内存中,并在Servlet实例中调用特定方法,在一个应用程序中,每个Servlet类型只能有一个实例。当用户的请求引发service方法,并给这个方法传入一个ServletRequest实例和一个ServletResponse实例。ServletRequest封装当前的HTTP请求,让开发者不必去解析和操作原始的HTTP数据,同理,ServletResponse表示当前用户的HTTP响应,它的作用是使得将响应回传给用户更容易。Servlet容器还为每个应用程序创建一个ServletContext实例。这个对象封装context(应用程序)的环境细节,而每个context只有一个ServletContext。每个Servlet实例还有一个封装Servlet配置信息的ServletConfig。

    Servlet

    下面先看看Servlet接口,(如果别人问你什么是Servlet,你可以告诉他,就是一个java接口,分分钟定义出来给你看,不过人们常说的Servlet应该是指任何实现了Servlet接口的类)看源码发现有234行(3.0.1版),仔细一看也就5个抽象方法,其他的全是注释,所以兄弟们,看源码不要虚,这就是传说中的和Servlet容器之间的约定(有没有很熟悉的感觉):

    Servlet接口
    • init 第一次请求我们编写的Serlvet时,Servlet容器调用此方法,后续不在调用,可以利用这个方法做一些初始化的工作。在调用这个方法时,Servlet容器会传递一个ServletConfig。一般会将这个ServletConfig赋给一个类级变量,以方便其他方法也可以使用这个对象。

    • service 每次用户请求service时,servlet容器都会调用这个方法,我们对请求的处理就是在这里完成的。

    • destroy 要销毁Servlet时,Servlet容器就会调用这个方法,它通常发生在卸载应用程序,或者关闭Servlet容器的时候,这里一般我们会写一些资源清理相关的代码。

    Servlet中另外另个费生命周期的方法:getServletInfo和getServletConfig

    • getServletInfo 就是字面意思,返回Servlet的描述

    • getServletConfig 这个方法返回由Servlet容器传给init方法的ServletConfig,上面说了,一般在init方法中将ServletConfig赋给一个类级变量,免得本方法返回null。

    PS:由于Servlet不是线程安全的,一个应用程序中所有的用户公用一个Servlet实例,因此不建议使用类级别的变量(只使用局部变量最好),除非是只读的或者java.utilconcurrent.atomic包中的成员。

    Servlet基础应用程序

    现在来写一个Servlet应用程序,写起来很简单,只要创建一个目录,并将Servlet类放在指定目录中就可以了,同时,如果要运行这个应用程序,你还需要安装一个Servlet容器,如Tomcat或者Jetty(安装方法自行搜索)。

    编写Servlet应用

    Servlet应用目录结构

    以上为Servlet的目录结构,要编译servlet,类路径中还要有servlet API,tomcat容器中已经自带了servlet-api.jar。另外珍爱生命,还是用IDE来创建吧,博主试过,自己去一步一步创建配置虽说也可以,但是确实会花费不少时间,如果你就是要自己一步一步创建,觉得这样能学到更多东西,我只能说加油骚年!

    应用程序中一般会有JSP、HTML、图像等其他资源,这些都应该放在应用程序的目录下面,并且经常放在子目录下,如上图,html放在html文件下,jsp放在jsp目录下。放在应用程序目录下的任何资源,用户可以通过资源的URL直接访问(放在应用程序目录下当然要可以访问了),如果希望某个资源可以被Servlet访问,但不能被用户访问,那就应该放在WEB-INF目录下(是不是找到该目录的作用了)。

    
    import java.io.IOException;
    
    import java.io.PrintWriter;
    
    
    
    import javax.servlet.Servlet;
    
    import javax.servlet.ServletConfig;
    
    import javax.servlet.ServletException;
    
    import javax.servlet.ServletRequest;
    
    import javax.servlet.ServletResponse;
    
    import javax.servlet.annotation.WebServlet;
    
    
    
    //name可选,提供servlet名,关键urlPatterns指定匹配当前servlet的模式
    
    @WebServlet(name="/FirstServlet",urlPatterns={ "/myapp" })
    
    public class FirstServlet implements Servlet {
    
        private transient ServletConfig config;
    
        @Override
    
        public void init(ServletConfig config) throws ServletException {
    
            //将config给类变量,扩大使用范围
    
            this.config=config;
    
        }
    
        @Override
    
        public ServletConfig getServletConfig() {
    
            return config;
    
        }
    
        @Override
    
        public String getServletInfo() {
    
            return "My Servlet";
    
        }
    
        @Override
    
        public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
    
            String servletName=config.getServletName();
    
            response.setContentType("text/html");
    
            PrintWriter pw=response.getWriter();
    
            pw.write("hello from "+servletName);
    
        }
    
        @Override
    
        public void destroy() {}
    
    }
    
    

    以上程序部署完成,启动Servlet容器,就可以通过url在浏览器中访问了:

    通过访问 http://localhost:8080/App01/FirstServlet

    效果图

    ServletRequest & ServletResponse

    对于每一个HTTP请求,servlet容器会创建一个封装了HTTP请求的ServletRequest实例传递给servlet的service方法,ServletResponse则表示一个Servlet响应,其影藏了将响应发给浏览器的复杂性。通过ServletRequest的方法你可以获取一些请求相关的参数,而ServletResponse则可以将设置一些返回参数信息,并且设置返回内容。返回内容之前一般会调用setContentType方法设置响应的内容类型,如果没有设置,大多数浏览器会默认以html的形式响应,不过为了避免出问题,我们一般都设置该项。

    值得注意的是ServletResponse中定义的getWriter方法,它返回可以将文本传给客户端的java.io.PrintWriter。在默认的情况下,PrintWriter对象使用ISO-8859-1编码,这有可能引起乱码。

    以下为ServletRequest和ServletResponse的大部分方法:

    ServletRequest ServletResponse

    ServletConfig

    ServletConfig封装可以通过@WebServlet或者web.xml传给一个Servlet的配置信息,以这种方式传递的每一条信息都称做初始化信息,初始化信息就是一个个K-V键值对。为了从一个Servlet内部获取某个初始参数的值,init方法中调用ServletConfig的getinitParameter方法或getinitParameterNames方法获取,除此之外,还可以通过getServletContext获取ServletContext对象。方法签名:

    ServletConfig

    通过WebServlet传递配置信息示例:

    WebServlet初始化参数

    ServletContext

    ServletContext是代表了Servlet应用程序。每个Web应用程序只有一个context。在分布式环境中,一个应用程序同时部署到多个容器中,并且每台Java虚拟机都有一个ServletContext对象。有了ServletContext对象后,就可以共享能通过应用程序的所有资源访问的信息,促进Web对象的动态注册,共享的信息通过一个内部Map中的对象保存在ServiceContext中来实现。保存在ServletContext中的对象称作属性。操作属性的方法:

    ServletContext

    GenericServlet

    前面编写的Servlet应用中通过实现Servlet接口来编写Servlet,但是我们每次都必须为Servlet中的所有方法都提供实现,还需要将ServletConfig对象保存到一个类级别的变量中,GenericServlet抽象类就是为了为我们省略一些模板代码,实现了Servlet和ServletConfig,完成了一下几个工作:

    • 将init方法中的ServletConfig赋给一个类级变量,使的可以通过getServletConfig来获取。
    
    public void init(ServletConfig config) throws ServletException {
            this.config = config;
            this.init();
      }
    
    

    同时为避免覆盖init方法后在子类中必须调用super.init(servletConfig),GenericServlet还提供了一个不带参数的init方法,当ServletConfig赋值完成就会被第带参数的init方法调用。这样就可以通过覆盖不带参数的init方法编写初始化代码,而ServletConfig实例依然得以保存(这难道不是适配器模式吗?)

    • 为Servlet接口中的所有方法提供默认实现。

    • 提供方法来包装ServletConfig中的方法。

    用GenericServlet实现Servlet应用

    
    import java.io.IOException;
    
    import java.io.PrintWriter;
    
    
    
    import javax.servlet.GenericServlet;
    
    import javax.servlet.ServletConfig;
    
    import javax.servlet.ServletException;
    
    import javax.servlet.ServletRequest;
    
    import javax.servlet.ServletResponse;
    
    import javax.servlet.annotation.WebInitParam;
    
    import javax.servlet.annotation.WebServlet;
    
    
    
    @WebServlet(name="SecondServlet",
    
             urlPatterns={"/generic"},
    
             initParams={
    
                 @WebInitParam(name="user",value="xiaobai"),
    
                 @WebInitParam(name="email",value="xiaobai@example.com")
    
             }
    
    )
    
    public class SecondServlet extends GenericServlet {
    
            @Override
    
            public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
    
                 ServletConfig config=getServletConfig();
    
                 String user=config.getInitParameter("user");
    
                 String email=config.getInitParameter("email");
    
                 response.setContentType("text/html");
    
                 PrintWriter pw=response.getWriter();
    
                 pw.write("User:"+user+"<br>email:"+email);
    
            }
    
    }
    
    

    HTTPServlet

    在编写Servlet应用程序时,大多数都要用到HTTP,也就是说可以利用HTTP提供的特性,javax.servlet.http包含了编写Servlet应用程序的类和接口,其中很多覆盖了javax.servlet中的类型,我们自己在编写应用时大多时候也是继承的HttpServlet,以下为其中的重要成员:

    HttpServelt主要成员

    从上图看,HttpServlet继承了GenericServlet,HttpServletRequest/Response继承了覆盖了ServletRequest/Response,成为了新的Servlet请求和响应的代表。在HttpServlet中覆盖了GenericServlet的service方法,并用新的Servlet请求和响应代表作为参数添加了一个service方法:

    
    //覆盖GenereicServlet中的service
    
    public void service(ServletRequest req, ServletResponse res)
    
           throws ServletException, IOException
       {
               HttpServletRequest  request;
               HttpServletResponse response;       
               if (!(req instanceof HttpServletRequest &&
                       res instanceof HttpServletResponse)) {
                   throw new ServletException("non-HTTP request or response");
               }
               request = (HttpServletRequest) req;
               response = (HttpServletResponse) res;
               service(request, response);
           }
    }
    
    //新service方法签名
    protected void service(HttpServletRequest req, HttpServletResponse resp)
    
    

    原始的service方法将请求和响应进行向下转换,分别为HttpServletRequest和HttpServletResponse,并调用新的service方法。看了下2.5版本中的实现,发现没有加以上代码是中的instanceof判断,恩,看来2.5中直接向下转型确实暴力了点,考虑容器不一定总是把请求当做HTTP请求,这样做看起来稳妥了些。新的service方法会查寻HTTP请求的方法从而调用do{Method}来处理请求。

    总之HttpServlet中有两项特性是GenericServlet中没有的:

    • 不覆盖service方法,而是覆盖doGet、doPost等。

    • 用HttpServletRequest\Response 替代ServletRequest\Response

    HttpServletRequest,HttpServletResponse由于带有了HTTP的特性,因此除了ServletRequest,ServletResponse中的方法之外还增加了几个可以获取HTTP特性信息的方法。

    
    //获取context的请求URI部分
    
    java.lang.String getContextPath()
    
    //获取Cookie对象数组
    
    Cookie [] getCookies()
    
    //返回指定HTTP标头的值
    
    java.lang.String getHeader(java.lang.String name)
    
    //返回发出这条请求的HTTP方法的名称
    
    java.lang.String getMethod()
    
    //返回请求URL中的查询字符串
    
    java.lang.String getQueryString()
    
    //获取session对象,没找到就新创建
    
    HttpSession getSession()
    
    //返回与这个请求相关的session对象,如果没有,并且create参数为true,创建新的session对象
    
    //响应对象添加cookie
    
    void addCookie(Cookie cookie)
    
    //添加标头
    
    void addheader(String name,String value)
    
    //重定向
    
    void sendRedirect(String location)
    
    

    使用web.xml配置Servlet应用

    在Servlet3.x中可以使用@WebServlet来部署应用,可以不必在WEB-INF目录下放一个web.xml配置文件,当然也可以同时使用,前者优先级更高,这是annotation为我们带来的好处,介于使用web.xml来配置Servlet也有其有点,就简单介绍下,使用web.xml配置优点:

    • 不用更改代码,也就意味着不需要重新编译

    • 可包含@WebServlet中没有的元素,如load-on-startup元素,init方法比较费时间这个就很有帮助了。

    
    <?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">
    
       <servlet>
           <servlet-name>SimpleServlet</servlet-name>
           <servlet-class>app01.SimpleServlet</servlet-class>
           <load-on-startup>2</load-on-startup>
       </servlet>
       <servlet-mapping>
           <servlet-name>SimpleServlet</servlet-name>
           <url-pattern>/simple</url-pattern>
       </servlet-mapping>
    
    </web-app>
    
    

    现在再来看下我们我们通常写的Servlet,看完上文的客官,看到下面Servlet是不是感觉自己看到了更多的东西呢?反正我是看到了。讲真,如果想要缕下Servlet的话,真的可以试一试下载Servlet-api的源码看看,结合本文,或许风味更佳哦!!

    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    
    import javax.servlet.annotation.WebServlet;
    
    import javax.servlet.http.HttpServlet;
    
    import javax.servlet.http.HttpServletRequest;
    
    import javax.servlet.http.HttpServletResponse;
    
    
    
    @WebServlet("/normal")
    
    public class NormalServlet extends HttpServlet {
    
        private static final long serialVersionUID = 1L;
    
    
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
            response.getWriter().append("Served at: ").append(request.getContextPath());
    
        }
    
    
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
            doGet(request, response);
    
        }
    
    }
    
    

    小结

    Servlet技术是Java EE技术的重要组成,Servlet容器中运行的所有Servlet,以及容器与Servlet之间的约定,都采用了javax.servlet.Servlet接口的形式。javax.servlet包也提供了实现Servlet接口的GenericServlet抽象类。这是一个比较方便的类,可以通过扩展它来创建Servlet。但是大多数的现代Servlet都在HTTP环境中处理请求,因此提供了javax.servlet.http.HttpServlet来继承GenericServlet并且加入HTTP特性。

    相关文章

      网友评论

      • SkyLine7:博主有个地方写错了,servlet是线程不安全的,所有请求都共用一个实例,所以才不能用类变量而是要用局部变量。
        BigfaceMonster:@SkyLine7 感谢skyline大人指正:grin:
      • e748608f2a58:Servlet并不是线程安全的

      本文标题:根据Servlet-API源码分析学习Servlet

      本文链接:https://www.haomeiwen.com/subject/uceusttx.html