Servelt & JSP

Java Web应用

Java Web 应用是一个 servlets, HTML 页面,类,和其他资源的集合,用于一个在 Web 服务器的完成的应用。Web 应用可以捆绑和运行来自多个供应商的在多个容器。
servlet 容器必须强制 Web 应用程序和 ServletContext 之间一对一对应的关系。ServletContext 对象提供了一个 servlet 和它的应用程序视图。

目录结构

可以使用标准的 Java 归档工具把 Web 应用程序打包并签名到一个 Web 存档格式(WAR)文件中。例如,一个关于“issue tracking”的应用程序可以分布在一个称为 issuetrack.war 的归档文件中。

当打包成这种形式时,将生成一个 META-INF 目录,其中包含了对 java归档工具有用的信息。尽管这个目录的内容可以通过 servlet 代码调用ServletContext 的 getResource 和 getResourceAsStream 方法来访问,容器也不能把这个目录当作内容来响应客户端请求。此外,任何请求访问 META-INF 目录中的资源必须返回一个 SC_NOT_FOUND(404)的响应。常见的归档格式war 和 ear格式对比:

  • war: Web Archive file, 结构如下:

    webapp.war
    |-index.jsp
    |— META-INF
    |-Manifest.mf
    |— WEB-INF
    |- web.xml
    |— classes
    |— lib // 依赖的jar包
  • ear: Enterprise ARchieve, 用于在Java EE中将一个或者多个模块封装到一个文件中, 这样, 多个不同模块在应用服务器上的部署就可以同时并持续的进行. 结构如下:

    app.ear
    |- ejb.jar // ejb-jar包
    |- other.jar // 普通jar包
    |- webapp.war // war包
    |—META-INF
    application.xml // EAR描述文件

Web.xml

servlet3.*的规范已经支持不使用 web.xml了

<?xml version="1.0" encoding="ISO-8859-1" ?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">

<display-name>HelloWorld Application</display-name>
<description>
This is a simple web application with a source code organization
based on the recommendations of the Application Developer's Guide.
</description>

<listener>
<listener-class>org.web.listener.MyServletRequestListener</listener-class>
</listener>
<listener>
<listener-class>org.web.listener.MyServletContextListener</listener-class>
</listener>

<filter>
<filter-name>ResponseFilter</filter-name>
<filter-class>org.web.filter.ResponseFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ResponseFilter</filter-name>
<servlet-name>HelloServlet</servlet-name>
</filter-mapping>

<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>org.web.Servlet.Hello</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

</web-app>

Servlet(Server Applet)

本节参考自: Java Servlet 3.1 Specification《Java Servlet 3.1 规范》中文翻译及示例 @Ref

什么是 Servlet

servlet 是基于 Java 的 Web 组件,由容器进行管理,来生成动态内容。像其他基于 Java 的组件技术一样,servlet 也是基于平台无关的 Java 类格式,被编译为平台无关的字节码,可以被基于 Java 技术的 Web 服务器动态加载并运行。容器(Container),有时候也叫做 servlet 引擎,是 Web 服务器为支持 servlet 功能扩展的部分。客户端通过 servlet 容器实现的 request/response paradigm(请求/应答模式) 与 Servlet 进行交互。

什么是 Servlet 容器

Servlet Container(Servlet 容器) 是 Web 服务器或者应用服务器的一部分,用于提供基于请求/响应发送模式的网络服务,解码基于 MIME 的请求,并且格式化基于 MIME 的响应。Servlet 容器同时也包含和管理他们的生命周期里 Servlet。
Servlet 容器可以嵌入到宿主的 Web 服务器中,或者通过 Web 服务器的本地扩展 API 单独作为附加组件安装。Servelt 容器也可能内嵌或安装到启用 Web 功能的应用服务器中。
所有的 Servlet 容器必须支持 HTTP 协议用于请求和响应,但额外的基于 请求/响应 的协议,如 HTTPS (HTTP over SSL)的支持是可选的。对于 HTTP 规范需要版本,容器必须支持 HTTP/1.0 和 HTTP/1.1。因为容器或许支持 RFC2616 (HTTP/1.1)描述的缓存机制,缓存机制可能在将客户端请求交给 Servlet 处理之前修改它们,也可能在将 Servlet 生成的响应发送给客户端之前修改它们,或者可能根据 RFC2616 规范直接对请求作出响应而不交给 Servlet 进行处理。
Servlet 容器应该使 Servlet 执行在一个安全限制的环境中。在 Java 平台标准版(J2SE, v.1.3 或更高) 或者 Java平台企业版(Java EE, v.1.3 或更高) 的环境下,这些限制应该被放置在 Java 平台定义的安全许可架构中。比如,高端的应用服务器为了保证容器的其他组件不受到负面影响可能会限制 Thread 对象的创建。

