1、什么是servlet:
servlet 是运行在 Web 服务器中的小型 Java 程序(即:服务器端的小应用程序)。servlet 通常通过 HTTP(超文本传输协议)接收和响应来自 Web 客户端的请求。servlet实例是由web服务器(tomcat)创建的,它是单例多线程的。单例是指servlet的实例只有一个,多线程是指每次客户端的请求,web服务器都会从线程池中分配一个工作线程去执行servlet的service()方法,编写servlet程序一共有三种方式。
2、创建servlet程序的三种方式:
- 方式1:实现javax.servlet.Servlet接口
1、在eclipse中创建一个web项目,然后新建一个类ServletTest实现此接口。
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 ServletTest implements Servlet { @Override public void init(ServletConfig config) throws ServletException { System.out.println("初始化:init"); } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("hello world!"); } @Override public String getServletInfo() { return null; } @Override public void destroy() { System.out.println("销毁:destory"); } }
2、在web.xml中配置servlet的映射信息,只有配置了映射信息,tomcat才能找到客户端的请求对应的是哪个servlet。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>test</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <!-- 配置servlet --> <servlet> <servlet-name>ServletTest</servlet-name> <servlet-class>com.neu.servlet.ServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletTest</servlet-name> <url-pattern>/s1</url-pattern> </servlet-mapping> </web-app>
<servlet-name>是指servlet的名称,<servlet>标签和<servlet-mapping>标签中的<servlet-name>必须一致。<servlet-class>是当前servlet的全限定类名,<url-pattern>是映射servlet的url,例如我们要想访问这个ServletTest这个servlet,浏览器输入http://localhost:8080/test/s1即可,/一定不能省略。
3、启动web项目,客户端访问:http://localhost:8080/test/s1
控制台输出:
再次访问此地址,控制台输出:
发现初始化init()方法只执行了一次,而且是浏览器第一次访问此servlet的时候才执行的。而以后每次客户端再次请求,service方法都会对应执行一次,但是init()方法不会再次执行。细心的读者会发现,销毁方法destory()并没有执行,那么destory什么时候执行呢?只有当应用卸载的时候才会执行。下面讲述servlet生命周期的时候会细说,先把剩下的两种创建servlet程序的方式演示完。
- 方式2:继承javax.servlet.GenericServlet类
package com.neu.servlet; import java.io.IOException; import javax.servlet.GenericServlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class GenericServletTest extends GenericServlet { @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("hello,world!"); } }
读者可能发现了,继承GenericServlet类后的代码很简洁,只需要重写service()方法。看看GenericServlet类的源码就明白了
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { //省略其他 @Override public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; }
它实现了servlet和ServletConfig接口,并且除了service()方法外,其他方法它自己都实现了(毕竟其他方法不是最重要的),这是典型的适配器模式。然后我们在web.xml中配置映射信息
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>test</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <!-- 配置servlet --> <servlet> <servlet-name>ServletTest</servlet-name> <servlet-class>com.neu.servlet.ServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletTest</servlet-name> <url-pattern>/s1</url-pattern> </servlet-mapping> <servlet> <servlet-name>GenericServlet</servlet-name> <servlet-class>com.neu.servlet.GenericServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>GenericServlet</servlet-name> <url-pattern>/s2</url-pattern> </servlet-mapping> </web-app>
浏览器访问:http://localhost:8080/test/s2 控制台输出如下:
- 方式3:继承javax.servlet.http.HttpServlet类(最常用的方式)
package com.neu.servlet; import javax.servlet.http.HttpServlet; public class HttpServletTest extends HttpServlet { }
继承这个类似乎不会实现什么抽象方法,那么真的什么方法都不用重写吗,当然不是。先来看看HttpServlet的源码
public abstract class HttpServlet extends GenericServlet { //省略其他方法 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 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 service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less 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)) { doPost(req, resp); } else if (method.equals(METHOD_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 { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // 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); } } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } service(request, response); }
HttpServlet继承了GenericServlet,并且重写了service()方法,并且在service()方法中又调用了一个自己定义的protected类型的service方法。在自定义的service()方法中又根据请求方法类型get还是post或者其他类型,再去调用对应的doGet()或者doPost()方法。所以我们只需要重写doGet()和doPost方法即可(典型的模版方法模式)。如果不重写,访问会报错,读者自己可以试试。
package com.neu.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class HttpServletTest extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("hello,world!"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
配置web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>test</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <!-- 配置servlet --> <servlet> <servlet-name>ServletTest</servlet-name> <servlet-class>com.neu.servlet.ServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletTest</servlet-name> <url-pattern>/s1</url-pattern> </servlet-mapping> <servlet> <servlet-name>GenericServlet</servlet-name> <servlet-class>com.neu.servlet.GenericServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>GenericServlet</servlet-name> <url-pattern>/s2</url-pattern> </servlet-mapping> <servlet> <servlet-name>HttpServlet</servlet-name> <servlet-class>com.neu.servlet.HttpServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>HttpServlet</servlet-name> <url-pattern>/s3</url-pattern> </servlet-mapping> </web-app>
浏览器访问:http://localhost:8080/test/s3 控制台输出如下:
创建servlet的三种方式就说完了,其实还需要再读读源码多理解理解,现在来总体看下这几个类的关系。
3、servlet的生命周期:
执行流程:当一个客户端请求过来的时候,tomcat服务器首先解析这个url,首先找到对应的应用app,然后根据/xxx去web.xml中根据配置信息去找对应的servlet,找到对应的servlet后去创建servlet实例,然后执行init()方法,service()方法,然后响应客户端。当应用卸载的时候,会销毁这个servlet,执行destory()方法。
声明周期:实例化-----初始化-----服务-----销毁,因为servlet是单例多线程的,所以实例化,初始化和销毁只执行一次。服务方法会多次执行,每次请求过来的时候,如果servlet已经被实例化过,那么web服务器直接分配一个工作线程去处理这个请求(调用service方法执行),所以实例化和初始化只会在第一次请求过来的时候执行。如果我们在servlet类中定义成员变量,并在service()方法中使用的话,就会出现线程安全问题,因为servlet实例只有一份,多个线程共享这个实例的成员变量,如果都执行写操作,必然会导致错误的结果。所以我们不要在servlet中定义成员变量,而是在方法中定义局部变量。因为每个方法都是在虚拟机栈(或者本地方法栈)中执行,是线程独立的,所以不存在线程安全问题。下面直接看代码吧,这样对servlet的生命周期和单例理解的更加深刻些。我们还是以最原始的实现servlet接口的方式:
package com.neu.servlet; 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 ServletLifeTest implements Servlet{ public ServletLifeTest() { System.out.println("***实例化servlet***"); } @Override public void init(ServletConfig config) throws ServletException { System.out.println("***初始化servlet***"); } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println(this + "***处理业务逻辑***"); } @Override public void destroy() { System.out.println("***销毁servlet***"); } @Override public String getServletInfo() {return null;} @Override public ServletConfig getServletConfig() {return null;} }
这里省略web.xml的配置。浏览器连续多次访问:http://localhost:8080/test/s4 ,控制台输出如下:
注意到没有,输出的servlet实例是同一个对象,而实例化和初始化只执行一次。接下来我们来卸载应用,让destory()执行。首先打开tomcat的根目录/conf/tomcat-users.xml,进行如下配置:
<?xml version="1.0" encoding="UTF-8"?> <!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --><tomcat-users> <role rolename="manager-gui"/> <role rolename="manager-script"/> <user password="tomcat" roles="manager-gui,manager-script" username="tomcat"/> </tomcat-users>
然后访问http://localhost:8080 ,进入tomcat的主页,点击Manage App。
输入刚才配置的用户名和密码,进入下面的界面:
找到我们的应用test,点击右侧的Undeploy,这个时候再看看控制台的输出,destory()执行了。