Servlet底层实现和生命周期
servlet是一个接口,GenericServlet实现了servlet中的四个方法,service方法除外。目的是让子类实现。其中,有获取参数,Servlet名字,配置,上下文等方法。
当浏览器发来请求报文的时候,servlet先调用init方法对servlet实例初始化,然后可以在service方法中处理请求和响应。
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.
*/
package javax.servlet;
import java.io.IOException;
import java.util.Enumeration;
import java.util.ResourceBundle;
/**
*
* Defines a generic, protocol-independent
* servlet. To write an HTTP servlet for use on the
* Web, extend {@link javax.servlet.http.HttpServlet} instead.
*
* <p><code>GenericServlet</code> implements the <code>Servlet</code>
* and <code>ServletConfig</code> interfaces. <code>GenericServlet</code>
* may be directly extended by a servlet, although it's more common to extend
* a protocol-specific subclass such as <code>HttpServlet</code>.
*
* <p><code>GenericServlet</code> makes writing servlets
* easier. It provides simple versions of the lifecycle methods
* <code>init</code> and <code>destroy</code> and of the methods
* in the <code>ServletConfig</code> interface. <code>GenericServlet</code>
* also implements the <code>log</code> method, declared in the
* <code>ServletContext</code> interface.
*
* <p>To write a generic servlet, you need only
* override the abstract <code>service</code> method.
*
*
* @author Various
*/
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;
/**
*
* Does nothing. All of the servlet initialization
* is done by one of the <code>init</code> methods.
*
*/
public GenericServlet() { }
/**
* Called by the servlet container to indicate to a servlet that the
* servlet is being taken out of service. See {@link Servlet#destroy}.
*
*
*/
public void destroy() {
}
/**
* Returns a <code>String</code> containing the value of the named
* initialization parameter, or <code>null</code> if the parameter does
* not exist. See {@link ServletConfig#getInitParameter}.
*
* <p>This method is supplied for convenience. It gets the
* value of the named parameter from the servlet's
* <code>ServletConfig</code> object.
*
* @param name a <code>String</code> specifying the name
* of the initialization parameter
*
* @return String a <code>String</code> containing the value
* of the initialization parameter
*
*/
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);
}
/**
* Returns the names of the servlet's initialization parameters
* as an <code>Enumeration</code> of <code>String</code> objects,
* or an empty <code>Enumeration</code> if the servlet has no
* initialization parameters. See {@link
* ServletConfig#getInitParameterNames}.
*
* <p>This method is supplied for convenience. It gets the
* parameter names from the servlet's <code>ServletConfig</code> object.
*
*
* @return Enumeration an enumeration of <code>String</code>
* objects containing the names of
* the servlet's initialization parameters
*/
public Enumeration<String> getInitParameterNames() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getInitParameterNames();
}
/**
* Returns this servlet's {@link ServletConfig} object.
*
* @return ServletConfig the <code>ServletConfig</code> object
* that initialized this servlet
*/
public ServletConfig getServletConfig() {
return config;
}
/**
* Returns a reference to the {@link ServletContext} in which this servlet
* is running. See {@link ServletConfig#getServletContext}.
*
* <p>This method is supplied for convenience. It gets the
* context from the servlet's <code>ServletConfig</code> object.
*
*
* @return ServletContext the <code>ServletContext</code> object
* passed to this servlet by the <code>init</code>
* method
*/
public ServletContext getServletContext() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletContext();
}
/**
* Returns information about the servlet, such as
* author, version, and copyright.
* By default, this method returns an empty string. Override this method
* to have it return a meaningful value. See {@link
* Servlet#getServletInfo}.
*
*
* @return String information about this servlet, by default an
* empty string
*/
public String getServletInfo() {
return "";
}
/**
* Called by the servlet container to indicate to a servlet that the
* servlet is being placed into service. See {@link Servlet#init}.
*
* <p>This implementation stores the {@link ServletConfig}
* object it receives from the servlet container for later use.
* When overriding this form of the method, call
* <code>super.init(config)</code>.
*
* @param config the <code>ServletConfig</code> object
* that contains configutation
* information for this servlet
*
* @exception ServletException if an exception occurs that
* interrupts the servlet's normal
* operation
*
* @see UnavailableException
*/
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
/**
* A convenience method which can be overridden so that there's no need
* to call <code>super.init(config)</code>.
*
* <p>Instead of overriding {@link #init(ServletConfig)}, simply override
* this method and it will be called by
* <code>GenericServlet.init(ServletConfig config)</code>.
* The <code>ServletConfig</code> object can still be retrieved via {@link
* #getServletConfig}.
*
* @exception ServletException if an exception occurs that
* interrupts the servlet's
* normal operation
*/
public void init() throws ServletException {
}
/**
* Writes the specified message to a servlet log file, prepended by the
* servlet's name. See {@link ServletContext#log(String)}.
*
* @param msg a <code>String</code> specifying
* the message to be written to the log file
*/
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
/**
* Writes an explanatory message and a stack trace
* for a given <code>Throwable</code> exception
* to the servlet log file, prepended by the servlet's name.
* See {@link ServletContext#log(String, Throwable)}.
*
*
* @param message a <code>String</code> that describes
* the error or exception
*
* @param t the <code>java.lang.Throwable</code> error
* or exception
*/
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
/**
* Called by the servlet container to allow the servlet to respond to
* a request. See {@link Servlet#service}.
*
* <p>This method is declared abstract so subclasses, such as
* <code>HttpServlet</code>, must override it.
*
* @param req the <code>ServletRequest</code> object
* that contains the client's request
*
* @param res the <code>ServletResponse</code> object
* that will contain the servlet's response
*
* @exception ServletException if an exception occurs that
* interferes with the servlet's
* normal operation occurred
*
* @exception IOException if an input or output
* exception occurs
*/
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
/**
* Returns the name of this servlet instance.
* See {@link ServletConfig#getServletName}.
*
* @return the name of this servlet instance
*/
public String getServletName() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletName();
}
}
HttpServlet继承了GenericServlet,增加了doGet,doPost,doOptions等等do方法。在其service方法中对请求类型进行了细分,由各种do方法分别处理不同的请求类型。
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);
}
}
生命周期
创建
servlet实例默认在这个地址第一次被访问的时候创建。
也可以在服务器启动的时候创建,在xml文件中设置其在服务器启动之后创建。
<load-on-startup>在servlet标签中。
<load-on-startup>1</load-on-startup>标签中的数字越小越先被创建。
初始化
实例创建之后默认先调用init()方法,所以可以在init方法中进行初始化操作。
String username = getServletConfig().getInitParameter("username");
String password = getServletConfig().getInitParameter("password");
//
执行
由于servlet在服务器启动的时候就创建,而且是单例,每个@Webservlet对应一个实例。因此这也是线程不安全的。
销毁
在服务器正常关闭时会调用destroy方法销毁所有servlet实例。
获取信息
获取参数的value或values
String name = req.getParameter("name");
String[] names = req.getParameterValues("name");
获取参数的name
Enumeration<String> parameterNames = req.getParameterNames();
while (parameterNames.hasMoreElements()){
String s = parameterNames.nextElement();
System.out.println(s);
Enumeration是个接口,类似迭代器
乱码问题
避免乱码的方法:
doPost中
req.setCharactorEncoding("utf-8");
resp.setCharactorEncoding("utf-8");
+
resp.setContentType("text/plain; charset = utf-8"); 或者
resp.setContentType("text/html; charset = utf-8"); 或者
resp.setContentType("application; charset = utf-8");
内部跳转
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/路径");
requestDispatcher.forward(req,resp);
路径可以是html页面,也能是另一个servlet。
重定向
resp.sendRedirect("/路径");
响应对象调用此方法可以重定向。过程是把新的路径(可以是html也可以是servlet)发送给浏览器,浏览器重新访问新地址。
服务器内部跳转:
使用request完成
跳转的本质是在服务器内部把请求和响应进行转发,转发给下一个指定的资源
如果request中有数据,在跳转到的下一个资源中是可以拿到这个数据,因为只有一个request请求
对象
由于服务器跳转是把请求和响应在服务器内部中进行转发,所以浏览器的地址栏中的地址是不受影
响的,始终还是第一次发出请求的地址,其实浏览器根本就不知道服务器内部的请求转发情况
在设置跳转的资源地址的时候,路径的前面一般要加上/,例如 String path = "/hello.html";
客户端重定向:
使用response完成
重定向的本质是把新的资源路径返回给浏览器,让浏览器向这个新地址发送一个新请求
此处是利用了响应的状态码302和响应头信息中的Location字段来完成
如果request中有数据,重定向后在新的资源中是拿不到这个数据的,因为重定向会发出新的请求,
但是数据在上一个老的请求中
由于重定向让浏览器发出新的请求,所以浏览器地址栏中的地址会变成新请求的地址
在设置重定向的资源地址的时候,路径的前面一般不加/
例如, String path = "hello.html";
但是如果一定要加/的话,那么还需要额外加上一个项目名
例如, String path = "/servlet/hello.html";
同时也可以动态获取项目: req.getContextPath()
例如, String path = req.getContextPath()+"/hello.html"
上下文
//通过request对象获取ServletContext对象
ServletContext servletContext = getServletContext();
String contextPath = servletContext.getContextPath();
System.out.println("contextPath = "+contextPath);
String realPath = servletContext.getRealPath("/Test");
System.out.println("realPath = "+realPath);
req.getServletContext().getContextPath()获取上下文路径。
req.getServletContext().getRealtPath()获取真实路径。
路径问题
改变路径的方式:
1.在html的<a href=""/>中直接修改
这是相对路径。同级目录直接 href = “文件名“,退回上级目录是 ../ 。
2.在head中添加base标签
<base href = "..."/>
此时再在此页面跳转时,默认路径就是base中的路径。
3.项目部署后,在server.xml文件中,可以更改Context标签中的path标签。
Servlet中三个范围对象
request: 生存于一次请求中
session: 默认伴生于打开浏览器的第一次请求,默认生存时间三十分钟,可以设置时长。
application: 生存于整个服务器开启时间。
获取方式:
request对象默认在第一次访问服务器时创建。也就是service方法参数中的HttpRequest
HttpSession session = req.getSession();
ServletContext application = req.getServletContext();
所有三个对象都能用 .setAtrribute(key,value)设置属性值。
getAttribute()获取属性,removeAttribute()删除属性。
Cookie
浏览器第一次向服务器发送请求时,会携带cookie信息于请求头中。
服务器接收到访问请求,session对象被创建,session对象的id会保存在cookie中,在response中发送回浏览器。这样,下次浏览器进行访问的时候,由于携带了cookie信息,知道对应的session的id,就能和唯一的session匹配上。
new Cookie(String , String)指定key和Value,
resp.addCookie(cookie名);可以将cookie添加到响应中。