Servlet 与其他技术的对比

从功能上看,servlet 位于 Common Gateway Interface(公共网关接口,简称 CGI)程序和私有的服务器扩展如 Netscape Server API(NSAPI)或 Apache Modules 这两者之间。
相对于其他服务器扩展机制 Servlet 有如下优势:

  • 它们通常比 CGI 脚本更快,因为采用不同的处理模型。
  • 它们采用标准的 API 从而支持更多的Web 服务器。
  • 它们拥有 Java 编程语言的所有优势,包括容易开发和平台无关。
  • 它们可以访问 Java 平台提供的大量的 API。

与 Java EE 的关系

Java Servlet API 3.1 版本是 Java 平台企业版 7 版本必须的 API。Servlet 容器和 servlet 被部署到平台中时,为了能在 Java EE 环境中执行,必须满足 JavaEE 规范中描述的额外的一些要求。

与 Servlet 2.5 规范的兼容性

在 Servlet 2.5 中, metadata-complete 只影响在部署时的注释扫描。 web-fragments 的概念在 servlet 2.5 并不存在。然而在 servlet 3.0 和之后,metadata-complete 影响扫描所有的在部署时指定部署信息和 web-fragments 注释。注释的版本的描述符必须不影响你扫描在一个web应用程序。除非 metadata-complete 指定,规范的一个特定版本的实现必须扫描所有配置的支持的注解。

HttpServletRequest – 请求

本节参考 请求 · Java Servlet 3.1 Specification《Java Servlet 3.1 规范》中文翻译及示例 @Ref

生命周期

每个请求对象只在一个 servlet 的 service 方法的作用域内,或过滤器的 doFilter 方法的作用域内有效,除非该组件启用了异步处理并且调用了请求对象的 startAsync 方法。
在发生异步处理的情况下,请求对象一直有效,直到调用 AsyncContext 的 complete 方法。容器通常会重复利用请求对象,以避免创建请求对象而产生的性能开销。
开发人员必须注意的是,不建议在上述范围之外保持 startAsync 方法还没有被调用的请求对象的引用,因为这样可能产生不确定的结果。

API

ServletRequest 接口提供方法:

  • getParameter
  • getParameterNames
  • getParameterValues
  • getParts
  • getPart
  • getAttribute
  • getHeader/getHeaders
  • getContextPath

文件上传

当数据以multipart/form-data的格式发送时,servlet 容器支持文件上传。

  • getAttribute
  • getAttributeNames
  • setAttribute

属性

  • getAttribute
  • getAttributeNames
  • setAttribute

请求路径

  • getContextPath
  • getServletPath
  • getPathInfo

requestURI = contextPath + servletPath + pathInfo

非阻塞 IO

非阻塞 IO · Java Servlet 3.1 Specification《Java Servlet 3.1 规范》中文翻译及示例

ServletResponse – 响应

响应 · Java Servlet 3.1 Specification《Java Servlet 3.1 规范》中文翻译及示例

ServletContext – 上下文

ServletContext 接口定义了 servlet 运行在的 Web 应用的视图。容器供应商负责提供 servlet 容器的 ServletContext 接口的实现。servlet 可以使用 ServletContext 对象记录事件,获取 URL 引用的资源,存取当前上下文的其他 servlet 可以访问的属性。
ServletContext 是 Web 服务器中已知路径的根。例如,servlet 上下文可以从 http://www.mycorp.com/catalog 找出,/catalog 请求路径称为上下文路径,所有以它开头的请求都会被路由到与 ServletContext 相关联的 Web 应用。

Filter – 过滤器

过滤器是一种代码重用的技术,它可以转换 HTTP 请求的内容,响应,及头信息。过滤器通常不产生响应或像 servlet 那样对请求作出响应,而是修改或调整到资源的请求,修改或调整来自资源的响应。

在web.xml中声明的每个<filter>在每个 JVM 的容器中仅实例化一个实例。容器提供了声明在过滤器的部署描述符的过滤器config(译者注:FilterConfig),对 Web 应用的 ServletContext 的引用,和一组初始化参数。
当容器接收到传入的请求时,它将获取列表中的第一个过滤器并调用doFilter 方法,传入 ServletRequest 和 ServletResponse,和一个它将使用的 FilterChain 对象的引用。

