image.png

DispatcherServlet的实现层级

GenericServlet及其底层

ServletContext

Servlet接口提供Servlet本体的基础语义(只运行一次的init,创建后可执行的service,相应的destory方法),ServletConfig以经典的key值枚举+键值对的方式提供了关于Servlet的抽象配置信息。
值得注意的是ServletConfig接口中规定了要有ServletContext的获取途径。Spring源码中对org.apache.catalina.Context和前图中的javax.servlet.ServletContext接口的解释,大体可以归为:在单机系统范畴下,每JVM每web应用拥有一个context,它是若干Servlet的抽象容器,服务端拥有这一抽象体并管理Servlet。如果把Servlet集合比作奶牛,那么context相当于牛棚+管理员。
回到ServletContext的定义,我们可以发现这个上下文实际上包含了管控Servlet生命周期流程以及其各种功能的信息,同时也定义了其包含web应用的全局信息,主要内容有

  • 关于web应用的必要全局信息,如资源路径与查询方法,MIME和资源编码类型与;同时能为Context本身提供KV形式的全局属性。
  • 管控Servlet的属性与方法:能够在Context中注册Servlet并查询Servlet注册信息;提供为Servlet以及JSP、HTML间分发(Dispatch)服务的分发器;
  • 管控Servlet的多种功能:Session管理相关方法,如超时时间,追踪模式等配置信息;能够为Context提供监听器,为Servlet提供过滤器
  • 有Servlet层面的Aware:能够根据路径获取其他web应用的Context

有了ServletContext的存在,Servlet集合就可以aware到自己所处的上下文并利用或修改这些信息。

其实从前往后看,既然Servlet的含义就是提供协议无关的服务,像session这种显然与http深度绑定的东西似乎并不适合在这么低的抽象层加入,个人觉得,在HttpServlet中通过接口Mixin入与http相关的上下文与配信息似乎更合理;但话说回来tomcat本身的定位就是web服务器,在最底层的Context声明这些内容似乎便于交互,所以Servlet本身所谓协议独立的作用要打个问号。

(待补全)ServletRequest和ServletResponce

在最基本的抽象层(Servlet)上的service的实际“出入参”。显然它是由部署所在的tomcat提供的

Servlet本体的行为定义

前后端分离背景下的Servlet

Generic封装了什么

这个抽象类其实是Servlet和ServletConfig的不复杂的组合,提供了基本层面上的、协议无关的Servlet抽象,它定义的方法基本上就是把Servlet的行为和ServletConfig里、aware到的ServletContext里的可用资源与信息糅合起来。它提供了行为(Servlet生命周期)+数据/配置的集合体。

HttpServlet

这一层是本问讨论内容中JavaEE架构下的最后一层封装,它声明了处理Http请求的Servlet的基本行为。
该类没有抽象方法却除了处理日志几乎个个是“抽象方法”,它主要做了以下几件事:

  1. 由于是针对http协议的Servlet,它将Servlet的req和res转换成Http的,并声明了http版本的service。这个声明规定了根据http头内容分发到doGet、doPost等方法,除非有扩展需求否则它基本定义了处理HTTP请求的Servlet的行为。
  2. 基于HTTP类型分别定义了doGet,doPost,doDelete等方法,他们对应着对相应类型http的处理,在该类中仅仅定义了默认的“不允许请求”。
  3. 定义了用于上述未允许行为对应的NO-OP组件,看似啥也没干,其实是在教你怎么干,这波作者在大气层。

总的来说,当单独JavaEE部署在Tomcat上时,Servlet的作者期望开发者的Servlet继承HttpServlet并扩展对应处理http请求的方法,处理输入输出。但作为旧时代的残党,Servlet(乃至Tomcat本身)都已经被深深嵌入进Spring(boot)这个革命家身体里。

HttpServletBean

从代码层面可以看出,HttpServletBean是对HttpServlet的简单封装(仅重写了init方法),却实现了对spring的基本支持:将Servlet作为Bean看待,把Servlet本身的一些参数配置视作bean properties,并为之设计了一系列routine。
HttpServletBean中实现的init函数如下,解析见注释:

  1. @Override
  2. public final void init() throws ServletException {// 用了final表示这就是Servlet以bean形式被初始化的范式
  3. // Set bean properties from init parameters.
  4. // 查源码,就是把init参数和值解析到ServletConfigPropertyValues中去,并查看这些参数是否全部覆盖了requiredProperties,否则报错
  5. // 用只读的KV集合:PropertyValues来接这个对象
  6. PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
  7. if (!pvs.isEmpty()) {
  8. try {
  9. // BeanWrapper是bean的实体的包裹,本质上是PropertyAccesor,也即可以访问的属性集合,
  10. BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
  11. // 这里利用ServletContext获取了能读取其ClassLoader和数据源的ResourceLoader
  12. ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
  13. // 将获取到的资源绑定到BeanWrapper上,方法是为指定的Resource创建ResourceEditor
  14. bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
  15. // 这个函数交给子类重写
  16. initBeanWrapper(bw);
  17. // 很复杂,不过目的就是通过一系列操作,把PV的属性集合赋值到BeanWrapper中
  18. bw.setPropertyValues(pvs, true);
  19. }
  20. catch (BeansException ex) {
  21. if (logger.isErrorEnabled()) {
  22. logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
  23. }
  24. throw ex;
  25. }
  26. }
  27. // 同样是让子类实现
  28. initServletBean();
  29. }

可以看到,这个init函数整体上做了一件事:把的一些配置和属性(主要包括init-param和一些其他信息)提取成KV对,进而把这些KV对绑定到Bean上,正式让Servlet变成了一个JavaBean。同时,它也暴露了两个供子类重写的函数,从而能够customize这个转换过程
另外,HttpServletBean实现了EnvironmentAware接口,关于spring中Environment的概念见:这里

FrameworkServlet

FrameworkServlet是将Servlet集成到SpringMVC的基类,它实现了ApplicationContextAware,这也是它的功能之一:将自身嵌入Spring容器,并且aware到自身所在的ApplicationContext,spring文档对其如下描述:

  • Manages a WebApplicationContext instance per servlet. The servlet’s configuration is determined by beans in the servlet’s namespace.(每个Servlet对应一个WebApplicationContext,并管理之)
  • Publishes events on request processing, whether or not a request is successfully handled.