Servlet

Posted yusiming

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Servlet相关的知识,希望对你有一定的参考价值。

 

Servlet

  Servlet是一个运行在Web服务器中的一个Java小程序,它能够接受和处理客户端的请求,并完成对客户端的响应。Servlet是Web应用程序中的核心类,它可以直接处理请求,也可以将请求委托给应用程序中的别的部分进行处理(请求转发、包含),所有的web服务器都内建了一个或者多个servlet,用来处理JSPhtml页面、图片等等,所以说所有的请求都是要基于servlet的,即使在请求JSP也需要servlet的处理才能够完成,一般我们不需要编写处理JSP、HTML的servlet,因为web容器已经帮我们完成了这个任务。

  可以在tomcat的conf目录下的web.xml文件中查看,有关处理JSP的servlet的配置,下面这段配置是tomcat默认配置(这里省略了一些servlet的配置),这个文件相当于写在了每一个使用tomcat做容器的web应用程序之中, org.apache.jasper.servlet.JspServlet这个类会处理所有访问JSP的请求,并且这个servlet在服务器启动时,就会被初始化.

 1   <servlet>
 2 
 3   <servlet-name>jsp</servlet-name>
 4 
 5   <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
 6 
 7   <load-on-startup>3</load-on-startup>
 8 
 9   </servlet>
10 
11  
12 
13   <servlet-mapping>
14 
15   <servlet-name>jsp</servlet-name>
16 
17   <url-pattern>*.jsp</url-pattern>
18 
19   <url-pattern>*.jspx</url-pattern>
20 
21   </servlet-mapping>

 

  还有一个默认的servlet,这个servle在服务器启动时就会被创建,如果没有任何servlet处理请求,那么这个servlet会处理,这个servlet匹配一切路径,这个servlet的service方法会给客户端发送状态码404

 1   <servlet>
 2 
 3   <servlet-name>default</servlet-name>
 4 
 5   <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
 6 
 7   <load-on-startup>1</load-on-startup>
 8 
 9   </servlet>
10 
11  
12 
13   <servlet-mapping>
14 
15   <servlet-name>default</servlet-name>
16 
17   <url-pattern>/</url-pattern>
18 
19   </servlet-mapping>

 

 

所有的Servlet都实现了javax.servlet.Servlet接口,接口中有如下五个方法:

 1 public class HelloServlet implements Servlet {
 2     private ServletConfig servletConfig;
 3     @Override
 4     public void init(ServletConfig servletConfig) throws ServletException {
 5         this.servletConfig = servletConfig;
 6         System.out.println("init()...");
 7     }
 8     @Override
 9     public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
10          System.out.println("service()..,.");
11     }
12     @Override
13     public void destroy() {
14         System.out.println("destroy()..");
15     }
16     @Override
17     public ServletConfig getServletConfig() {
18         return this.servletConfig;
19     }
20     @Override
21     public String getServletInfo() {
22         return "";
23     }
24 }                    

 

其中有三个方法是servlet的生命周期方法

  • init(ServletConfig config)

    • 这个方法会在servlet被创建之后立即被调用,只会调用一次

    • 服务器会给这个方法传递参数,即 ServletConfig的一个实例

  • service(ServletRequest request, ServletResponse response)

    • 每次处理请求都是调用这个方法,

    • 参数也是由服务器传递

  • destroy();

    • servlet在被销毁之前会调用这个方法

当然上面的这些方法都不是由我们来调用,都是服务器在调用,我们也不会创建servlet对象,只编写servlet,servlet实例也由服务器创建。



GenericServlet

  GenericServlet实现了javax.servlet.Servletjavax.servlet.ServletConfig接口,我们可以来模仿一下GenericServlet类的实现,重写这两个接口中的方法,其中init()方法中的参数是ServletConfig,我们可以在类中保存这个这个参数,然后使用服务器传递过来的ServletConfig完成ServletConfig接口中的方法,

 

 1 import java.io.IOException;
 2 import java.util.Enumeration;
 3 
 4 public abstract class MyGenericServlet implements Servlet, ServletConfig {
 5     private ServletConfig servletConfig;
 6 
 7     @Override
 8     public void init(ServletConfig servletConfig) throws ServletException {
 9         this.init();
10         this.servletConfig = servletConfig;
11     }
12 
13     public void init() {
14         // 在这里去做一些初始化操作
15     }
16 
17     @Override
18     public ServletConfig getServletConfig() {
19         return this.servletConfig;
20     }
21 
22     @Override
23     public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws
24             ServletException, IOException;
25 
26     @Override
27     public String getServletInfo() {
28         return null;
29     }
30 
31     @Override
32     public void destroy() {
33         System.out.println("destroy()");
34     }
35 
36     @Override
37     public String getServletName() {
38         return this.getServletConfig().getServletName();
39     }
40 
41     @Override
42     public ServletContext getServletContext() {
43         return this.getServletConfig().getServletContext();
44     }
45 
46     @Override
47     public String getInitParameter(String s) {
48         return this.getServletConfig().getInitParameter(s);
49     }
50 
51     @Override
52     public Enumeration<String> getInitParameterNames() {
53         return this.getServletConfig().getInitParameterNames();
54     }
55 }

 