过滤器组件示例

  • Authentication filters //用户身份验证过滤器
  • Logging and auditing filters //日志记录与审计过滤器
  • Image conversion filters //图片转换过滤器
  • Data compression filters //数据压缩过滤器
  • Encryption filters //加密过滤器
  • Tokenizing filters //分词过滤
  • Filters that trigger resource access events //触发资源访问事件过滤
  • XSL/T filters that transform XML content
  • MIME-type chain filters //MIME-TYPE 链过滤器
  • Caching filters //缓存过滤器

Listener – 监听器

Listener 用于监听 java web程序中的事件,例如创建、修改、删除Session、request、context等,并触发响应的事件。
Listener 对应观察者模式,事件发生的时候会自动触发该事件对应的Listeer。 Listener 主要用于对 Session、Request、Context 进行监控。servlet2.5 规范中共有 8 种Listener 。

不同功能的Listener 需要实现不同的 Listener 接口,一个Listener也可以实现多个接口,这样就可以多种功能的监听器一起工作。监听器接口可以分为三类:

  • 1)监听 Session、Request、Context 的创建于销毁,分别为 HttpSessionListerServletContextListenerServletRequestListener
  • 2)监听对象属性变化,分别为:HttpSessionAttributeListerServletContextAttributeListenerServletRequestAttributeListener
  • 3)监听Session 内的对象,分别为HttpSessionBindingListenerHttpSessionActivationListener。与上面六类不同,这两类 Listener 监听的是Session 内的对象,而非 Session 本身,不需要在 web.xml中配置。

实现一个Listener

web.xml的Listener配置: <listener>标签与 <listener-class>

<listener>
<listener-class>servlet.listener.MyListener</listener-class>
</listener>

创建 MyListener, 实现监听对Session, Context, Request对象的创建与销毁:

public class MyListener implements HttpSessionListener, ServletContextListener, ServletRequestListener {

Log log = LogFactory.getLog(getClass());

// 创建 session
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
log.info("新创建一个session, ID为: " + session.getId());
}

// 销毁 session
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
log.info("销毁一个session, ID为: " + session.getId());
}

// 加载 context
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext servletContext = sce.getServletContext();
log.info("即将启动" + servletContext.getContextPath());
}

// 卸载 context
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContext servletContext = sce.getServletContext();
log.info("即将关闭" + servletContext.getContextPath());
}
}

HttpSession – 会话

会话跟踪机制:

  • Cookie: 通过 HTTP cookie 的会话跟踪是最常用的会话跟踪机制,且所有 servlet 容器都应该支持。所有 servlet 容器必须提供能够配置容器是否标记会话跟踪 cookie 为HttpOnly的能力。
  • SSL会话: 安全套接字层(Secure Sockets Layer),在 HTTPS 使用的加密技术,有一种内置机制允许多个来自客户端的请求被明确识别为同一会话。Servlet容器可以很容易地使用该数据来定义会话。
  • URL 重写: URL 重写是会话跟踪的最低标准。当客户端不接受 cookie 时,服务器可使用 URL 重写作为会话跟踪的基础。URL 重写涉及添加数据、会话 ID、容器解析 URL 路径从而请求与会话相关联。

Dispatcher – 分发器

RequestDispatcher 接口负责把请求转发给一个 servlet 处理;
当请求启用异步处理时,AsyncContext 允许用户将这个请求转发到servlet 容器。

可以通过ServletContext.getRequestDispatcher()获取 RequestDispatcher.

使用请求调度器:

  • include 方法: include 方法的目标 servlet 能够访问请求对象的各个方法(all aspects),但是使用响应对象的方法会受到更多限制。
  • forward 方法: RequestDispatcher 接口的 forward() 方法,只有在没有输出提交到向客户端时,通过正在被调用的 servlet 调用。如果响应缓冲区中存在尚未提交的输出数据,这些数据内容必须在目标 servlet 的 service() 方法调用前清除。如果响应已经提交,必须抛出一个 IllegalStateException 异常。
String path = “/raisins.jsp”;
RequestDispatcher rd = context.getRequestDispatcher(path);
rd.include(request, response);

生命周期

Servlet 生命周期

