Spring系列之手写一个SpringMVC

目录

引言

在前面的几个章节中咱们已经简单的完成了一个简易版的spring,已经包括容器,依赖注入,AOP和配置文件解析等功能。这一节咱们来实现一个本身的springMvc。html

关于MVC/SpringMVC

springMvc是一个基于mvc模式的web框架,SpringMVC框架是一种提供了MVC(模型 - 视图 - 控制器)架构和用于开发灵活和松散耦合的Web应用程序的组件。git

MVC模式使得应用程序的不一样部分分离,同时提供这些元素之间的松散耦合。github

  • 模型(Model)封装了应用程序数据,一般是指普通的bean。
  • 视图(View)负责渲染模型数据,通常来讲它生成客户端浏览器能够解释HTML输出。
  • 控制器(Controller)负责处理用户请求并获取请求结果,将其传递给视图进行渲染。

SpringMVC

SpringMVC处理请求流程

首先咱们来了解一下SpringMVC在处理http请求的整个流程中都在作些什么事。web

SpringMVC请求处理流程

//图片来源于网络spring

从上图中咱们能够总结springmvc的处理流程:json

  1. 客户端发送请求,被web容器(tomcat等)拦截到,web容器将请求交给DispatcherServlet。
  2. DispatcherServlet收到请求后将请求交给HandlerMapping(处理器映射器)查找该请求对应的Handler(处理器)。实际上这个过程在图上是分为两个的,这是由于一个请求url可能会有多个请求处理器,好比GET请求,POST请求等就是不一样的处理器对象来处理的,因此须要一个HandlerAdapter(处理器适配器)来根据不一样的请求参数来获取对应的处理器对象。
  3. 获取到请求的处理器对象后,执行处理器的请求处理流程。这里的处理流程通常是值咱们在开发中定义的业务流程。
  4. 处理流程执行完毕后将返回的结果包装为一个ModelAndView对象返回给DispatcherServlet。
  5. DispatcherServlet经过ViewResolver(视图解析器)将ModelAndView解析为View。
  6. 经过View渲染页面,响应给用户。

上面就是一个http请求从开始带完成响应中由SpringMVC完成的流程,咱们的SpringMVC没有实际的那么复杂,不过相应的功能都会进行实现。后端

SpringMVC分析

咱们知道SpringMVC是实现了MVC模式的一个web框架,因此确定有ModelViewController三种角色。从上图中咱们还能够看到一个十分重要的类:DispatcherServlet。接下来咱们单独来分析。设计模式

Controller

控制器(Controller)负责处理用户请求并获取请求结果,将其传递给视图进行渲染。浏览器

在SpringMVC中Controller负责来处理由DispatcherServlet分发来的请求,并将进过业务处理后的请求结果包装成一个Model提供给View使用。在SpringMVC中将请求映射到对应的Controller上是给咱们提供了两种不一样的方法:tomcat

  1. 实例级别的映射,每个请求都有一个对应的类实例来处理,相似于Struts2。这种方法实际不多使用。要实现这种类型须要实现一个Controller接口。
  2. 方法级别的映射,请求映射到bean的方法上,这样每个Controller能够对应多个请求,同时也能更易于保证并发请求的线程安全。
实例级别的映射

考虑如何实现实例级别的映射?

在实例级别映射中每个请求对应一个不一样的类,即一个URL<==>一个Class,这样咱们能够将beanName和请求地址进行对应。

同时咱们框架须要提供一个请求处理的入口供使用者实现业务代码。这里咱们定义一个Controller接口,接口中提供一个处理方法。

Controller

接口中包含一个handlerRequest的处理方法,全部实例级别的Controller都须要Controller接口。在handlerRequest方法中完成业务逻辑。由于目前咱们还没法判断业务逻辑完成后须要返回那种类型的值,因此用Object代替。

这里要肯定方法应该返回什么,咱们首先得明白返回值用来干吗的?这个返回的值包含了业务处理的结果。而且返回给页面用于页面渲染。因此确定是持有一个结果值和须要返回的具体的页面。考虑到返回的值不只仅是业务处理的结果,可能用户须要设置一些其余的值给页面,咱们定义一个map类型来接收。

