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

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);

        }

}

在编写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特性。



作者:BigfaceMonster
链接:https://www.jianshu.com/p/e9f31c783ff1
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值