您的位置:首页 > 编程语言 > Java开发

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

2018-04-03 15:31 651 查看

现在我主要分四步走。

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



这是spring与springmvc框架的web应用的web.xml简单配置。虽然这篇博客讲的是springmvc但是,它的启动流程还是离不开Spring的,所以我先简单提下Spring的启动流程,后期会写另一篇博客详细的说一说Spring的,请大家见谅。
首先我们看到在这个web.xml中配置了<listener>标签,里面配置了一个监听器类ContextLoaderListener。


我们可以看到这个类实现了ServletContextListener这个监听器接口 ,这个接口是web容器自带的,它是用于监听容器的启动与销毁的,并给实现了这个接口的监听器发送通知。所以web容器启动的时候会先触发这个ContextLoaderListener监听器,这个监听器会执行它的 initWebApplicationContext的方法,方法名称即是其含义。方法中首先创建了WebApplicationContext,配置并且刷新实例化整个SpringApplicationContext中的Bean。因此,如果我们的Bean配置出错的话,在容器启动的时候,会抛异常出来的。


然后会把创建的好的WebApplicationContext放到ServletContext容器中,以key为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 存储这个WebApplication 到servlet容器中。并在后续springMVC的初始化中会用到。

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

从配置的web.xml中我们可以看到整个web应用就配置了一个Servlet,也就是DispatcherServlet。


我们可以看到 DispatcherServlet这个类继承了这么多的抽象类,其实是用了一种模板方法的设计模式, 公共部分的统一自己实现,变化的部分抽象,交给子类去实现。我就随便举个HttpServletBean类的例子,大家看一下。


这里HttpServletBean重写了init()方法,使的Servlet不再关心init-param部分的赋值,让servlet更关注于自身Bean初始化的实现,并实现SpringMVC自己的容器。这里我们需要注意:在Spring的配置文件中root-context.xml 我们扫描注解的时候不需要扫描@Controller注解,需要用exclude把这个注解排除在外,不让Spring去扫描它。但是我们在SpringMVC的配置文件servlet-context.xml中扫描包的时候是需要声明扫描Controller注解的,需要用到 include,例如:<context:component-scan base-package="com.nn.web.controller"  
        use-default-filters="false">  
        <context:include-filter type="annotation"  
            expression="org.springframework.stereotype.Controller" />  
    </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。我在网上找到一张图,大家可以看一看。


三、DispatchServlet的工作流程

     因为它是个Servlet ,所以它肯定会有doService()方法,我们先从doService()开始看起。


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


一个是流转图


这样大家应该清楚了。

四、下面我们来简单的自己实现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,谢谢。github地址:https://github.com/vicoqi/SpringMVC-Simple-realize.git
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