需要注意的是我们在类中重载了一个init()方法,这个方法是无参数的,并且在有参的init方法中调用了这个方法。

为什么要添加这个方法?假设一个场景,我们继承了GenericServlet类重写了service方法,但是我们还想做一些初始化操作,于是我们重写了有参的init()方法,注意这里就出问题了,我们重写了init方法,但是在父类中的init方法中将ServletConfig保存了下来,现在一重写,我们的私有成员变量servletConfig将的得不到赋值,此时如果我们调用getServletContxt()等等方法都会出错,所以重载了一个无参的init方法就防止了此类错误的发生,我们应该重写无参的init方法,来做一些初始化操作,

 

HttpServlet

  前面的Servlet接口,抽象类GenericServlet都是与具体的应用层协议无关的类,我们在编写web应用程序时,一般都是使用与Http协议相关的这个类HttpServlet,这个类继承于GenericServlet,它重写了GenericServlet中的唯一的抽象方法service(),并且在类中重载了一个service(HttpServletRequest request , HttpServletResponse response)方法,注意这个方法的的两个参数也是与Http协议相关的, HttpServletRequest继承于ServletRequest, HttpServletResponse继承于ServletResponse

  需要注意的是重载的这个方法虽然名字与service(ServletRequest request, ServletResponse response) 方法一样,但是重载的方法不是生命周期方法,也就是说服务器在处理请求时,是不会调用重载的service方法的,只会调用service(ServletRequest request, ServletResponse response) 这个生命周期方法,我们看HttpServlet的原码可以发现:

 1 public void service(ServletRequest req,ServletResponse res)throws ServletException,IOException{
 2         HttpServletRequest request;
 3         HttpServletResponse response;
 4         try{
 5             request=(HttpServletRequest)req;
 6             response=(HttpServletResponse)res;
 7         }catch(ClassCastException var6){
 8             throw new ServletException("non-HTTP request or response");
 9         }
10         this.service(request,response);
11 }

 

  在生命周期service方法中,声明Http类型的request和response,然后进行了强转,并调用了重载的service方法,这里强转能够成功说明服务器给service方法传递的实际还是HttpServletRequest和HttpServletResponse,不然不会强转成功。我们再来看重载的service方法,

 1 protected void service(HttpServletRequest req,HttpServletResponse resp)throws ServletException,IOException{
 2         String method=req.getMethod();
 3         long lastModified;
 4         if(method.equals("GET")){
 5 
 6         }else if(method.equals("HEAD")){
 7           lastModified=this.getLastModified(req);
 8           this.maybeSetLastModified(resp,lastModified);
 9           this.doHead(req,resp);
10         }else if(method.equals("POST")){
11           this.doPost(req,resp);
12         }else if(method.equals("PUT")){
13           this.doPut(req,resp);
14         }else if(method.equals("DELETE")){
15           this.doDelete(req,resp);
16         }else if(method.equals("OPTIONS")){
17           this.doOptions(req,resp);
18         }else if(method.equals("TRACE")){
19           this.doTrace(req,resp);
20         }else{
21           String errMsg=lStrings.getString("http.method_not_implemented");
22           Object[]errArgs=new Object[]{method};
23           errMsg=MessageFormat.format(errMsg,errArgs);
24           resp.sendError(501,errMsg);
25 }

 

在重载的service方法中,首先会获取请求的方式,这与Http协议有关,所谓的请求方法指的是Http请求报文中的请求首行第一个字段所表示的方法,Http协议规定的请求方法有:

  • GET

    • 请求读取由URL所标志的信息(资源),一般没有请求体

  • POST

    • 给服务器添加信息,通常用在表单的提交方式中,这种方法会产生请求体

  • OPTION

    • 请求一些选项信息,比如支持的HTTP方法,

  • HEAD

    • 与GET方法类似,不过该请求只会返回页面的头部数据

  • PUT

    • 在指明的URL下存储一个文档

  • DELETE

    • 删除由URL所标识的资源

  • TRACE

    • 用于进行环回检测的请报文的请求方法,通常用于诊断的目的

  • CONNECT

    • 由于代理服务器,HttpServlet中没有这个请求方式对应的方法,所以如果客户端使用这个方法请求服务器将会得到一个501的状态码

上述的方法除了CONNECT,其余的方法在HttpServlet都有相应的处理方法,名字都差不多,doXXX,一系列方法,对应了相应的请求方法。

service方法在获取到请求方法之后,就调用相应的doXXX方法来完成响应,所以我们应该重写一系列的doXXX方法,而不应该重写service方法,这才是最简单的方式。

再来看一下在HttpServlet中的doXXX一系列方法的默认实现,这些方法都不是抽象方法,都是有默认实现的,

1 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
2     String protocol = req.getProtocol();
3     String msg = lStrings.getString("http.method_get_not_supported");
4     if (protocol.endsWith("1.1")) {
5         resp.sendError(405, msg);
6     } else {
7         resp.sendError(400, msg);
8     }
9 }

 