ModelAndView

添加hasView()方法是由于咱们返回的并非必需要有页面的信息,好比返回json值。

view的工做很是简单,就是讲咱们返回的值响应给浏览器。因此view的接口是这样的:

View

方法级别的映射

用过SpringMVC的都很清楚,上面那种实例级别映射的方式基本上都不会使用。实例级别的映射一旦项目中的请求多了将会致使项目中的类特别的多。咱们平时用的多的是方法级别的映射。

咱们这里方法级别的映射不须要实现Controller接口,这里咱们仿造SpringMVC使用@Controller来表示一个控制器,使用@RequestMapping来表示不一样的请求。

annotation

RequestMethod是指http请求类型,包括GEt,POST,PUT等类型,一个枚举类型。

方法映射的处理除了映射到方法上外其余和实例映射相似。

请求分发

客户端发送请求,后端接收到请求后,须要将请求分发到对应的处理器上,从宏观角度说就是请求交给DispatcherServlet,而后由DispatcherServlet分发给不一样的处理器。咱们很明显须要知道请求是如何分发处处理器上的。

请求分发

不一样类型的映射对应的请求处理器确定是不同的,好比对于实例映射是经过实现Controller接口,处理也是关于接口的,而方法映射是经过注解实现。即不一样的方式,映射方式不一样,请求处理器也不同。

简单的方法就是分别定义处理不一样类型的处理器,而后在DispatcherServlet中经过判断肯定具体的处理器。这样处理思路很简单,可是问题在于假设咱们要再添加一种处理的方式,那么就须要改变原有的代码,很明显的违反了开闭原则,也会给代码维护带来麻烦。因此咱们但愿有一种DispatcherServlet能避开这种改变的方式。

咱们这里须要一个可以根据传递进来的不一样的请求来调用不一样的处理器,并且可以简单的进行扩展而不改动原代码。这里确定就须要用到设计模式了,那么使用什么设计模式呢? 策略模式

HandlerMapping

咱们定义一个用于请求处理器映射的接口,该接口的做用就是获取一个请求具体的请求处理器,不一样方式的处理方式分别实现该接口。

handlerMapping

BeanNameUrlHandlerMapping就是实例映射的处理器映射器,RequestMappingHandlerMapping就是方法映射的处理器。而如何将请求和HandlerMapping对应起来呢?咱们能想到的就是url了,这里咱们定义一个urlMaps用来存储url和处理器映射器的对应关系。

HandlerAdapter

如今咱们有了HandlerMapping后就能够获取到某一种类型的处理方式的处理对象。可是实际上咱们仍是没有获取到实际的处理器,因此咱们还须要根据请求来获取到实际的处理器,这里咱们定义一个HandlerAdapter来获取实际的处理器。

handlerAdapter

handler(...)就是具体的处理方法,实际上就是执行控制器,而support主要是用于判断是不是一个处理器对象。

这里很明显对于实例映射来讲咱们只须要执行方法中的handlerRequest(...)方法便可,可是对于方法映射就不是那么简单了,不一样的方法根据@RequestMapping表示不一样的请求。因此咱们还须要一个类来表示不一样的方法信息,便于请求传递过来后直接取用。

requestMappinginfo

类的定义中classRequestMapping表示做用在类上的@RequestMappingmethodRequestMapping表示做用在方法上的@RequestMappingmethod表示做用在类上的方法信息。match(...)方法是用来检测当前请求与这个RequestMappingInfo是否相匹配。

扫描注册

基本上咱们的准备工做完成了,如今咱们须要考虑如何来识别咱们的控制器和生成RequestMappingInfo的信息。

首先建立的时机确定是在项目启动的时候就讲这些信息初始化好,由于请求过来后会马上使用到这些信息。而初始化这些信息的行为在哪里发生呢?个人第一反应是交给DispatcherServlet,由于直观来说是它来使用,实际上真正的使用这些信息的事HandlerMapping的实现类,经过请求和RequestMappingInfo等信息来获取实际的处理器,因此初始化的信息应该交给HandlerMapping的实现类。

