• 从源码角度了解SpringMVC的执行流程


    从源码角度了解SpringMVC的执行流程

      SpringMVC的执行流程网上有很多帖子都有讲解,流程图和文字描述都很详细,但是你如果没有通过具体源码自己走一遍流程,其实只是死记硬背。所以想开个帖子从源码角度再梳理一遍SpringMVC的执行流程,加深印象。

    SpringMVC介绍

      SpringMVC采用的是前端控制器(Front Controller) + 各个业务处理器(Controller)来处理请求的。前端控制器来响应所有请求,通过一定的调度规则找到具体负责处理的业务处理器,并将请求委派给具体的业务处理器去执行业务逻辑,业务处理器返回给前端控制器模型数据model,最后前端控制器将model交给视图View进行渲染。

    源码分析思路

      看源码的同学可能往往会陷入一个怪圈,刚开始看可能还能看懂,等到一层一层点进去会越来越晕,让自己陷入了太多的细节中,而这些细节其实对主要流程并没有多大影响,然后就埋头研究。之后不得不又从头开始看,又让自己陷入了另一个细节。其实看源码开始时只是需要看一个大致的框架和思路,了解代码的大致执行流程,千万不要让自己陷入到细节的泥潭中。所以本文是通过几个关键的接口作为切入点来梳理SpringMVC的执行流程,如果我们把关键的接口弄懂了,也就了解了SpringMVC的执行流程。所以本文只是去了解接口功能,并不关注到具体的实现逻辑上。当我们把大体流程了解后,之后就只是各个击破具体的实现类了。之后作者还会通过自己来实现这些接口来处理自己定义的请求,结合具体的例子来理解。

    阅读SpringMVC源码给我最大的感触有两点:

    • 开放封闭原则,SpringMVC中可扩展性很强,我们只需要实现具体的接口,然后将接口加入到容器中,就可以实现我们的扩展功能,不需要改任何代码,对扩展开放。而且已有实现类中的关键方法都是用final修饰的,对修改关闭。
    • 面向接口编程,所有重要的流程代码,几乎都是接口调用,而不是具体到某一个特定的类上面。

    源码解读

    注:源码版本为 spring-webmvc-5.2.2.RELEASE.jar

    几个关键接口和类

    HandlerMapping

    public interface HandlerMapping {
        @Nullable
    	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
    
    }
    

    HandlerMapping映射了请求与具体处理器的关系,可以理解为内存中有这样一个内存数据 Map<request, Handler>,HandlerMapping就是根据请求从Map中找到Handler返回。HandlerExecutionChain只是将Handler和其对应的拦截器interceptors进行了包装。

    前文所说的调度规则,通过请求的 HttpServletRequest 获取具体的请求处理类,将请求处理类包装成 HandlerExecutionChain 返回。其中 HandlerExecutionChain中的Object handler就是具体的请求处理类。

    public class HandlerExecutionChain {
        
    	private final Object handler;
    
    	@Nullable
    	private HandlerInterceptor[] interceptors;
    
    	@Nullable
    	private List<HandlerInterceptor> interceptorList;
    }
    

    我们现在通过请求找到了具体的处理类,那么我们怎么通过处理类去执行具体的方法呢?那么就需要HandlerAdapter了。

    HandlerAdapter

    public interface HandlerAdapter {
        
        /**
         * 通过方法 supports 判断适配器是否适配这种类型的Handler,返回true则代表适配。
         */
        boolean supports(Object handler);
        
        /**
         * 如果适配则通过方法 handle 去让 Object handler 执行具体的处理方法。
         */
        @Nullable
    	ModelAndView handle(HttpServletRequest request, HttpServletResponse response
                            , Object handler) throws Exception;
        
        	long getLastModified(HttpServletRequest request, Object handler);
    
    }
    

    HandlerAdapter 处理器的适配器,帮助前端控制器去执行处理器Handler中具体的处理业务,让前端控制机不需要关注具体的执行细节,也就是说HandlerAdapter对前端控制机屏蔽了处理器执行的具体细节。

    ModelAndView

    public class ModelAndView {
    
    	/** View instance or view name String. */
    	@Nullable
    	private Object view;
    
    	/** Model Map. */
    	@Nullable
    	private ModelMap model;
    

    对逻辑视图名view和数据模型的封装。

    Object view为通常为String类型的逻辑视图名。

    ModelMap 为MVC中Model的角色,底层为Map类型。

    ViewResolver

    public interface ViewResolver {
        @Nullable
    	View resolveViewName(String viewName, Locale locale) throws Exception;
    
    }
    

    解析逻辑视图名viewName找到具体的View,前端控制器找到具体视图View的向导。

    View

    public interface View {
        	void render(@Nullable Map<String, ?> model, HttpServletRequest request
                        , HttpServletResponse response) throws Exception;
    
    }
    

    调用render方法通过数据模型渲染视图。

    请求参数中的 Map<String, ?> model 在SpringMVC中扮演Model的角色。

    比如我们想返回json格式的数据,那么render方法逻辑就是将model转为json格式输出。

    或者我们想返回jsp,那么我们就可以model解析到具体的jsp页面进行展示。

    前端控制器 DispatcherServlet

     在SpringMVC中,DispatcherServlet作为前端控制器,控制服务的具体执行流程,主要的执行流程代码也都在这个类中。

    下面是DispatcherServlet简化的源码,只包含了重要的执行流程,保留了关键的执行代码,读者可以具体关键字进行搜索去找到具体的代码行。

    public class DispatcherServlet extends FrameworkServlet {
        protected void doDispatch(HttpServletRequest request, 
                                  HttpServletResponse response) throws Exception {
            HandlerExecutionChain mappedHandler = null;
            ModelAndView mv = null;
            mappedHandler = getHandler(processedRequest);
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            
            render(mv, request, response);
        }
        
        protected void render(ModelAndView mv, HttpServletRequest request
                              , HttpServletResponse response) throws Exception {
            String viewName = mv.getViewName();
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
            view.render(mv.getModelInternal(), request, response);
        }
    
    代码流程:
    • 获取处理器:HandlerMapping通过HttpServletRequest请求找到具体的Handler
    • 获取处理器对应的适配器:通过Handler找到具体的HandlerAdapter
    • 调用处理器的处理逻辑:HandlerAdapter调用Handler执行具体的处理逻辑,返回ModelAndView
    • 解析视图:ViewResolver通过ModelAndView中的逻辑视图名找到具体的View。
    • 渲染视图:View将数据模型进行渲染。
    获取处理器
     @Nullable
    	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    		if (this.handlerMappings != null) {
    			for (HandlerMapping mapping : this.handlerMappings) {
    				HandlerExecutionChain handler = mapping.getHandler(request);
    				if (handler != null) {
    					return handler;
    				}
    			}
    		}
    		return null;
    	}
    

    SpringMVC在启动的时候会扫描所有实现了HandlerMapping接口的类,并将这些类加入到容器中。

    获取处理器其实就是循环实现了HandlerMapping的类,调用getHandler()方法,找到了就停止并返回。

    每种类型的Handler都有各自对应的HandlerMapping。比如SpirngMVC中默认的处理器为 Controller,与之对于的HandlerMapping为RequestMappingHandlerMapping。

    获取处理器的适配器
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    		if (this.handlerAdapters != null) {
    			for (HandlerAdapter adapter : this.handlerAdapters) {
    				if (adapter.supports(handler)) {
    					return adapter;
    				}
    			}
    		}
    }
    

    逻辑和获取处理器一样,判断逻辑就是前面提过的,只要supports方法返回true,则代表适配这个Handler。

    同理也是一种Handler应该有与之对应的HandlerAdapter。与Controller对应的为RequestMappingHandlerAdapter。

    所以如果我们要编写自定义的处理器。那么我们需要自己的Handler类和与之对于的HandlerMapping和HandlerAdapter。

    解析视图
     @Nullable
    	protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
    			Locale locale, HttpServletRequest request) throws Exception {
    
    		if (this.viewResolvers != null) {
    			for (ViewResolver viewResolver : this.viewResolvers) {
    				View view = viewResolver.resolveViewName(viewName, locale);
    				if (view != null) {
    					return view;
    				}
    			}
    		}
    		return null;
    	}
    

    仍然是同样的逻辑,所有的代码都是面向接口来开发的。

    SpringMVC支持各种各样的视图渲染,如JSP、json、freemarker、thymeleaf。ViewResolver就是这些视图的向导,它告诉SpringMVC需要通过什么方式去找到具体的视图View。

    每种视图View都有自己对应的视图解析器,例如FreeMarkerView对应的视图解析器为FreeMarkerViewResolver。

    视图渲染

    其实就一句代码。

    view.render(mv.getModelInternal(), request, response);
    

    视图渲染,作者刚开始看到这个词,觉得好高大上。其实就是让数据模型model应以什么样的方式来展示。

    如果你想将model以json格式返回,那么你就去实现View接口,把model转为json格式,然后写入到响应类的输出流即可。

    ServletOutputStream out = response.getOutputStream();
    baos.writeTo(json);
    out.flush();
    

    结语

    本文只是通过几个重要的接口来描述SpringMVC的执行流程,没有具体分析实现类的逻辑。也想在这里分享下自己看源码的心得体会。看源码时千万不要让自己陷入过深的业务逻辑中去,先看主要执行流程,重要的接口,比如以debug的方式先预览下执行的方法栈,根据方法栈去定位。如果有哪些地方有误或者有不同的理解,还请不吝赐教。

  • 相关阅读:
    C# Sleep延时方法
    浅谈模糊测试
    python time模块常用方法小结
    Markdown使用小结
    关于测试用例设计、评审及用例质量评估的思考
    关于评估软件产品质量的思考
    关于软件测试工程师进阶提升的思考
    关于软件测试中回归测试的思考
    测试技术的思考 ---- 读《微软的软件测试之道》有感系列
    vue-learning:22
  • 原文地址:https://www.cnblogs.com/LuxBai/p/12180668.html
Copyright © 2020-2023  润新知