当容器启动后, 容器会判断内存中是否存在指定的 Servlet对象, 如果没有则创建它, 当容器停止或者重新启动, Servlet容器调用 Servlet对象的 destroy方法来释放资源;
Servlet生命周期分几个步骤: Servlet类加载 -> 实例化 -> 服务 -> 销毁:

  • Servlet容器 负责加载 Servlet类
  • Servlet容器 使用开始实例化 Servlet, 创建对象并调用 init()方法
  • 响应客户请求阶段调用 service()方法
  • 销毁阶段调用 destroy()方法

Request 生命周期

接收到HTTP请求后, 容器会创建 HttpServletRequest对象, 并传递给 Servlet, 在这次请求结束后, Request对象也被销毁;
每个请求对象只在一个 servlet 的 service() 方法的作用域内, 或过滤器的 doFilter() 方法的作用域内有效,
除非该组件启用了异步处理并且调用了请求对象的 startAsync() 方法. 在发生异步处理的情况下, 请求对象一直有效, 直到调用 AsyncContext 的 complete() 方法.

并发 & 多线程问题

Servlet采用多线程来处理多个请求同时访问,Servlet容器维护了一个线程池来服务请求。
线程池实际上是等待执行代码的一组线程叫做工作者线程(WorkerThread),Servlet容器使用一个调度线程来管理工作者线程(DispatcherThread)。
当容器收到一个访问Servlet的请求,调度者线程从线程池中选出一个工作者线程,将请求传递给该线程,然后由该线程来执行Servlet的service()方法。
当这个线程正在执行的时候,容器收到另外一个请求,调度者线程将从池中选出另外一个工作者线程来服务新的请求,容器并不关心这个请求是否访问的是同一个Servlet还是另外一个Servlet。当容器同时收到对同一Servlet的多个请求,那这个Servlet的service()方法将在多线程中并发的执行。

同步service()的两种方式:

  • Servlet实现SingleThreadModel接口: 开发人员实现 SingleThreadModel 接口,由容器保证一个 service() 方法在同一个时间点仅被一个请求线程调用,但是此方案是不推荐的。servlet 容器可以通过串行化访问 servlet的请求,或者维护一个 servlet 实例池完成该需求。如果 Web 应用中的 servlet 被标注为分布式的,容器应该为每一个分布式应用程序的 JVM 维护一个 servlet 实例池。
  • synchronized同步service()方法, 不建议使用: 对于那些没有实现 SingleThreadModel 接口的 servlet,但是它的service() 方法(或者是那些 HttpServlet 中通过 service 方法分派的doGet、doPost 等分派方法)是通过 synchronized 关键词定义的,servlet 容器不能使用实例池方案,并且只能使用序列化请求进行处理。强烈推荐开发人员不要去同步 service() 方法(或者那些由 service() 分派的方法),因为这将严重影响性能。

线程不安全

这就导致了Servlet里的实例变量是线程不安全的,多个线程(多个客户端的请求)共享这些实例变量,一个线程对这些实例变量的改变会影响其它线程的取值,Servlet规范已经声明Servlet不是线程安全的,包括jsp,Servlet,javabean等。

ServletContext:(线程不安全)

ServletContext是可以多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clon。所以在Servlet上下文中尽可能少量保存会被修改(写)的数据,可以采取其他方式在多个Servlet中共享,比方我们可以使用单例模式来处理共享数据。

HttpSession:(线程不安全)

HttpSession对象在用户会话期间存在,只能在处理属于同一个Session的请求的线程中被访问,因此Session对象的属性访问理论上是线程安全的。当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,可能造成同时多线程读写属性。这时我们需要对属性的读写进行同步处理:使用同步块Synchronized和使用读/写器来解决。

ServletRequest:(线程安全)

对于每一个请求,由一个工作线程来执行,都会创建有一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。ServletRequest对 象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存请求对象的引用。

http://wenboo.site/2016/11/14/Servlet-%E5%B9%B6%E5%8F%91%E5%B0%8F%E7%BB%93/

异步 & AsyncContext

@Ref Servlet 3.0/3.1 中的异步处理

在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。
为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。

在Servlet 3.0中,@WebServlet@WebFilter 注解有一个属性——asyncSupported,boolean 类型默认值为 false。
asyncSupported 设置为 true,我们可以从HttpServletRequest对象中通过startAsync()获得一个AsyncContext对象,AsyncContext对象构成了异步处理的上下文,Request和Response对象都可从中获取。
AsyncContext 可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,当前请求的线程便可以还回给容器线程池以处理更多的请求。

