一、Servlet概述
一个类型的Servlet只有一个实例对象,就会现时出一个Servlet同时处理多个请求。多个线程公用一个Servlet对象,所以Servlet是线程不安全的。但Servlet的工作效率很高
所以不应该在Servlet中创建成员变量,因为可能会存在一个线程对这个成员变量进行写操作,另一个线程对这个成员变量进行读操作。
特性:
单例(第一次访问时,被服务器创建),一个类只有一个对象;当然可能存在多个Servlet类,线程不安全的,效率是高的!
Servlet实现类由用户实现,但对象由服务器来创建,并且由服务器来调用相应的方法。
Servlet介绍
Servlet是JavaWeb的三大组件之一,它属于动态资源。Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要:
接收请求数据;处理请求;完成响应。
例如客户端发出登录请求,这些请求都由Servlet来完成处理。Servlet需要用户自己来编写,每个Servlet必须实现javax.servlet.Servlet接口。
Servlet实现方式
实现Servlet有三种方式:
- 实现javax.servlet.Servlet接口;
- 继承javax.servlet.GenericServlet类;
- 继承javax.servlet.http.HttpServlet类;
通常我们继承HttpServlet类来实现Servlet
package javax.servlet;
import java.io.IOException;
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}
1.实现Servlet接口
public class DemoServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {}
public ServletConfig getServletConfig() {return null;}
public void destroy() {}
public String getServletInfo() {return null;}
//用来处理请求的方法
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
System.out.println("hello servlet!");
}
}
配置web.xml
将访问路径与Servlet绑定到一起。即将访问路径:/demoworld与com.masterlu.servlet.DemoServlet绑定
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.masterlu.servlet.DemoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>demo</servlet-name>
<!-- 指定Servlet的访问路径(必须以/开头),
可以配置多个,多个路径指向同一个Servlet。
也可以使用通配符配置路径(通配符不能出现在中间,只能作为前缀或后缀)。
-->
<url-pattern>/demoworld</url-pattern>
<url-pattern>/demoworld2</url-pattern>
<url-pattern>/servlet/*<url-patter>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
2.继承GenericServlet
GenericServlet是Servlet接口的实现类,也可以通过继承GenericServlet来编写自己的Servlet。
package javax.servlet;
import java.io.IOException;
import java.util.Enumeration;
import java.util.ResourceBundle;
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings =
ResourceBundle.getBundle(LSTRING_FILE);
private transient ServletConfig config;
public GenericServlet() { }
@Override
public void destroy() {
}
@Override
public String getInitParameter(String name) {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getInitParameterNames();
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public ServletContext getServletContext() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletContext();
}
@Override
public String getServletInfo() {
return "";
}
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
@Override
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
@Override
public String getServletName() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletName();
}
}
GenericServlet的init()方法
在GenericServlet中,定义ServletConfig config实例变量,并在init(ServletConfig)方法中把参数ServletConfig赋给了实例变量。然后该类的很多方法使用实例变量config。则子类需要完成一些初始化操作,只能覆盖GenericServlet提供的init()方法。它是没有参数的init()方法,它会在init(ServletConfig)方法中被调用。
如果子类覆盖GenericServlet的init(StringConfig)方法,那么this.config=config这一条语句就会被覆盖了,则config的值为null,那么所有依赖config的方法都不能使用。
实现了ServletConfig接口
GenericServlet还实现了ServletConfig接口,所以可以直接调用getInitParameter()、getServletContext()等ServletConfig的方法。
3.继承HttpServlet(常用方式)
HttpServlet类是GenericServlet的子类,提供了对HTTP请求的特殊支持,通常都会通过继承HttpServlet来完成自定义的Servlet。(SpringMVC的Servlet也是继承HttpServlet)
package javax.servlet.http;
import javax.servlet.*;
public abstract class HttpServlet extends GenericServlet
{
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE =
"javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings =
ResourceBundle.getBundle(LSTRING_FILE);
public HttpServlet() { }
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 long getLastModified(HttpServletRequest req) {
return -1;
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
NoBodyResponse response = new NoBodyResponse(resp);
doGet(req, response);
response.setContentLength();
}
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 doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_put_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 doDelete(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_delete_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
private Method[] getAllDeclaredMethods(Class<? extends HttpServlet> c) {
Class<?> clazz = c;
Method[] allMethods = null;
while (!clazz.equals(HttpServlet.class)) {
Method[] thisMethods = clazz.getDeclaredMethods();
if (allMethods != null && allMethods.length > 0) {
Method[] subClassMethods = allMethods;
allMethods =
new Method[thisMethods.length + subClassMethods.length];
System.arraycopy(thisMethods, 0, allMethods, 0,
thisMethods.length);
System.arraycopy(subClassMethods, 0, allMethods, thisMethods.length,
subClassMethods.length);
} else {
allMethods = thisMethods;
}
clazz = clazz.getSuperclass();
}
return ((allMethods != null) ? allMethods : new Method[0]);
}
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
Method[] methods = getAllDeclaredMethods(this.getClass());
boolean ALLOW_GET = false;
boolean ALLOW_HEAD = false;
boolean ALLOW_POST = false;
boolean ALLOW_PUT = false;
boolean ALLOW_DELETE = false;
boolean ALLOW_TRACE = true;
boolean ALLOW_OPTIONS = true;
for (int i=0; i<methods.length; i++) {
String methodName = methods[i].getName();
if (methodName.equals("doGet")) {
ALLOW_GET = true;
ALLOW_HEAD = true;
} else if (methodName.equals("doPost")) {
ALLOW_POST = true;
} else if (methodName.equals("doPut")) {
ALLOW_PUT = true;
} else if (methodName.equals("doDelete")) {
ALLOW_DELETE = true;
}
}
// we know "allow" is not null as ALLOW_OPTIONS = true
// when this method is invoked
StringBuilder allow = new StringBuilder();
if (ALLOW_GET) {
allow.append(METHOD_GET);
}
if (ALLOW_HEAD) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_HEAD);
}
if (ALLOW_POST) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_POST);
}
if (ALLOW_PUT) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_PUT);
}
if (ALLOW_DELETE) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_DELETE);
}
if (ALLOW_TRACE) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_TRACE);
}
if (ALLOW_OPTIONS) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_OPTIONS);
}
resp.setHeader("Allow", allow.toString());
}
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
int responseLength;
String CRLF = "\r\n";
StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI())
.append(" ").append(req.getProtocol());
Enumeration<String> reqHeaderEnum = req.getHeaderNames();
while( reqHeaderEnum.hasMoreElements() ) {
String headerName = reqHeaderEnum.nextElement();
buffer.append(CRLF).append(headerName).append(": ")
.append(req.getHeader(headerName));
}
buffer.append(CRLF);
responseLength = buffer.length();
resp.setContentType("message/http");
resp.setContentLength(responseLength);
ServletOutputStream out = resp.getOutputStream();
out.print(buffer.toString());
}
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 = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// 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);
}
}
/**
* Dispatches client requests to the protected
* <code>service</code> method. There's no need to
* override this method.
*
* @param req the {@link HttpServletRequest} object that
* contains the request the client made of
* the servlet
*
* @param res the {@link HttpServletResponse} object that
* contains the response the servlet returns
* to the client
*
* @throws IOException if an input or output error occurs
* while the servlet is handling the
* HTTP request
*
* @throws ServletException if the HTTP request cannot
* be handled or if either parameter is not
* an instance of its respective {@link HttpServletRequest}
* or {@link HttpServletResponse} counterparts.
*
* @see javax.servlet.Servlet#service
*/
@Override
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);
}
}
HttpServlet#service(HttpServletRequest,HttpServletResponse)方法,判断当前请求是GET还是POST,如果是GET请求,那么会去调用本类的doGet()方法,如果是POST请求会去调用doPost()方法。用户在子类中去覆盖doGet()或doPost()方法即可
二、Servlet生命周期
生命周期相关方法
void init(ServletConfig);
void service(ServletRequest,ServletResponse);
void destroy();
1.Servlet初始化方法
默认情况下,Servlet实例,在Servlet第一次被访问时,由服务器创建(或者在web.xml文件中配置,使得在服务器启动时创建Servlet实例)。
一个Servlet类型,服务器只创建一个实例对象,例如在首次访问http://localhost:8080/appName/demoworld时,服务器通过“/helloworld”找到绑定的Servlet名称为com.masterlu.servlet.DemoServlet,然后服务器查看这个类型的Servlet是否已经创建过,如果没有创建过,那么服务器会通过反射来创建DemoServlet的实例。再次访问http://localhost:8080/helloservlet/helloworld时,服务器就不会再次创建DemoServlet实例了,而是直接使用上次创建的实例。
在Servlet被创建后,服务器会立刻调用Servlet的void init(ServletConfig)方法。init方法只会被调用一次。
利用这个特性,可以把Servlet的初始化工作放到init方法中。
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.masterlu.servlet.DemoServlet</servlet-class>
<!-- 服务器在启动时就创建该Servlet,其中<load-on-startup>元素的值必须是大于等于0的整数,
数字越小优先级越高-->
<load-on-startup>0</load-on-startup>
</servlet>
2.Servlet服务方法
用户每次请求服务器时,服务器都会调用Servlet#service()方法来处理请求。因为service()方法可以被多次调用,所以我们才需要把处理请求的代码写到service()方法中。
3.Servlet销毁方法
在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法,我们可以把Servlet的销毁逻辑放到destroy()方法中,例如对某些资源的释放等代码放到destroy()方法中。
三、Servlet接口用到的对象
ServletConfig
init()方法的参数,表示Servlet配置对象,它对应Servlet的配置信息,即:对应web.xml的
ServletRequest
由服务器创建的请求对象,它封装了所有与请求相关的数据;
ServletResponse
由服务器创建的响应对象,在service()方法中完成对客户端的响应,需要使用这个对象;
ServletRequest和ServletResponse的实例由服务器创建,然后传递给Servlet#service() 。一个是请求对象,一个是响应对象,可以从ServletRequest对象中获取请求数据,可以使用ServletResponse对象完成响应。
如果在service() 方法中使用HTTP相关的功能,可以把ServletRequest和ServletResponse强转成HttpServletRequest和HttpServletResponse。
HttpServletRequest方法
String getParameter(String paramName)
获取指定请求参数的值;
String getMethod()
获取请求方法,例如GET或POST;
String getHeader(String name)
获取指定请求头的值;
void setCharacterEncoding(String encoding)
设置请求体的编码!因为GET请求没有请求体,所以这个方法仅对POST请求有效。当调用request.setCharacterEncoding(“utf-8”)之后,再调用getParameter()方法获取参数值时,则参数值都已经转换成UTF-8编码。所以这个方法必须在调用getParameter()方法之前调用!
HttpServletResponse方法
PrintWriter getWriter()
获取字符响应流,使用该流向客户端输出响应信息。例如response.getWriter().print(“
Hello JavaWeb!
”);ServletOutputStream getOutputStream()
获取字节响应流,使用该流向客户端响应字节数据,例如要向客户端响应图片;
void setCharacterEncoding(String encoding)
用来设置字符响应流的编码,例如在调用setCharacterEncoding(“utf-8”);之后,再response.getWriter()获取字符响应流对象,响应流的编码为utf-8,使用response.getWriter()输出的中文都会转换成utf-8编码后发送给客户端;
void setHeader(String name, String value)
向客户端添加响应头信息,例如setHeader(“Refresh”, “3;url=http://www.baidu.com”),表示3秒后自动刷新到http://www.baidu.com;
void setContentType(String contentType)
是setHeader(“content-type”, “xxx”)的简便方法,即用来添加名为content-type响应头的方法。content-type响应头用来设置响应数据的MIME类型,
例如要向客户端响应jpg的图片,那么可以setContentType(“image/jepg”),
如果响应数据为文本类型,且同时设置编码,例如setContentType(“text/html;chartset=utf-8”)表示响应数据类型为文本类型中的html类型,并且该方法会调用setCharacterEncoding(“utf-8”)方法;
void sendError(int code, String errorMsg)
向客户端发送状态码,以及错误消息。例如给客户端发送404:response(404, “您要查找的资源不存在!”)。
ServletConfig
对应web.xml文件中的
String getServletName()
获取Servlet在web.xml文件中的配置名称,即
ServletContext getServletContext()
用来获取ServletContext对象。
String getInitParameter(String name)
用来获取在web.xml中配置的初始化参数,通过参数名来获取参数值;
Enumeration getInitParameterNames()
用来获取在web.xml中配置的所有初始化参数名称;
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.masterlu.servlet.DemoServlet</servlet-class>
</servlet>
<init-param>
<param-name>paramName1</param-name>
<param-value>paramValue1</param-value>
</init-param>
<init-param>
<param-name>paramName2</param-name>
<param-value>paramValue2</param-value>
</init-param>
</servlet>
四、web.xml文件继承
每一个javaWeb应用中都需要有web.xml文件,应用中的web.xml都有一个共同的父文件(tomca的conf/web.xml)。
tomca中conf/web.xml相当于写到项目中web.xml中
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<!-- 它的优先级最低的Servlet
1.当访问路径不存在时,会执行该Servlet,
2.在访问index.html时也是在执行这个Servlet。
-->
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<!-- 匹配所有URL,即用户访问的URL路径没有匹配的页面时,那么执行的就是名为default的Servlet,
即org.apache.catalina.servlets.DefaultServlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 任何URL后缀为jsp的访问,都会执行名为jsp的Servlet,
即org.apache.jasper.servlet.JspServlet
-->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
<!-- session的默认超时时间为30分钟 -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<!-- 省略了大多数tomcat配置的MIME类型的定义,这里只给出两种MIME类型的定义 -->
<mime-mapping>
<extension>bmp</extension>
<mime-type>image/bmp</mime-type>
</mime-mapping>
<mime-mapping>
<extension>htm</extension>
<mime-type>text/html</mime-type>
</mime-mapping>
<!-- 设置默认主页为index.html、index.html、index.jsp -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
五、ServletContext
一个项目只有一个ServletContext对象,在整个Web应用的动态资源之间实现共享数据。可以在所有Servlet中来获取这个唯一的对象,使用它可以给多个Servlet传递数据!对象在Tomcat启动时就创建,在Tomcat关闭时销毁!
获取ServletContext
ServletConfig#getServletContext();
GenericServlet#getServletContext();
HttpSession#getServletContext()
ServletContextEvent#getServletContext()
域对象的功能
JavaWeb四大域对象
PageContext、ServletRequest、HttpSession、ServletContext;
所有域对象都有存取数据的功能,因为域对象内部有一个Map,用来存储数据。
ServletContext对象用来操作数据的方法:
void setAttribute(String name, Object value)
存储一个域属性。如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值;
Object getAttribute(String name)
获取ServletContext中的数据
void removeAttribute(String name)
用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
Enumeration getAttributeNames()
获取所有域属性的名称;
获取应用初始化参数
配置公共的初始化参数,为所有Servlet而用!需要使用ServletContext才能使用
<web-app ...>
<context-param>
<param-name>paramName1</param-name>
<param-value>paramValue1</param-value>
</context-param>
<context-param>
<param-name>paramName2</param-name>
<param-value>paramValue2</param-value>
</context-param>
</web-app>
ServletContext context = this.getServletContext();
String value1 = context.getInitParameter("paramName1");
String value2 = context.getInitParameter("paramName2");
System.out.println(value1 + ", " + value2);
Enumeration names = context.getInitParameterNames();
while(names.hasMoreElements()) {
System.out.println(names.nextElement());
}