> 文章列表 > Servlet教程

Servlet教程

Servlet教程

        在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}.*/@Overridepublic void destroy() {// NOOP by default}
​@Overridepublic String getInitParameter(String name) {return getServletConfig().getInitParameter(name);}
​@Overridepublic Enumeration<String> getInitParameterNames() {return getServletConfig().getInitParameterNames();}
​@Overridepublic ServletConfig getServletConfig() {return config;}
​@Overridepublic ServletContext getServletContext() {return getServletConfig().getServletContext();}
​@Overridepublic String getServletInfo() {return "";}
​@Overridepublic 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*/@Overridepublic abstract void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;
​@Overridepublic 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 {
​@Overridepublic 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 logicdoGet(req, resp);} else {long ifModifiedSince;try {ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);} catch (IllegalArgumentException iae) {// Invalid date header - proceed as if none was setifModifiedSince = -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 lessmaybeSetLastModified(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 {// 给这个响应添加一个cookievoid 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()方法原理:

  1. 客户端发送请求,Servlet1做出处理。
  2. Servlet1调用sendReadirect()方法,将客户端的请求 重新定位 到Servlet2。
  3. 客户端浏览器访问Servlet2.
  4. Servlet2对客户端浏览器做出响应。

forward()方法原理:

  1. 客户端发送请求,Servlet1做出处理。
  2. Servlet1调用sendReadirect()方法,将请求转发给Servlet2来处理请求,为客户端服务。
  3. 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() {}
}

  1. 客户端请求访问容器内的 Web 资源。
  2. Servlet 容器接收请求,并针对本次请求分别创建一个 request 对象和 response 对象。
  3. 请求到达 Web 资源之前,先调用 Filter 的 doFilter() 方法,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作。
  4. 在 Filter 的 doFilter() 方法内,调用 FilterChain.doFilter() 方法,将请求传递给下一个过滤器或目标资源。
  5. 目标资源生成响应信息返回客户端之前,处理控制权会再次回到 Filter 的 doFilter() 方法,执行 FilterChain.doFilter() 后的语句,检查 response 对象,修改响应头和响应正文。
  6. 响应信息返回客户端。

Filter 的生命周期

Filter 的生命周期分为 3 个阶段:

  1. 初始化阶段:web应用程序启动时,web服务器将创建Filter的实例对象,并调用其init方法,完成对象的初始化。容器启动时,读取 web.xml 或 @WebFilter 的配置信息对所有的过滤器进行加载和实例化。
  2. 拦截和过滤阶段:当客户端请求访问 Web 资源时,Servlet 容器会根据 web.xml 或 @WebFilter 的过滤规则进行检查。当客户端请求的 URL 与过滤器映射匹配时,容器将该请求的 request 对象、response 对象以及 FilterChain 对象以参数的形式传递给 Filter 的 doFilter() 方法,并调用该方法对请求/响应进行拦截和过滤。可以执行多次
  3. 销毁阶段:Filter 对象创建后会驻留在内存中,直到容器关闭或应用被移除时销毁。销毁 Filter 对象之前,容器会先调用 destory() 方法,释放过滤器占用的资源。在 Filter 的生命周期内,destory() 只执行一次。

filterChain

public class FilterDemo1 implements Filter{
​/** @see javax.servlet.Filter#init(javax.servlet.FilterConfig)*/@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// TODO Auto-generated method stub}
​@Overridepublic void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {// TODO Auto-generated method stubSystem.out.println("我是FilterDemo1,客户端向Servlet发送的请求被我拦截到了");//对请求放行,进入下一个过滤器FilterDemo2chain.doFilter(request, response);System.out.println("我是FilterDemo1,Servlet向客户端发送的响应被我拦截到了");}
​@Overridepublic void destroy() {// TODO Auto-generated method stub}
​
}public class FilterDemo2 implements Filter{
​@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// TODO Auto-generated method stub
​}
​@Overridepublic void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {// TODO Auto-generated method stubSystem.out.println("我是FilterDemo2,客户端向Servlet发送的请求被我拦截到了");//对请求放行,进入Servletchain.doFilter(request, response);System.out.println("我是FilterDemo2,Servlet向客户端发送的响应被我拦截到了");}
​@Overridepublic 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 进行属性值的访问。

有如下两个步骤:

  1. ServletContext 对象是一个为整个 web 应用提供共享的内存,任何请求都可以访问里面的内容
  2. 如何实现在服务启动的时候就动态的加入到里面的内容:我们需要做的有:
    1. 实现 servletContextListerner 接口 并将要共享的通过 setAttribute ( name,data )方法提交到内存中去 ;
    2. 应用项目通过 getAttribute(name) 将数据取到