Servlet
Posted yusiming
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Servlet相关的知识,希望对你有一定的参考价值。
Servlet
Servlet是一个运行在Web服务器中的一个Java小程序,它能够接受和处理客户端的请求,并完成对客户端的响应。Servlet是Web应用程序中的核心类,它可以直接处理请求,也可以将请求委托给应用程序中的别的部分进行处理(请求转发、包含),所有的web服务器都内建了一个或者多个servlet,用来处理JSP、html页面、图片等等,所以说所有的请求都是要基于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.Servlet和javax.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的主要内容,如果未能解决你的问题,请参考以下文章