咱们要明白的事提取带有Controller注解的bean或者是实现类Controller接口的类确定是在bean初始化以后进行的,因此咱们须要提供一个在初始化后获取控制器类型的接口。同时获取已经初始化好的类那么确定会使用到ApplicationContext。咱们如今对RequestMapping接口修改下。

image

afterPropertiesSet()方法中获取控制器类型的bean。在咱们以前完成的代码中只提供了经过beanName获取bean的方法,因此这里咱们还须要提供一种获取全部的执行类型的方法。

public void afterPropertiesSet() {
    String[] beanNameForType = applicationContext.getBeanNameForType(Object.class);
    for(String beanName:beanNameForType){
        Class type = applicationContext.getType(beanName);
        //判断是不是控制器类型
        if (isHandler(type)) {
            //注册控制器的类型
            detectHandlerMethod(type);
        }
    }
}
复制代码

DispatcherServlet

好了,到如今对于控制器的准备已经差很少了,如今咱们须要来实现DispatcherServlet了。

DispatcherServlet名字来看就知道这是一个Servlet,咱们的框架是基于Servlet来完成的,SpringMVC框架自己也是基于Servlet的。固然也能够根据其余技术来实现,好比基于Filter的Struts2。

咱们先来捋一下DispatcherServlet须要完成的任务吧:

  1. 建立ApplicationContext容器对象
  2. 从容器中获取HandlerMappingHandlerAdapter对象。
  3. 分发请求
  4. view转发

熟悉Servlet的都应该知道Servlet提供了一系列生命周期的API,上面的这些事情都须要在Servlet生命周期的不一样阶段来完成。

  • 容器对象的初始化和获取HandlerMappingHandlerAdapter对象在init(...)完成。
  • 请求分发由service(HttpServletRequest req, HttpServletResponse res)完成。
  • destroy()完成关闭后的处理。

DispatcherServlet

View

在以前咱们定义控制器的时候有说到由控制器来返回一个ModelAndView对象,该对象肯定具体返回哪个页面和处理结果的数据。这样不只须要提供ModelAndView对象,同时还须要提供一个View的对象,如今咱们但愿这个过程可以尽可能的简单,使用者能够仅仅提供一个视图的名称,而后框架就能够自动的找到对应的页面而后进行渲染。

咱们如今须要从新定义ModelAndViewView类。

ModelAndView

这样用户能够传递一个名称过来,而后由HandlerAdapter根据传递的handler来生成ModelAndView,同时也能够自定义ModelAndView对象。

ViewResolver

当咱们前面的准备工做都作好了并不表明就已经能够完成了,由于对于不一样的视图可能会有不一样的操做,好比直接转发给一个URL,可能还会重定向到另外一个URL,或者直接就是返回json串的。因此咱们还须要定义不一样的视图解析器来将ModelAndView解析成相应的View。

ViewResolver

这里定义了一个解析JSP视图的解析器,同理也能够定义其余的处理器。

定义好了视图解析器后咱们还须要定义几个用于处理不一样状况的视图类。

View

这里的View类型还能够根据不一样的需求添加其余类型的处理器,好比freemarker、JSTL等。对于json处理咱们还须要像SpringMVC那样来定义一个@ResponseBody的注解。当使用了该注解的时候咱们就将返回值转换为JSON串而后直接经过response返回给客户端便可。

小结

SpringMVC到这里就基本结束了,总得来讲这一篇的内容稍微比较麻烦。主要是涉及到的内容较多,再加上这段时间比较忙,平时就下班后抽时间整理,目前也只是将思路基本捋完。代码也只是整理了一个框架,内容尚未进行填充。因此文章中可能会有一些错误的地方,你们若是发现了能够指出来。后面有时间会将代码实现的。代码都在这里:Spring

总结

Spring的手写框架差很少就是这些了,写这一系列文章是为了巩固个人Spring学习的成果,固然若是能帮助你们学习Spring固然是更好了。Spring的内容十分的繁杂,涉及的内容多,这一系列文章只能帮助你们对Spring的原理有一个最初的了解,在看Spring源码的过程当中不至于彻底就是一头雾水。由于技术水平的缘由,文章中可能还存在着一些错误,欢迎你们指出。