一个有较长耗时操作的Servlet可以这样写:

@WebServlet(value = "/simpleAsync", asyncSupported = true)
public class SimpleAsyncHelloServlet extends HttpServlet {

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
AsyncContext asyncContext = request.startAsync();
asyncContext.start(() -> {
new LongRunningProcess().run();
try {
asyncContext.getResponse().getWriter().write("Hello World!");
} catch (IOException e) {
e.printStackTrace();
}
asyncContext.complete();
});
}
}

先通过request.startAsync()获取到该请求对应的AsyncContext,然后调用AsyncContextstart()方法进行异步处理,处理完毕后需要调用AsyncContext.complete()方法告知Servlet容器。
AsyncContext.start()方法会向Servlet容器另外申请一个新的线程(可以是从Servlet容器中已有的主线程池获取,也可以另外维护一个线程池,不同容器实现可能不一样),然后在这个新的线程中继续处理请求,而原先的线程将被回收到主线程池中。
事实上,这种方式对性能的改进不大,因为如果新的线程和初始线程共享同一个线程池的话,相当于闲置下了一个线程,但同时又占用了另一个线程。

这里有一篇文章The Limited Usefulness of AsyncContext.start() - DZone Java,
对该方法做了性能测试, 结论如下 :

  • Tomcat的 AsyncContext.start实现是, 把处理Request的线程返回线程池, 再从 Http work threadpool里取出线程
  • 相比不使用异步的情况, 在 Tomcat中使用 Servlet3.0 的 AsyncContext.start 不会带来任何 Tomcat并发性能改进
  • 正确的办法是另外维护一个线程池,这个线程池不同于Servlet容器的主线程池(请求线程池),如下图:

在上图中,用户发起的请求首先交由Servlet容器主线程池(请求线程池)中的线程处理,在该线程中,我们获取到AsyncContext,然后将其交给异步处理线程池。
请求线程可以被归还回主线程池,这样主线程池用来处理 Http请求的线程没有被长时间占用。
但是需要注意的是,这种做法可以及时归还主线程池,但在仍旧占用另一个线程,所以 JVM的线程总数没有减少,系统瓶颈仍旧在 JVM进程的最大线程数上(单个线程的栈大小默认是-Xss1M

代码如下:

@WebServlet(value = "/threadPoolAsync", asyncSupported = true)
public class ThreadPoolAsyncHelloServlet extends HttpServlet {

private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
AsyncContext asyncContext = request.startAsync();
executor.execute(() -> {
new LongRunningProcess().run();
try {
asyncContext.getResponse().getWriter().write("Hello World!");
} catch (IOException e) {
e.printStackTrace();
}
asyncContext.complete();
});
}

}

Requet 和 Response的非阻塞IO

Servlet 3.0对请求的处理虽然是异步的,但是对InputStreamOutputStream的IO操作却依然是阻塞的,对于数据量大的请求体或者返回体,阻塞IO也将导致不必要的等待。因此在Servlet 3.1中引入了非阻塞IO(参考下图红框内容),通过在HttpServletRequestHttpServletResponse中分别添加ReadListenerWriterListener方式,只有在IO数据满足一定条件时(比如数据准备好时),才进行后续的操作。

对应的代码示例子:

@WebServlet(value = "/nonBlockingThreadPoolAsync", asyncSupported = true)
public class NonBlockingAsyncHelloServlet extends HttpServlet {

private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

AsyncContext asyncContext = request.startAsync();
ServletInputStream inputStream = request.getInputStream();
inputStream.setReadListener(new ReadListener() {
@Override
public void onDataAvailable() throws IOException {
}

@Override
public void onAllDataRead() throws IOException {
executor.execute(() -> {
new LongRunningProcess().run();

try {
asyncContext.getResponse().getWriter().write("Hello World!");
} catch (IOException e) {
e.printStackTrace();
}
asyncContext.complete();

});
}

@Override
public void onError(Throwable t) {
asyncContext.complete();
}
});
}

}

在上例中,我们为ServletInputStream添加了一个ReadListener,并在ReadListeneronAllDataRead()方法中完成了长时处理过程。

异常处理