上面是doGet方法的源码,实现很简单,先获取协议名,若是http1.1版本发送状态码405,并附带一个信息”http.method_get_not_supported”,若是其他版本如http1.0发送状态码400,可见如果我们不重写doGet等一系列方法将会得到一个405或者400的错误页面

但是等等,我们如何使用浏览器访问servlet?这是一个重要的问题。

在web.xml中配置servlet

  前面说到了我们可以重写HttpServlet的一系列doXXX方法来完成对客户端请求的响应,但是忘记了一个重要的问题,在浏览器的地址栏输入什么来访问servlet?这么多servlet,如何访问特定的一个servlet我们可以在web.xml中对servlet进行配置,每个web项目的WEB-INF都有一个web.xml文件,我们在文件中添加如下配置:

  

 1 <servlet>
 2 
 3   <servlet-name>HelloServlet</servlet-name>
 4 
 5   <servlet-class>yu.servlet.HelloServlet</servlet-class>
 6 
 7   <load-on-startup>1</load-on-startup>
 8 
 9   </servlet>
10 
11  
12 
13   <servlet-mapping>
14 
15   <servlet-name>HelloServlet</servlet-name>
16 
17   <url-pattern>/HelloServlet</url-pattern>
18 
19   </servlet-mapping>

 

  分别进行解释,这段配置分为了两个部分,一个servlet标签,一个 servlet-mapping标签,一个servlet对应web.xml中的这两段配置:

  servlet标签中:

  <servlet-name>HelloServlet</servlet-name>,这段配置是表示给servlet取个名字 <servlet-class>yu.servlet.HelloServlet</servlet-class> ,指定servlet的类名(带包名)

  <load-on-startup>1</load-on-startup> ,在服务器启动时就创建这个servlet的实例,如果没有这段配置,那么服务器会在第一次收到请求这个servlet的请求时,才会创建这个类的实例

  servlet-mapping标签中:

  <servlet-name>HelloServlet</servlet-name>,必须与servlet标签中配置的name一致,

  <url-pattern>/HelloServlet</url-pattern>,这个就是我们在浏览器中可以输入的标识,可以为一个servlet配置多个 url-pattern

  这段配置将一个servlet类与一个 url-pattern关联起来,客户端在发出请求时,服务器将解析请求报文中请求首行的URL,若发现与某个servlet的 url-pattern一致,就调用该servlet的service方法完成响应,服务器可以解析xml文件找出匹配的servlet的配置,在通过配置中的 servlet-class反射创建对象,并调用其service方法。

  另外一个配置的servlet的方式是使用注解,这种方式比较简单,但是有一定的缺陷,比如无法控制过滤器的执行顺序?可以查看注解类,查看可以配置的信息,

  @WebServlet(name = "AServlet",urlPatterns = {"/AServlet"})




以上是关于Servlet的主要内容,如果未能解决你的问题,请参考以下文章

servlet和filter的区别

Java基础——JSP

java---servlet与filter的联系与区别

servlet,filter,listener,intercepter区别

Tomcat根据JSP生成Servlet机制解析

servlet,过滤器,监听器,拦截器的区别