Spring MVC深入源码之一HttpServletBean

最近想写一些系列文章来深入探讨理解下Spring MVC的运行方式及源码实现。好了,废话不多说了,直接进入正题。

大家都知道一个传统的Web Application都是从web.xml开始的,这个文件也是Application容器加载项目时第一个读取的。对于Spring MVC来说,我们需要在web.xml中配置一个DispatcherServlet作为前端控制器并为其指定一些初始参数。从类名就可以看出,DispatcherServlet本质上还是一个Servlet,只不过多了一些和Spring MVC框架有关的一些功能。

今天打算在这里讲的并不是DispatcherServlet,而是它父类的父类HttpServletBean,下图是一个描述了DispatcherServlet继承结构的图,图中只展示了Spring提供的三个类,其实在往上就是servlet-api中的HttpServlet, GenericServlet等。

Spring MVC深入源码之一HttpServletBean_第1张图片

好了,开始上源码,先从类声明开始。

public abstract class HttpServletBean extends HttpServlet      
implements EnvironmentCapable, EnvironmentAware {

可以看到HttpServletBean继承了HttpServlet,所以它只是在功能上对HttpServlet进行了一些扩展。并且它实现了EnvironmentCapable和EnvironmentAware接口。

  • EnviromentCapable接口里定义,所有实现它的类必须能返回一个Environment的实例。
  • EnvironmentAware接口里定义,所有实现它的类必须提供一个setEnvironment的方法以便于set一个现有的Environment实例。

那看到这里,相信大家不禁要问,这个Enviroment又是一个什么东西,它起到了什么样的作用?在这里我不准备展开说,简单来说Environmenti 里封装了3样东西,Spring MVC的profile配置信息,System Properties的一个Map以及System Environment的一个Map。

接下去看一下这个类中的成员变量声明,不多就3个。

protected final Log logger = LogFactory.getLog(getClass());
private final Set requiredProperties = new HashSet();
private ConfigurableEnvironment environment;
  • Log不需要多说了吧,用来记录日志的。
  • 一个名为requiredProperties的HashSet可以用来指定那些你认为必须存在的servlet参数,可以通过下面这个方法添加如果有的话。
protected final void addRequiredProperty(String property) {  
     this.requiredProperties.add(property);
}
  • 一个类型为ConfigurableEnvironment的变量去存放Environment的实例。
@Override
public void setEnvironment(Environment environment) {
     this.environment = (ConfigurableEnvironment) environment;
}

然后我们一起来看一下比较关键的一个方法init,这个方法覆盖了它祖先类GenericServlet中定义的空实现,并且这个方法会在容器初始化每个Servlet的时候被调用一次。


Spring MVC深入源码之一HttpServletBean_第2张图片
  • 123行实例化了一个ServletConfigPropertyValues的对象,PropertyValues类其实是一个容器类,它内部会维护一个PropertyValue (注意此处没有s) 的数组,而PropertyValue是一个专门存放属性/参数的结构,它可以存储一个String类型的属性名以及一个Object类型的属性对象。
public class PropertyValue extends BeanMetadataAttributeAccessor implements Serializable {   
private final String name;   
private final Object value;
....

那么ServletConfigPropertyValues其实就是一个存放和Servlet相关的参数的容器类,比如那些定义在web.xml中的initParam。

  • 124行使用了一个PropertyAccessorFactory类里的方法,并且返回了一个BeanWrapper, 简单来说BeanWrapper就是一个对象的包裹者,它里面会存放被包裹对象的引用并且提供一系列利用了反射机制的方法去设置被包裹者的属性,大家可以看到在这行代码里传入了一个this引用,这里的this指的就是当前这个类的实例,而实际中这个其实是一个DispatcherServlet的实例,因为我们在web.xml里使用的是DispatcherServlet而不是直接去使用HttpServletBean。

  • 125行new了一个ServletContextResourceLoader的实例,也说一下ResourceLoader吧,这个接口定义了一些获取Resource的方法,你可以用它来获取各种各样的资源,比如配置文件,class文件等。那ServletContextResourceLoader的实现其实是提供了获取那些和当前Servlet相关的一些Resource,它会通过ServletContext中的一些方法去获取资源,比如servletContext.getResourceAsStream(), servletContext.getResource()

  • 127行调用了一个initBeanWrapper的方法,这个方法是一个空实现,可以在子类中扩展如果有需要的话。

protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
}
  • 128行调用了BeanWrapper上的setPropertyValues方法,并且把前面提到的ServletConfigPropertyValues作为参数传了进去。这句代码的作用就是把ServletConfigPropertyValues中的那些Servlet初始化参数设置到BeanWrapper所包裹的实例中的相应成员变量里。(有点拗口。。)。
    举个例子,前面已经提到BeanWrapper里被包裹对象其实是一个DispatcherServlet,我们平时在web.xml中定义这个Servlet的时候一般都会有一些initParam, 比如
   
    contextConfigLocation    
    classpath*: *-dispatcher-servlet.xml

那这个参数的名字和值就会作为一个PropertyVaule存放在ServletConfigPropertyValues中,在128行被调用的时候这个参数的值就会被set到被包裹者实例的相应的属性中, 在这里就是DispatcherServlet的contextConfigLocation中。

public void setContextConfigLocation(String contextConfigLocation) {   
    this.contextConfigLocation = contextConfigLocation;
}
  • 136行调用了一个initServletBean的方法,这个方法是一个抽象方法,真正的实现是在子类FrameworkServlet里。

结语:
从源码可以看出,HttpServletBean是在HttpServlet的基础上提供了把Servlet配置中相关的一些属性,参数设置到成员变量上这样的一个功能,这么做的好处就是你可以很方便的通过getter/setter方法去获取参数值,而不是通过一个通用的Map去获取。

在下一篇将会深入分析下HttpServletBean的一个子类FrameworkServlet。

你可能感兴趣的:(Spring MVC深入源码之一HttpServletBean)