在JavaEE平台上,处理TCP连接,解析HTTP协议这些底层工作统统扔给现成的Web服务器去做,我们只需要把自己的应用程序跑在Web服务器上。为了实现这一目的,JavaEE提供了Servlet API,我们使用Servlet API编写自己的Servlet来处理HTTP请求,Web服务器实现Servlet API接口
Servlet
public interface Servlet {
// 根据 ServletConfig 来对 Servlet 进行初始化,只执行一次
public void init(ServletConfig config) throws ServletException;
// 获取 Servlet 传给 init() 的对象
public ServletConfig getServletConfig();
// 每次请求 Servlet 时,就会调用该方法
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
// 返回 Servlet 的一段描述
public String getServletInfo();
// 当卸载应用或者关闭 Servlet 容器时,调用该方法
public void destroy();
}
GenericServlet
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
/**
* Does nothing. All of the servlet initialization is done by one of the
* <code>init</code> methods.
*/
public GenericServlet() {
// NOOP
}
/**
* Called by the servlet container to indicate to a servlet that the servlet
* is being taken out of service. See {@link Servlet#destroy}.
*/
@Override
public void destroy() {
// NOOP by default
}
@Override
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
@Override
public String getServletInfo() {
return "";
}
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
// 提供给子类覆盖使用,如果选择覆盖上面的哪个方法,那么必须手动维护 ServletConfig 实例
public void init() throws ServletException {
// NOOP by default
}
public void log(String message) {
getServletContext().log(getServletName() + ": " + message);
}
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
*/
@Override
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
@Override
public String getServletName() {
return config.getServletName();
}
}
HttpServlet
HttpServlet 抽象类是继承于 GenericServlet 抽象类而来的。使用 HttpServlet 抽象类时,还需要借助分别代表 Servlet请求和 Servlet 响应的 HttpServletRequest 和 HttpServletResponse 对象。
HttpServlet 抽象类覆盖了 GenericServlet 抽象类中的Service()方法,并且添加了一个自己独有的Service(HttpServletRequest request,HttpServletResponse)方法。
public abstract class HttpServlet extends GenericServlet {
@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(lStrings.getString("http.non_http"));
}
//
service(request, response);
}
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);
}
}
HttpServlet 中的 service 方法把接收到的 ServletRequsest 类型的对象转换成了 HttpServletRequest 类型的对象,把ServletResponse 类型的对象转换成了 HttpServletResponse 类型的对象。之所以能够这样强制的转换,是因为在调用Servlet 的 Service 方法时,Servlet 容器总会传入一个 HttpServletRequest 对象和 HttpServletResponse 对象,预备使用HTTP。因此,转换类型当然不会出错了。
总之,HttpServlet有两个特性是GenericServlet所不具备的:
- 不用覆盖 service 方法,而是覆盖 doGet 或者 doPost 方法。在少数情况,还会覆盖其他的5个方法。
- 使用的是 HttpServletRequest 和 HttpServletResponse 对象
我们使用Servlet API时,并不直接与底层TCP交互,也不需要解析HTTP协议,因为HttpServletRequest和HttpServletResponse就已经封装好了请求和响应。以发送响应为例,我们只需要设置正确的响应类型,然后获取PrintWriter,写入响应即可。
ServletRequest
public interface ServletRequest {
// 返回上下文指定对象(全局)
public Object getAttribute(String name);
// 返回请求主体的字节数
public int getContentLength();
// 返回主体的MIME类型 ex:text/html image/gif ...
public String getContentType();
// 返回请求参数的值
public String getParameter(String name);
}
HttpServletRequest
HttpServletRequest 表示 Http 环境中的 Servlet 请求。它扩展于 javax.servlet.ServletRequest 接口,并添加了几个方法。
public interface HttpServletRequest extends ServletRequest {
// 返回请求上下文的请求URI部分
String getContextPath();
// 返回一个cookie对象数组
Cookie[] getCookies();
// 返回指定HTTP标题的值
String getHeader(String var1);
// 返回生成这个请求HTTP的方法名称
String getMethod();
// 返回请求URL中的查询字符串
String getQueryString();
// 返回与这个请求相关的会话对象
HttpSession getSession();
}
乱码问题
*在service中使用的编码解码方式默认为:ISO-8859-1编码*,但此编码并不支持中文,因此会出现乱码问题
// post 请求
request.setCharacterEncoding("UTF-8")
// get 请求
parameter = newString(parameter.getBytes("iso-8859-1"), "utf-8");
ServletResponse
public interface ServletResponse {
public String getContentType();
public ServletOutputStream getOutputStream() throws IOException;
// 默认使用 ISO-8859-1 编码
public PrintWriter getWriter() throws IOException;
}
HttpServletResponse
public interface HttpServletResponse extends ServletResponse {
// 给这个响应添加一个cookie
void addCookie(Cookie var1);
// 给这个请求添加一个响应头
void addHeader(String var1, String var2);
// 发送一条响应码,讲浏览器跳转到指定的位置
void sendRedirect(String var1) throws IOException;
// 设置响应行的状态码
void setStatus(int var1);
// 获得字符流
PrintWriter getWriter() throws IOException;
// 获得字节流
ServletOutputStream getOutputStream() throws IOException;
}
通过字符流的 write(String s) 方法可以将字符串设置到 response 缓冲区中,随后 Tomcat 会将 response 缓冲区中的内容组装成 Http 响应返回给浏览器端。
通过该字节流的 write(byte[] bytes) 可以向 response 缓冲区中写入字节,再由 Tomcat 服务器将字节内容组成 Http 响应返回给浏览器。
**注意:虽然response对象的getOutSream()和getWriter()方法都可以发送响应消息体,但是他们之间相互排斥,不可以同时使用,否则会发生异常。
乱码问题
response.setCharacterEncoding("utf-8");
// 通知浏览器使用 utf-8 解码
response.setHeader("Content-Type", "text/html;charset=utf-8")
// 该方法包含上面两个方法
response.setContentType("text/html;charset=utf-8")
ServletConfig
ServletConfig是指当前servlet在web.xml文件中的配置信息。开发者通过ServletConfig对象就可以得到当前servlet的初始化参数信息
public interface ServletConfig {
public String getServletName();
public ServletContext getServletContext();
// 获取 Servlet 初始化参数
public String getInitParameter(String name);
// 获取 Servlet 初始化参数的名称
public Enumeration<String> getInitParameterNames();
}
ServletContext
ServletContext是一个全局的储存信息的空间,服务器开始就存在,服务器关闭才释放
由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。公共聊天室就会用到它。
当web应用关闭、Tomcat关闭或者Web应用reload的时候,ServletContext对象会被销毁
获取 ServletContext
this.getServletContext();
this.getServletConfig.getServletContext()
ServletContext
public interface ServletContext {
public Object getAttribute(String name);
public void setAttribute(String name, Object object);
public void removeAttribute(String name);
public RequestDispatcher getRequestDispatcher(String path);
}
ServletContext应用
- 多个Servlet可以通过ServletContext对象来实现数据间的共享
类似于Session,通过ServletContext对象我们也可以实现数据共享,但值得注意的是,Session是只能在一个客户端中共享数据,而ServletContext中的数据是在所有客户端中都可以实现数据共享的
- 实现Servlet的请求转发
request.getRequestDispatcher("/url").forward(request, response);
this.getServletContext().getRequestDispather("/url").forward(request, response);
- 获取Web应用的初始化参数
String name = this.getServletContext().getInitParameter("name");
- 利用ServletContext对象读取资源文件(比如properties文件
// 文件在WebRoot文件夹下,即Web应用的根目录。这时候我们可以使用ServletContext来读取该资源文件
InputStream stream = this.getServletContext().getResourceAsStream("dbinfo.properties");
// 如果这个文件放在了src目录下,这时就不能用ServletContext来读取了,必须要使用类加载器去读取
InputStream stream = MyServlet.class.getClassLoader().getResourceAsStream("dbinfo.properties")
RequestDispatcher
RequestDispatcher 有一个特点,就是浏览器上显示的URL是最先请求的目标资源的URL,不会因为使用了forward、include方法而改变。因此forward和include的调用对于用户来说是透明的
public interface RequestDispatcher {
public void forward(ServletRequest request, ServletResponse response)
throws ServletException, IOException;
public void include(ServletRequest request, ServletResponse response)
throws ServletException, IOException;
}
forward
servlet2的response发送给客户端。而servlet1的response不会显示给用户
例如用户请求的是目标资源A,A接受到请求后,转发到B,真正产生响应数据是被转发的资源B,而A只是起个引导转发作用。浏览器的地址栏不会变,依然是A的URL。
注意事项:
1、在目标资源中调用forward方法时,必须保证此响应没有提交。也就是不要使用 ServletResponse 对象的输出流对象,因为即便你写入了数据到响应缓冲区,最后也会被清空,如果缓冲区数据被刷新提交(out.flush),还会抛出IllegalStateException异常。
2、对于forward方法传递的request对象:虽然我们从调用上看,好像是将request对象传递给转动的资源上去了,但是我发现目标资源使用的request对象和转发的资源使用的request对象不是同一个request对象,因为分别从这2个request中获取RequestURL,发现是不一样的。但是在目标资源request提取的Paramter 和 Attribute ,在转发后的资源的request对象中,依然都可以提取到,且是相同的。所以,二者只是在请求路径相关的属性上不同,其它API调用返回的都是一样的。
3、在forward语句的前后,都不应该有响应输出的语句,应该会被忽略
include
servlet2的response包含在(正在发送给客户端的)servlet1的response包中
注意事项:
1、被包含者(servlet2)不能设置ServletResponse的响应状态和响应头(否则并不会产生效果),因为这些都是包含者做的事,被包含者只需要产生响应数据解可以了。
2、不同于 forward中的request的传递特性:在被包含的资源中从request中获取请求路径相关的信息,发现依然是原始请求的路径,也就是浏览器地址栏相关的路径,也就是说被包含的资源获得的request对象的路径属性和原始请求资源的路径一样。其它的API调用也是一样的(Attribute 和Parameter)
sendReadirect
public interface HttpServletResponse extends ServletResponse {
void sendRedirect(String var1) throws IOException;
}
sendReadirect() 方法和 forward() 都是用于请求转发的方法,转发给另外的资源为客户端服务。但二者有本质的区别
sendReadirect()方法原理:
- 客户端发送请求,Servlet1做出处理。
- Servlet1调用sendReadirect()方法,将客户端的请求 重新定位 到Servlet2。
- 客户端浏览器访问Servlet2.
- Servlet2对客户端浏览器做出响应。
forward()方法原理:
- 客户端发送请求,Servlet1做出处理。
- Servlet1调用sendReadirect()方法,将请求转发给Servlet2来处理请求,为客户端服务。
- Servlet2对客户端浏览器做出响应。
区别:
定位与转发
sendReadirect()方法是重新定位到另外一个资源来处理请求,URL会重新定位,让客户端重新访问另外一个资源。 forward()方法是转发到另外一个资源来处理请求。URL不会变化。隐藏了处理对象的变化。
处理请求的资源的范围
sendReadirect()方法可以跨WEB应用程序和服务器重新定位资源来处理请求。 forward()方法只能在应用程序内部转发。
参考文档
https://www.cnblogs.com/lulipro/p/7471987.html
Servlet请求转发 RequestDispatcher接口_qfs_v的博客-CSDN博客
Filter
它是在 Servlet 2.3 规范中定义的,能够对 Servlet 容器传给 Web 资源的 request 对象和 response 对象进行检查和修改。
Filter 不是 Servlet,不能直接访问,它本身也不能生成 request 对象和 response 对象,它只能为 Web 资源提供以下过滤功能:
- 在 Web 资源被访问前,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作。
- 将请求传递到下一个过滤器或目标资源。
- 在 Web 资源被访问后,检查 response 对象,修改响应头和响应正文。
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
- 客户端请求访问容器内的 Web 资源。
- Servlet 容器接收请求,并针对本次请求分别创建一个 request 对象和 response 对象。
- 请求到达 Web 资源之前,先调用 Filter 的 doFilter() 方法,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作。
- 在 Filter 的 doFilter() 方法内,调用 FilterChain.doFilter() 方法,将请求传递给下一个过滤器或目标资源。
- 目标资源生成响应信息返回客户端之前,处理控制权会再次回到 Filter 的 doFilter() 方法,执行 FilterChain.doFilter() 后的语句,检查 response 对象,修改响应头和响应正文。
- 响应信息返回客户端。
Filter 的生命周期
Filter 的生命周期分为 3 个阶段:
- 初始化阶段:web应用程序启动时,web服务器将创建Filter的实例对象,并调用其init方法,完成对象的初始化。容器启动时,读取 web.xml 或 @WebFilter 的配置信息对所有的过滤器进行加载和实例化。
- 拦截和过滤阶段:当客户端请求访问 Web 资源时,Servlet 容器会根据 web.xml 或 @WebFilter 的过滤规则进行检查。当客户端请求的 URL 与过滤器映射匹配时,容器将该请求的 request 对象、response 对象以及 FilterChain 对象以参数的形式传递给 Filter 的 doFilter() 方法,并调用该方法对请求/响应进行拦截和过滤。可以执行多次
- 销毁阶段:Filter 对象创建后会驻留在内存中,直到容器关闭或应用被移除时销毁。销毁 Filter 对象之前,容器会先调用 destory() 方法,释放过滤器占用的资源。在 Filter 的生命周期内,destory() 只执行一次。
filterChain
public class FilterDemo1 implements Filter{
/*
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
System.out.println("我是FilterDemo1,客户端向Servlet发送的请求被我拦截到了");
//对请求放行,进入下一个过滤器FilterDemo2
chain.doFilter(request, response);
System.out.println("我是FilterDemo1,Servlet向客户端发送的响应被我拦截到了");
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
public class FilterDemo2 implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
System.out.println("我是FilterDemo2,客户端向Servlet发送的请求被我拦截到了");
//对请求放行,进入Servlet
chain.doFilter(request, response);
System.out.println("我是FilterDemo2,Servlet向客户端发送的响应被我拦截到了");
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
<filter>
<filter-name>filterDemo1</filter-name>
<filter-class>com.oracle.filter.FilterDemo1</filter-class>
</filter>
<filter>
<filter-name>filterDemo2</filter-name>
<filter-class>com.oracle.filter.FilterDemo2</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo1</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filterDemo2</filter-name>
<url-pattern>/*</url-pattern>
<!-- /*是对所有的文件进行拦截 -->
</filter-mapping>
分 析:当有多个过滤器对同一个请求进行拦截时,根据web.xml文件中的配置顺序,谁在前,先执行谁。当第 一过滤器拦截成功后,会执行doFilter方法,该方法中,调用chain.doFilter方法,会将该请求放行给下一个过滤器,依次执行,直到执行 到最后一个过滤器,当最后一个过滤器调用chain.doFilter方法时,请求会被放行给Servlet,当Servlet处理返回响应信息时,先返 回到最后执行的过滤器,继续执行该过滤器剩下的代码。依次返回,直到返回到第一个过滤器,最后返回给客户端。
ServletContextListener
监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期。
当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该事件由ServletContextListener 来处理。
public interface ServletContextListener extends EventListener {
// 当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化
// 并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。
default void contextInitialized(ServletContextEvent sce) {
}
// 当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。
default void contextDestroyed(ServletContextEvent sce) {
}
}
应用
在服务启动时,将数据库中的数据加载进内存,并将其赋值给一个属性名,其它的 Servlet 就可以通过 getAttribute 进行属性值的访问。
有如下两个步骤:
- ServletContext 对象是一个为整个 web 应用提供共享的内存,任何请求都可以访问里面的内容
- 如何实现在服务启动的时候就动态的加入到里面的内容:我们需要做的有:
- 实现 servletContextListerner 接口 并将要共享的通过 setAttribute ( name,data )方法提交到内存中去 ;
- 应用项目通过 getAttribute(name) 将数据取到