欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

springmvc源码分析原理及简单实现

程序员文章站 2024-02-29 21:21:10
...

现在我主要分四步走。

一、我们先来看下springmvc的配置与启动流程。

springmvc源码分析原理及简单实现

这是spring与springmvc框架的web应用的web.xml简单配置。
虽然这篇博客讲的是springmvc但是,它的启动流程还是离不开Spring的,所以我先简单提下Spring的启动流程,后期会写另一篇博客详细的说一说Spring的,请大家见谅。

首先我们看到在这个web.xml中配置了<listener>标签,里面配置了一个监听器类ContextLoaderListener。
springmvc源码分析原理及简单实现

我们可以看到这个类实现了ServletContextListener这个监听器接口 ,这个接口是web容器自带的,它是用于监听容器的启动与销毁的,并给实现了这个接口的监听器发送通知。
所以web容器启动的时候会先触发这个ContextLoaderListener监听器,这个监听器会执行它的 initWebApplicationContext的方法,方法名称即是其含义。方法中首先创建了WebApplicationContext,配置并且刷新实例化整个SpringApplicationContext中的Bean。因此,如果我们的Bean配置出错的话,在容器启动的时候,会抛异常出来的。
springmvc源码分析原理及简单实现
然后会把创建的好的WebApplicationContext放到ServletContext容器中,以key为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 存储这个WebApplication 到servlet容器中。并在后续springMVC的初始化中会用到。

二、下面我们开始来讲一讲SpringMVC部分的初始化。

从配置的web.xml中我们可以看到整个web应用就配置了一个Servlet,也就是DispatcherServlet。
springmvc源码分析原理及简单实现
我们可以看到 DispatcherServlet这个类继承了这么多的抽象类,其实是用了一种模板方法的设计模式, 公共部分的统一自己实现,变化的部分抽象,交给子类去实现。我就随便举个HttpServletBean类的例子,大家看一下。
springmvc源码分析原理及简单实现
这里HttpServletBean重写了init()方法,使的Servlet不再关心init-param部分的赋值,让servlet更关注于自身Bean初始化的实现,并实现SpringMVC自己的容器。
这里我们需要注意:
在Spring的配置文件中root-context.xml 我们扫描注解的时候不需要扫描@Controller注解,需要用exclude把这个注解排除在外,不让Spring去扫描它。
但是我们在SpringMVC的配置文件servlet-context.xml中扫描包的时候是需要声明扫描Controller注解的,需要用到 include,例如:
  1. <context:component-scan base-package="com.nn.web.controller"  
  2.         use-default-filters="false">  
  3.         <context:include-filter type="annotation"  
  4.             expression="org.springframework.stereotype.Controller" />  
  5.     </context:component-scan>
这个  use-default-filters = "false"最好添加,默认为true,因为 springmvc 配置文件中不添加use-default-filters = "false"的话,spring会扫描的包下的@repository,@service的bean,可能会导致一些问题。
这个尤其在springmvc+spring+hibernate等集成时最容易出问题的,最典型的错误就是:事务不起作用。
 
现在我们来说说DispatcherServlet继承的另外一个抽象类 FrameworkServlet,它主要的作用是把Spring的ApplicationContext上下文设置成SpringMVC上下文的parent。我在网上找到一张图,大家可以看一看。
springmvc源码分析原理及简单实现

三、DispatchServlet的工作流程

     因为它是个Servlet ,所以它肯定会有doService()方法,我们先从doService()开始看起。
springmvc源码分析原理及简单实现


主要是做了两件事,一个是放很多属性或者全局变量到request中,第二个调用doDispatch()。
我们现在简单说下doDispatch 做了什么事。
页面来了请求,通过request和URL来遍历 HandlerMapping 找到对应的handler ,并给这个handler加上拦截器,形成一个处理链对象 HandlerExecutionChain。
再通过handler得到对应类型的handlerAdapter适配器(通过遍历),handlerAdapter调用handler,也就是去执行对应controller方法,返回一个 ModelAndView 给handlerAdapter适配器。然后Dispatch执行视图解析器。进行数据的解析和渲染视图。
我在网上找了两张图,大家可以看看。
一个是时序图
springmvc源码分析原理及简单实现
一个是流转图
springmvc源码分析原理及简单实现

这样大家应该清楚了。

四、下面我们来简单的自己实现SpringMVC的功能


     这个是web.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   version="3.0">
   <servlet>
      <servlet-name>MySpringMVC</servlet-name>
      <servlet-class>com.vicoqi.servlet.MyDispatcherServlet</servlet-class>
      <init-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>application.properties</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>MySpringMVC</servlet-name>
      <url-pattern>/*</url-pattern>
   </servlet-mapping>

</web-app>
这个是MyDispatcherServlet中的 init 方法。
@Override
public void init(ServletConfig config) throws ServletException {
   
   //1.加载配置文件
   doLoadConfig(config.getInitParameter("contextConfigLocation"));
   
   //2.初始化所有相关联的类,扫描用户设定的包下面所有的类
   doScanner(properties.getProperty("scanPackage"));
   
   //3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v  beanName-bean) beanName默认是首字母小写
   doInstance();
   
   //4.初始化HandlerMapping(将url和method对应上)
   initHandlerMapping();
   
   
}
这个是doDispatch 方法
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
   if(handlerMapping.isEmpty()){
      return;
   }
   
   String url =req.getRequestURI();
   String contextPath = req.getContextPath();
   
   //拼接url并把多个/替换成一个
   url=url.replace(contextPath, "").replaceAll("/+", "/");
   
   if(!this.handlerMapping.containsKey(url)){
      resp.getWriter().write("404 NOT FOUND!");
      return;
   }
   
   Method method =this.handlerMapping.get(url);
   
   //获取方法的参数列表
   Class<?>[] parameterTypes = method.getParameterTypes();

   //获取请求的参数
   Map<String, String[]> parameterMap = req.getParameterMap();
   
   //保存参数值
   Object [] paramValues= new Object[parameterTypes.length];
   
   //方法的参数列表
       for (int i = 0; i<parameterTypes.length; i++){ 
           //根据参数名称,做某些处理 
            String requestParam = parameterTypes[i].getSimpleName(); 
           
           
           if (requestParam.equals("HttpServletRequest")){ 
               //参数类型已明确,这边强转类型 
                paramValues[i]=req;
               continue; 
           } 
           if (requestParam.equals("HttpServletResponse")){ 
               paramValues[i]=resp;
               continue; 
           }
           if(requestParam.equals("String")){
               for (Entry<String, String[]> param : parameterMap.entrySet()) {
                 String value =Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
                 paramValues[i]=value;
              }
           }
       } 
   //利用反射机制来调用
   try {
      method.invoke(this.controllerMap.get(url), paramValues);//obj是method所对应的实例 在ioc容器中
   } catch (Exception e) {
      e.printStackTrace();
   }
}
项目很多不严谨,但是只给提供一下思路,请大家见谅了。但是注释写的都很详细。希望可以帮助大家
我可以给大家我github地址,大家去下和运行,试着体会。这个项目是没有问题的我运行的是好的。如果大家认为可以,请给一下博客赞和github上star,谢谢。