servlet 在处理一个请求时可能抛出 ServletExceptionUnavailableException 异常。

  • ServletException 表示在处理请求时出现了一些错误,容器应该采取适当的措施清理掉这个请求。
  • UnavailableException 表示 servlet 目前无法处理请求,或者临时性的或者永久性的:
    • 如果 UnavailableException 表示的是一个永久性的不可用,servlet 容器必须从服务中移除这个 servlet,调用它的 destroy 方法,并释放servlet 实例。所有被容器拒绝的请求,都会返回一个 SC_NOT_FOUND (404) 响应。
    • 如果 UnavailableException 表示的是一个临时性的不可用,容器可以选择在临时不可用的这段时间内路由任何请求到 Servlet。所以在这段时间内被容器拒绝的请求,都会返回一个 SC_SERVICE_UNAVAILABLE (503) 响应状态码,且同时会返回一个 Retry-After 头指示此 servlet 什么时候可用。容器可以选择忽略永久性和临时性不可用的区别,并把UnavailableException 视为永久性的,从而 servlet 抛出UnavailableException 后需要把它从服务中移除。

Servlet API

参考:

Servlet

  1. Servlet[I]: 属于javax.servlet
    • init()
    • destroy()
    • service(ServletRequest, ServletResponse)
  2. HttpServlet: 属于javax.servlet.http
    • service(): 根据method调用: doHead(), doGet(), doPost()
  3. DispatcherServlet : 属于org.springframework.web.servlet
    • doService() : 调用了doDispatch()
    • doDispatch() : 从这里调用进@Controller中相关的方法

ServletConfig

对应web.xml的<servlet>, ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象
常用方法:

  • getServletName
  • getServletContext

ServletContext

对应web.xml的<context-param>, 容器中部署的每一个web应用都有一个ServletContext接口的实例对象与之关联
常用方法:

  • getInitParameter / getInitParameterNames
  • addFilter
  • addListener
  • addServlet

在任何Servlet实现类中可以使用this.getServletContext获取Context

Filter

  • init / destroy
  • doFilter
  • FilterChain

Response:

  1. ServletResponse[I]
    • getOutputStream() / getWriter()
    • flushBuffer()
  2. HttpServletResponse[I]
    • addCookie
    • setHeader
  3. HttpServletResponseWrapper

Request:

  1. ServletRequest[I]
    • getInputStream() / getReader()
    • getParameter / getAttribute
    • startAsync()
  2. HttpServletRequest[I]
    • getContextPath(), getServletPath(), getPathInfo()
    • getRequestURI() / getRequestURL()
  3. HttpServletRequestWrapper

IO Stream

  1. ServletInputStream:
    • readLine
  2. ServletOutputStream:
    • print / println

RequestDispatcher

RequestDispatcher对象由Servlet容器来创建, 封装一个由路径所标识的服务器资源.
在Servlet实现类中获取dispatcher对象: this.getServletContext().getRequestDispatcher("/api/v2/topic/load")

  1. 获取RequestDispatcher对象
    1. ServletRequest的getRequestDispatcher(String path)方法
    2. ServletContext的getNamedDispatcher(String path)getRequestDispatcher(String path)方法
  2. RequestDispatcher.forward(ServletRequest, ServletResponse) : 类似php里的inclde, 在返回页面中包括其他资源
  3. RequestDispatcher.include(ServletRequest, ServletResponse) : 转发
request.getRequestDispatcher("/2.html").include(request, response); // 在当前页面包含2.html
requset.getRequestDisPatcher("/servlet2").dispatcher.forward(request, response); // 转发到servlet2

JSP(JavaServer Pages)

Servlet & JSP 区别与联系

  • Servlet在Java代码中通过HttpServletResponse对象动态输出HTML内容
  • JSP在静态HTML内容中嵌入Java代码, Java代码被动态执行后生成HTML内容,
  • JSP的本质仍是Servlet, JSP编译之后生成的*.java文件和*.class里有什么?
  • Servlet是被Context的类加载器加载的, 所以重写Servlet需要重新部署Context, JSP有自己的加载器, JSP文件在修改之后不需要”重新加载”

语法

  • 代码段 <% ... %>
  • 声明: <%! ... >

    <%! int i = 0; %>
    <%! int a, b, c; %>
  • 表达式:

    <p>
    今天的日期是: <%= (new java.util.Date()).toLocaleString()%>
    </p>

动作元素

  • jsp:include : 在页面被请求的时候引入一个文件。
  • jsp:useBean : 寻找或者实例化一个JavaBean。
    <jsp:useBean id="myName" ... >
    <jsp:setProperty name="myName" property="someProperty" .../>
    </jsp:useBean>