Tomcat1 - Servlet容器,与 SpringMVC 的关系
参考:
PoetryAndTheDistance :一:Tomcat核心组件及应用架构详解
Hong EuiSung –@gowoonosori : 요청처리 내부구조
Http 请求响应实例
Servlet 接口
为了解耦 【http服务器代码】和【业务代码】,Java就定义了一个接口,各种业务类都必须实现这个接口,这个接口就叫 Servlet 接口,有时我们也把实现了 Servlet 接口的业务类叫作 Servlet。
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(); }
- 其中最重要是的 service 方法,具体业务类在这个方法里实现处理逻辑。这个方法有两个参数:ServletRequest 和 ServletResponse。
- ServletRequest 用来封装请求信息,ServletResponse 用来封装响应信息,因此本质上这两个类是对通信协议的封装。
- 比如 HTTP 协议中的请求和响应就是对应了 HttpServletRequest 和 HttpServletResponse 这两个类。你可以通过 HttpServletRequest 来获取所有请求相关的信息,包括请求路径、Cookie、HTTP 头、请求参数等。还可以通过 HttpServletRequest 来创建和获取 Session。而 HttpServletResponse 是用来封装 HTTP 响应的。
- 两个跟生命周期有关的方法 init 和 destroy
-
- Servlet 容器在加载 Servlet 类的时候会调用 init 方法,在卸载的时候会调用 destroy 方法。我们可能会在 init 方法里初始化一些资源,并在 destroy 方法里释放这些资源,
- 比如 Spring MVC 中的 DispatcherServlet,就是在 init 方法里创建了自己的 Servlet WebApplicationContext
- 还有 ServletConfig 这个类,ServletConfig 的作用就是封装 Servlet 的初始化参数。
-
- 你可以在 web.xml 给 Servlet 配置参数,并在程序里通过 getServletConfig 方法拿到这些参数。
有接口一般就有抽象类,抽象类用来实现接口和封装通用的逻辑。
(接口和抽象类都不能实例化,接口只能有方法的声明不能有实现,抽象类可以有方法的实现。接口用来抽象功能,抽象类用来抽象类型)
因此 Servlet 规范提供了 GenericServlet 抽象类,我们可以通过扩展它来实现 Servlet。虽然 Servlet 规范并不在乎通信协议是什么,但是大多数的 Servlet 都是在 HTTP 环境中处理的,因此 Servet 规范还提供了 HttpServlet 来继承 GenericServlet,并且加入了 HTTP 特性。这样我们通过继承 HttpServlet 类来实现自己的 Servlet,只需要重写两个方法:doGet 和 doPost。
HttpServlet 实现的 Servlet 声明的业务方法接口 service 方法里会调用 doGet 和 doPost
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); long lastModified; if (method.equals("GET")) { lastModified = this.getLastModified(req); if (lastModified == -1L) { this.doGet(req, resp); } else { .......省略 } } else if (method.equals("HEAD")) { lastModified = this.getLastModified(req); this.maybeSetLastModified(resp, lastModified); this.doHead(req, resp); } else if (method.equals("POST")) { this.doPost(req, resp); } else if (method.equals("PUT")) { this.doPut(req, resp); } else if (method.equals("DELETE")) { this.doDelete(req, resp); } else if (method.equals("OPTIONS")) { this.doOptions(req, resp); } else if (method.equals("TRACE")) { this.doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[]{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } }
Servlet 容器
当客户请求某个资源时:
- HTTP 服务器会用一个 ServletRequest 对象把客户的请求信息封装起来,然后调用 Servlet 容器的 service 方法
- Servlet 容器拿到请求后,根据请求的 URL 和 Servlet 的映射关系,找到相应的 Servlet,如果 Servlet 还没有被加载,就用反射机制创建这个 Servlet,并调用 Servlet 的 init 方法来完成初始化,接着调用 Servlet 的 service 方法来处理请求,把 ServletResponse 对象返回给 HTTP 服务器
- HTTP 服务器会把响应发送给客户端
扩展机制
Servlet 规范提供了两种扩展机制:Filter 和 Listener。
Filter
允许你对请求和响应做一些统一的定制化处理。过滤器的工作原理是这样的:Web 应用部署完成后,Servlet 容器需要实例化 Filter 并把 Filter 链接成一个 FilterChain。当请求进来时,获取第一个 Filter 并调用 doFilter 方法,doFilter 方法负责调用这个 FilterChain 中的下一个 Filter。
比如你可以根据请求的频率来限制访问,或者根据国家地区的不同来修改响应内容。还有 SpringSecurity 的 SessionRepositoryFilter 就 implements 了 Filter,用来校验请求头中的 session
Listener
当 Web 应用在 Servlet 容器中运行时,Servlet 容器内部会不断的发生各种事件,如 Web 应用的启动和停止、用户请求到达等。 Servlet 容器提供了一些默认的监听器来监听这些事件,当事件发生时,Servlet 容器会负责调用监听器的方法。当然,你可以定义自己的监听器去监听你感兴趣的事件,将监听器配置在web.xml中。
比如 Spring 就实现了自己的监听器,来监听 ServletContext 的启动事件,目的是当 Servlet 容器启动时,创建并初始化全局的 Spring 容器。
Servlet容器 与 SpringMVC 的关系
在 SpringMVC 中,有一个名为 DispatcherServlet 的 Servlet,它充当 FrontController,转发、匹配、将请求委托给其他控制器来处理请求。
执行service(),并在方法中执行 dispatch.doService() -> doDispatch(),doDispatch 负责 从 HandlerMapping 中获取handler
Root WebApplicationContext
- 就是 org.springframework.context.ApplicationContext
- 就是 Spring 的 IOC 容器,包括 Service、datasource、repositories 等 bean
- 通过 Servlet 容器的 Listener 机制,Spring 的 ContextLoaderListener 类实现 ServletContextListener,监听 Servlet 容器的启动事件,创建并初始化这个 spring 全局上下文。
- 管理bean的生命周期并提供 IOC/DI
- Root WebApplicationContext 不能访问 Servlet WebApplicationContext 中的 bean(Controller 里可以访问 Service 对象,Service 对象不可以访问 Controller 对象)
Servlet WebApplicationContext
- 就是 org.springframework.web.context.WebApplicationContext
- 与标准 javax.servlet.ServletContext 配合使用。ServletContext 实例可以做很多事情,例如通过调用 getResourceAsStream() 方法访问 WEB-INF 资源(xml 配置等)。
- 继承自 RootWebApplicationContext 实现的 Context,主要包含 Controller、Intercepter、ViewResolver、HandlerMapping 等bean。
- 之所以有这样的继承关系,是因为一个Servlet容器内部可能会出现多个Servlet,它们要共用 Root WebApplicationContext 中的服务和数据源。
- 延迟加载,当第一个请求到来,Tomcat 发现 DispatcherServlet 还没被实例化,就会调用它的 init 方法,建立容器初始化相关 bean
- 如果一个servlet 上下文的 bean注册了与Application Context和context相同的ID,则使用Servlet Context中声明的bean,查找 bean 时按照 Servlet Context -> Application Context的顺序查找。
public interface WebApplicationContext extends ApplicationContext { ServletContext getServletContext(); }
从服务器启动到请求的步骤
- Web 服务器初始化
- 监听到 Servlet 容器启动。加载 Root WebApplicationContext
- 网络服务器启动
- 客户端第一次向 Web 服务器发出请求
- Web Server 交付给 Servlet 容器
- 创建 Servlet线程
- 如果 DispatcherServlet 没有创建,DispatcherServlet init 初始化 Servlet WebApplicationContext
- 在生成的线程中调用 DisapatcherServlet 的 service() 方法。
- 通过 HandlerMapping 查找 Controller
- 通过 HandlerAdapter 传递给 Controller
- Controller->Service->Response
Filter 与 Intercepter
Filter 是 Servlet 规范的一部分,Servlet 容器 Tomcat 实现了它;Intercepter 是 Spring 实现的。它们的执行顺序是
- Filter.doFilter();
- HandlerInterceptor.preHandle();
- Controller
- HandlerInterceptor.postHandle();
- HandlerInterceptor.afterCompletion();
- Filter.doFilter()
- Servlet 方法返回