servlet究竟是什么, 当然首先是一个对象, 来自于javax.servlet (与HTTP相关的在 javax.servlet.http 中), 不过servlet的实现类并不是由我们编写的, 而是由容器的提供商进行编写.
在IDEA里如果创建web.xml并且使用tomcat, 在左侧的外部lib中可以看到Tomcat 9 提供的包, 其中有jsp-api.jar 以及 servlet-api.jar, 其中就有javax.servlet包.
简单的说, Java的Web规范已经规定好了Servlet的所有API, 然后容器厂商来实现容器的时候, 同时实现Servlet类, 使用这个容器的应用程序员, 根据其提供的Servlet来编写具体应用, 这样写出来的类才可被容器正常使用.
Servlet的本质, 就是一个向用户提供服务, 或者说实际处理请求和提供响应内容的单元. 自然也就是Java Web的核心, 这一次就来仔细看看Servlet.
- Servlet的生命周期
- HttpServlet对象
- HttpServletRequest
- HttpServletResponse
- 请求分派与重定向
Servlet的生命周期
从之前已经可以知道, 我们启动一个Web服务, 实际上启动的是容器, 容器再去使用其中的Servlet. 容器如何使用呢, 因为是Java 环境, 本质上是创建对象, 使用对象, 销毁对象.
Servlet也是如此, 有如下生命周期:
- Web容器加载类文件
- 实例化Servlet
- 调用init()函数
- 调用service()方法
- 调用destroy()方法
- 销毁(容器不再引用这个servlet)
对于Tomcat来说, 每个请求都会在一个新的线程内执行上述实例化到销毁的全过程, 注意一定是对应请求, 而不是用户, 比如用户连续刷新两次, 是两个请求, 会分别处理, 而不是一个请求.
对于我们来说, 一般会覆盖service()方法中调用的具体处理某种请求的方法, 可能在init()和destroy中做一些必要的工作, 一般不覆盖service()方法.
Servlet接口
我们现在使用的是HTTPServlet, 顾名思义, 就是提供HTTP服务的Servlet. 通过IDEA的diagram类图可以看到这个类的体系如下:
- HttpServlet 继承 GenericServlet, 这两个都是抽象类
- GenericServlet 实现了 Servlet, ServletConfig, Serializable 三个接口
- Servlet接口如下:
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
- ServletConfig接口如下:
public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
}
通过上边的体系可以看出, Servlet接口提供了关键的三个生命周期函数, 以及获取ServletConfig对象(每个Servlet都有一个对应的ServletConfig对象,记录了web.xml中针对该servlet的配置信息)的方法, 还有一个获取getServletInfo()方法, 反正一会都打算来尝试一遍.
GenericServlet是Servlet接口的扩展, 是一个抽象类, 顾名思义: 通用Servlet, 是一个让Servlet之所以成为Servlet的抽象, 提供了很多方法, 其中的构造器可以看到是一个无参构造器. 然后有几个方法是在ServletConfig的方法基础上套壳, 用来获取配置信息和ServletContext(Web容器上下文). 不过关键的生命周期方法都是留空或者未实现的.
HttpServlet是通用Servlet在HTTP处理方面的扩展, 也是一个抽象类, 针对HTTP特化. 其中定义了一批私有的静态变量, 用来标识各种HTTP方法, 也是空参构造器. 对于生命周期函数, 变成了service(HttpServletRequest req, HttpServletResponse resp)
, 其中的参数是HttpServletRequest和HttpServletResponse类, 一会自然也要去看这两个类.
除此之外还针对每种请求提供了 protect doXXX(HttpServletRequest req, HttpServletResponse resp)
方法, 用于继承. 而service()方法其实变成了一个分发器, 通过获取请求的种类交给对应的函数进行处理. 这个类里基本都实现了doXXX默认的处理, 基本上就是判断协议版本然后返回支持或者不支持. 而我们具体的业务Servlet, 就继承HttpServlet, 然后覆盖所需要的doXXX方法即可, 至于service()方法一般不会覆盖.
下边就把这些方法都来试验一下吧:
package com.example.web;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
public class TestServlet extends HttpServlet {
//Servlet接口中的除了service()的生命周期方法
@Override
public void destroy() {
System.out.println("Servlet即将被销毁");
super.destroy();
}
@Override
public void init() throws ServletException {
System.out.println("Servlet正在init()中");
super.init();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.println(LocalDateTime.now().toString());
//Servlet接口中获取getServletConfig()方法
ServletConfig servletConfig = getServletConfig();
//获取了servletConfig对象, 这个对象稍后还会来看, 是配置对象
System.out.println(servletConfig.getServletName());
out.println(servletConfig.getServletName());
//Servlet接口中获取getServletInfo()方法, 其实这个方法由GenericServlet实现
System.out.println(getServletInfo());
//GenericServlet中的方法
//记录日志, 不配置日志插件的话默认会显示在控制台里
log("gugugu");
//记录日志并抛出异常, 这里注释掉了
// log("saner", new RuntimeException());
//可以看到方法不算多, Generic中的方法主要来自于接口, 新方法只有log, 也是调用ServletContext的log方法
//这也说明Web容器还需要日志功能.
}
}
HttpServletRequest
之前已经知道, 容器会创建HttpServletRequest和HttpServletResponse两个对象, 然后交给各个Servlet处理, 处理完之后将请求返回去.
可见这两个对象贯穿Web应用的全部, 而且是Servlet的处理对象, 所以必须知道这两个对象才可以. 先来看HttpServletRequest对象.
HttpServletRequest在容器内部, 是一个HTTP请求的封装, 里边携带了按照HTTP协议发过来的请求的全部信息.
在实际中传递给service()方法的两个实现类, 是由容器将对应的请求和响应包装好之后实现的, 这点要理解.
说是HttpServletRequest对象, 其实HttpServletRequest是一个接口, 这个接口继承自ServletRequest接口:
ServletRequest接口中的方法有很多, 其中有几个比较重要, HttpServletRequest则是带有很多HTTP特化的内容, 非常重要.
下边就来看一下其中的方法:
ServletRequest中的方法
void setAttribute(String var1, Object var2);
, 非常重要的方法, 给对象设置一个属性(键)和对应的值, 一般可以用来传递简单的模型数据
Object getAttribute(String var1);
, 非常重要的方法, 和上一个方法搭配起来使用, 其中获取指定的数据
void removeAttribute(String var1);
, 删除指定的键和值, 与前两个方法属于同一个体系
Enumeration<String> getAttributeNames();
, 获取被设置上的键名的集合, 与前三个方法属于同一个体系
int getContentLength();
, 获取内容的长度
String getContentType();
, 获取内容属于什么性质的内容
ServletInputStream getInputStream() throws IOException;
, 获取一个字节流, 用于从请求中读取二进制
String getParameter(String var1);
, 获取请求体中键对应的值, 注意和前四个方法不同, 是获取请求体中的值, 如果是GET请求, 也可以获取到参数对应的值
Enumeration<String> getParameterNames();
, 获取请求体中所有键名,通常用于获取POST的内容, 如果POST也附带请求行参数, 也一并可以获取
String[] getParameterValues(String var1);
, 如果请求体中的键对应一系列值, 这个就是将其获取成一个字符串数组.
Map<String, String[]> getParameterMap();
, 将请求体中的键值封装成一个Map对象返回
String getProtocol();
, 获取请求的协议, 一般是HTTP/1.1
String getScheme();
, 这个打印出http, 应该也是协议之类
String getServerName();
, 看名字是获取服务端名称
int getServerPort();
, 获取服务器所使用的端口, 用tomcat默认就是8080
BufferedReader getReader() throws IOException;
, 获取Reader, 用于从请求中获取字符
String getRemoteAddr();
, 获取远程地址
String getRemoteHost();
, 获取远程访问主机
Locale getLocale();
, 获取了当前的地区代码, 我测试出是zh_CN
Enumeration<Locale> getLocales();
, 应该也是和国际化有关
boolean isSecure();
, 是否安全, 到了HttpServlet中应该是指是否是HTTPS
RequestDispatcher getRequestDispatcher(String var1);
, 非常重要的方法, 通过传入字符串获取转发目标, 可以进行转发
int getRemotePort();
, 获取用户的端口
String getLocalName();
, 主机名称
String getLocalAddr();
, 本机地址
int getLocalPort();
, 本机端口
ServletContext getServletContext();
, 获取Web容器上下文, 通过请求也可以获取Web容器上下文
AsyncContext startAsync() throws IllegalStateException;
, 这个好像是异步? 现在不知道
AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws IllegalStateException;
, 也是异步相关吧, 好像是异步处理?
boolean isAsyncStarted();
, 也是异步相关吧, 表示异步是否已经开始?
boolean isAsyncSupported();
, 这个大概是指是否支持异步?
AsyncContext getAsyncContext();
, 异步上下文?
DispatcherType getDispatcherType();
, 获取转发器的类型? 这个测试出来是REQUEST, 应该表示转发的是请求?
HttpServletRequest中的方法
String getAuthType()
, 获取认证类型, 我测试都是Null
Cookie[] getCookies();
, 关键方法, 获取Cookie集合
long getDateHeader(String var1);
, 专门获取头部中的时间信息, 然后解析成长整型值, 例如 Date: Thu, 24 Oct 2019 05:14:34 GMT 这样的就可以被这个方法解析
String getHeader(String var1);
, 重要, 根据请求头中的键获取对应的值
Enumeration<String> getHeaders(String var1);
, 重要, 根据请求头中的键获取对应的多个值
Enumeration<String> getHeaderNames();
, 重要, 获取头部信息中的所有键名
int getIntHeader(String var1);
, 这个是可以直接将头部中某个可以解析成int的值获取出来
String getMethod();
, 获取请求方法,比如是GET还是POST
String getPathInfo();
, 实际测试下来, 显示是null
String getPathTranslated()
, 实际测试下来, 显示是null
String getContextPath();
, 实际测试下来, 显示是空字符串
String getQueryString();
, 这个是获取请求后边附带的参数字符串, 如果想自己解析的话也很重要
String getRemoteUser();
, 这个一会测试一下
boolean isUserInRole(String var1);
, 这里开始和用户相关了, 似乎是HTTPServlet附带的认证功能
Principal getUserPrincipal();
, Principal对象, 也和认证相关
String getRequestedSessionId();
, 获取SessionID,重要
String getRequestURI();
, 获取URI, 是相对地址
StringBuffer getRequestURL();
, 获取URL, 是绝对地址
String getServletPath();
, 测试下来应该是当前servlet解析的地址, 也就是访问地址
HttpSession getSession(boolean var1);
, 获取Session对象, 重要
HttpSession getSession();
, 获取Session对象, 重要
String changeSessionId();
, 这个命令测试下来是更改当前的SessionID, 返回新的SessionID
boolean isRequestedSessionIdValid();
看名称大概是sessionid是否还有效, 实际测试显示的是true
boolean isRequestedSessionIdFromCookie();
看意思是判断session是不是来自cookie, 我用postman和Chrome测试是true, 看来默认策略就是通过Cookie传递SessionID
boolean isRequestedSessionIdFromURL();
, 这个测试之后是false, 这几个SessionId相关的估计以后还是要学的
boolean authenticate(HttpServletResponse var1) throws IOException, ServletException;
, 这个现在不知道
void login(String var1, String var2) throws ServletException;
, 这个现在不知道
void logout() throws ServletException;
, 这个现在不知道, 但肯定还是和认证相关
下边就逐个试验一下吧, 除了异步的那几个以及用户验证的,基本都测试到了:
package com.example.web;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
public class TestServlet extends HttpServlet {
//Servlet接口中的除了service()的生命周期方法
@Override
public void destroy() {
System.out.println("Servlet即将被销毁");
super.destroy();
}
@Override
public void init() throws ServletException {
System.out.println("Servlet正在init()中");
super.init();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
resp.setHeader("content-type", "text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
String target = "saner";
req.setAttribute("penguin", target);
req.setAttribute("penguin2", target + "2");
String result = (String) req.getAttribute("penguin");
String result2 = (String) req.getAttribute("penguin2");
writer.println("penguin键的值是: " + result + "<br>");
writer.println("penguin2键的值是: " + result2 + "<br>");
Enumeration<String> names = req.getAttributeNames();
while (names.hasMoreElements()) {
writer.println(names.nextElement());
}
req.removeAttribute("penguin2");
writer.println("<br>");
writer.println("删除一个键之后的键名" + "<br>");
names = req.getAttributeNames();
while (names.hasMoreElements()) {
writer.println(names.nextElement());
}
writer.println("内容的长度是: " + req.getContentLength() + "<br>");
writer.println("内容的长度是(long): " + req.getContentLengthLong() + "<br>");
result = req.getParameter("name");
writer.write("GET请求传参的name=" + result + "<br>");
names = req.getParameterNames();
while (names.hasMoreElements()) {
writer.println(names.nextElement());
}
Map<String, String[]> map = req.getParameterMap();
writer.println(map.keySet() +"<br>");
writer.println("请求的类型是:" + req.getProtocol()+"<br>");
writer.println("请求的scheme是:" + req.getScheme()+"<br>");
writer.println("请求的ServerName是:" + req.getServerName()+"<br>");
writer.println("请求的Serverport是:" + req.getServerPort()+"<br>");
BufferedReader in = req.getReader();
in.lines().forEach(System.out::println);
writer.println("读出的一行是: " + in.readLine() + "<br>");
writer.println("请求的RemoteAddr(客户端地址)是:" + req.getRemoteAddr()+"<br>");
writer.println("请求的RemoteHost(客户端地址)是:" + req.getRemoteHost()+"<br>");
writer.println("请求的Locale(客户端地址)是:" + req.getLocale()+"<br>");
writer.println("请求isSecure:" + req.isSecure()+"<br>");
writer.println("请求的RemotePort:" + req.getRemotePort()+"<br>");
writer.println("请求的LocalName:" + req.getLocalName()+"<br>");
writer.println("请求的LocalAddr:" + req.getLocalAddr()+"<br>");
writer.println("请求的转发器类型:" + req.getDispatcherType()+"<br>");
writer.println("请求的AuthType:" + req.getAuthType()+"<br>");
writer.println("请求的Cookie[]:" + Arrays.toString(req.getCookies()) +"<br>");
writer.println("请求的getDateHeader:" + req.getDateHeader("Date")+"<br>");
writer.println("请求的Header Date:" + req.getHeader("Date")+"<br>");
writer.println("请求的Header Postman-Token:" + req.getHeader("Postman-Token")+"<br>");
Enumeration<String> headers = req.getHeaders("My");
writer.println("My 头信息对应的值是:");
while (headers.hasMoreElements()) {
writer.println(headers.nextElement() + " ");
}
writer.println("<br>");
headers = req.getHeaderNames();
writer.println("所有头部键名是:");
while (headers.hasMoreElements()) {
writer.println(headers.nextElement() + " ");
}
writer.println("<br>");
writer.println("请求的Header Time 解析成int:" + req.getIntHeader("Time")+"<br>");
writer.println("请求方法是:" + req.getMethod()+"<br>");
writer.println("请求 PathInfo:" + req.getPathInfo()+"<br>");
writer.println("请求 PathTranslated:" + req.getPathTranslated()+"<br>");
writer.println("请求 ContextPath:" + req.getContextPath()+"<br>");
writer.println("请求 QueryString:" + req.getQueryString()+"<br>");
writer.println("请求 RemoteUser:" + req.getRemoteUser()+"<br>");
writer.println("请求 RequestedSessionId:" + req.getRequestedSessionId()+"<br>");
writer.println("请求 RequestURI:" + req.getRequestURI()+"<br>");
writer.println("请求 RequestURL:" + req.getRequestURL()+"<br>");
writer.println("请求 ServletPath:" + req.getServletPath()+"<br>");
writer.println("请求 Session:" + req.getSession()+"<br>");
writer.println("请求 changeSessionId:" + req.changeSessionId()+"<br>");
writer.println("请求 isRequestedSessionIdValid:" + req.isRequestedSessionIdValid()+"<br>");
writer.println("请求 isRequestedSessionIdFromCookie:" + req.isRequestedSessionIdFromCookie()+"<br>");
writer.println("请求 isRequestedSessionIdFromURL:" + req.isRequestedSessionIdFromURL()+"<br>");
}
}
测试下来核心的方法主要是四大内容: 获取请求的基础信息(端口, URL, 方法等), 获取请求头信息, 获取请求行和请求体附带的参数, 操作Attributes. 此外还有一个转发器肯定也会用到.
HttpServletResponse
与HttpServletRequest类似, HttpServletResponse也是一个接口, 然后继承自ServletResponse接口.
这个对象相比响应对象还要重要, 因为承载着向用户返回信息的重任. 从请求中获取信息之后, 剩下的大部分任务都是如何组装这个对象的内容.
由于请求对象的内容很重要, 一般请求对象会先对其进行设置一些响应的头部信息, 比如setContentType()以及其他一些内容, 然后就是获取PrintWriter对象对其中近些
ServletResponse接口是基础的服务响应接口, 其中的方法有:
String getCharacterEncoding();
, 获取编码方式, 不设置的话, 获取出来是ISO-8859-1
String getContentType();
, 获取MIME内容, 没有设置的话是null
ServletOutputStream getOutputStream() throws IOException;
, 获取输出的字节流, 用于写入二进制内容
PrintWriter getWriter() throws IOException;
, 这个获取字符流, 用于写入页面内容
void setCharacterEncoding(String var1);
, 设置字符编码, 设置的是PrintWriter写字符时候使用的编码, 需要在获取PrintWriter之前调用
void setContentLength(int var1);
, 设置内容长度
void setContentLengthLong(long var1);
, long类型的设置内容长度
void setContentType(String var1);
, 设置ContentType, 和上边的get是一对
void setBufferSize(int var1);
, 应该是设置写入时候的缓冲区
int getBufferSize();
, 应该是获取写入时候的缓冲区长度, 我试验了一下默认是8192
void flushBuffer() throws IOException;
, 这个是刷新缓冲区, 有可能在Writer等方法中已经包含了这个.
void resetBuffer();
, 重置缓冲区
boolean isCommitted();
, 是否已经提交, 这个自己猜想估计是如果已经提交了, 就无法再更改了. 我测试下来是false
void reset();
, 这个是重置输出流, 即清空.
void setLocale(Locale var1);
, 设置区域.
Locale getLocale();
, 获取区域, 这个在没有先设置的情况下,我测试默认获取了zh_CN.
这其中比较重要的方法就是CharacterEncoding和ContentType相关的方法, 对于HTTP来说, 要将CharacterEncoding设置为"UTF-8, ContentType设置成为正确的MIME类型.
在完成这两个工作之后, 再调用输出流来写入.
在我测试的时候, 如果仅仅只设置 resp.setCharacterEncoding("UTF-8");
, 但不去设置resp.setContentType("****"), 则MIME里不会出现charset=utf-8, 如果之后进行了设置, 则ContentType中会追加charset=utf-8
所以一般推荐的顺序, 如果是输出字符流, 就先设置CharacterEncoding, 再设置ContentType, 之后再获取输出流对象进行打印. 关于MIME的设置可以看这里.
然后来看HttpServletResponse中的方法:
void addCookie(Cookie var1);
, 添加一个Cookie对象
boolean containsHeader(String var1);
, 是否包含指定的头部信息
String encodeURL(String var1);
, 重写URL, 使用附带sessionid的参数
String encodeRedirectURL(String var1);
, 重写URL, 使用附带sessionid的参数, 然后重定向
void sendError(int var1, String var2) throws IOException;
, 待测试
void sendError(int var1) throws IOException;
, 待测试
void sendRedirect(String var1) throws IOException;
, 重定向方法
void setDateHeader(String var1, long var2);
, 设置日期的头部, 属于快捷方法
void addDateHeader(String var1, long var2);
, 上一个方法的add版本, 注意和set系列方法的区别
void setHeader(String var1, String var2);
, 通用的设置头部键值的方法
void addHeader(String var1, String var2);
, 上一个方法的add版本
void setIntHeader(String var1, int var2);
, 设置int类型的header的快捷方法
void addIntHeader(String var1, int var2);
, 上一个方法的add版本
void setStatus(int var1);
, 设置状态码
int getStatus();
, 获取状态码
String getHeader(String var1);
, 获取某一个头部信息
Collection<String> getHeaders(String var1);
, 获取一个包含多个值的头部信息
Collection<String> getHeaderNames();
, 获取头部信息的键名集合
HttpServletResponse接口中, 除了上边这些方法, 还通过实例域把所有的HTTP状态码都定义了. 这个HTTP特化的servlet主要添加的功能是操作cookie和头部信息以及状态码.
这里想想还挺有意思, 很重要的contentType之类的设置, 竟然还不是在HTTPServlet中的, 我只能想到一个, 就是在HTTP大为流行之前, EJB的远程调用, 可能就已经使用到了ContentType之类功能, 而HTTP是在其后才大行其道的.
写HTML文件其实我们一般不会直接用HttpServletResponse的PrintWriter, 都会交给JSP. 但是下载文件一般还是用Servlet直接输出的. 输出文件的套路如下:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
File file = new File("D:\\test3.txt");
//重置响应内容
resp.reset();
//红色部分是文件名, 可以自己定义
resp.addHeader("Content-Disposition", "attachment;filename=" + "download");
//设置响应体的字节长度
resp.addHeader("Content-Length", String.valueOf(file.length()));
//这里下载了txt文件, 所以要设置如下的MIME, 根据具体文件要设置成不同的MIME类型
resp.setContentType("text/plain");
//下边就是从一个输入流读取然后写入到一个输出流的套路
BufferedOutputStream out = new BufferedOutputStream(resp.getOutputStream());
InputStream fis = new BufferedInputStream(new FileInputStream(file));
int read = 0;
byte[] bytes = new byte[1024];
while ((read = fis.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
//刷新缓冲区然后关闭流
out.flush();
out.close();
}
这里就不一一测试了, HttpServletResponse的关键方法就在于设置编码,设置MIME, 设置头部, 然后组装响应体. 注意响应里有一个方法叫做sendRedirect, 这个在下边要专门看一下.
请求分派与重定向
由于servlet已经沦为了控制器, 在之前的例子里, 控制器通过模型获取数据, 将数据设置到请求对象上, 然后采取了将请求对象进行转发给jsp处理的方式.
对比HttpServletRequest和HttpServletResponse, 前者有 getRequestDispatcher(String var1);
方法(其实是ServletRequest接口里的), 而后者有 void sendRedirect(String var1)
方法(这个属于HTTP重定向, 因此ServletResponse没这个方法, 是HttpServletResponse的方法).
这里我又想到, 看来请求转发的思想比HTTP出现的要早. 对比一下就发现, 请求可以转发, 而响应的时候可以重定向. 一对比就清晰很多了. 重定向其实就是自己什么也不做, 让浏览器去访问其他的内容. 而请求转发, 转发来转发去, 最后还是要向客户提供服务, 这是最根本的区别.
sendRedirect()函数的参数有如下三种方式:
- 可以是绝对地址URL, 比如
resp.sendRedirect("http://conyli.cc");
, 这样就跳转到绝对地址
- 可以是不带斜线的相对URL, 处理就和链接一样, 将当前URL上最后的部分去掉, 然后进行拼接, 比如
resp.sendRedirect("time");
- 可以是带斜线的相对URL, 处理就和链接一样, 表示相对Web应用根目录的, 比如
resp.sendRedirect("time");
要注意的是, 不能够在已经打开响应的流对象, 写入之后再调用sendRedirect()方法, 会报错:
resp.getWriter().write("gugugugugu");
//不刷新还没事, 刷新了缓冲区之后就会报错
resp.getWriter().flush();
resp.sendRedirect("/SelectBeer.do");
所以一般为了语义和代码清晰, 需要进行重定向了, 就直接重定向即可.
而请求分派就不同了, 分派的依然是服务器上的其他处理请求的servlet. 请求分派的字符串是一个相对地址, 可以加斜杠也可以不加, 含义与sendRedirect()中的参数是一样的.