提到SpringMVC,你的第一印象是什么?一个简化Web开发的轻量级框架?事实上,在现代开发过程中,开发流程和开发效率的不断提升,伴随着Restful和Json结合的兴起,使得多设备跨平台相互调用访问变得更加容易,所以SpringMVC简化web开发的使命自然而然地变成了简化服务器端开发。那么今天我们就抛开繁杂的代码,从宏观的角度来看一下SpringMVC是如何实现处理请求和简化服务端开发的解决方案的。1、曾经的王者——Servlet在笔者最初接触使用Java进行Web开发的时候,SpringMVC远没有今天流行。君不见昔日Servlet之王的繁荣景象。现在回想起来,虽然使用Servlet进行开发不像现在这么容易,很多事情都需要自己来完成,但是Servlet让开发的逻辑非常清晰,尤其是Servlet和jsp很好的承担了各自的角色之后。再加上MVC分层思想的流行。那时,编写Web应用程序是一件快乐而轻松的事情。事实上,Servlet并没有做很多事情。我认为Servlet要完成的是统一接受、处理和响应请求的过程。网络编程中绕不开的一个东西想必大家都猜到了,那就是Socket。但是如果需要网络传输,就很复杂了。首先,它需要遵循一定的协议。现在我们一般使用Http和Https来传输数据,而Socket是在一些网络协议之上,屏蔽了底层协议的细节,为用户提供了统一的api。但是Servlet认为Socket不够用,还是得相应处理。所以Servlet(就HttpServlet而言),他将请求报文封装在网络中,转化为Request表示。在Http通信过程中,就是HttpServletRequest,将服务器处理请求后返回的response统一封装为一个HttpServletResponse对象。.这样做有什么好处?作为开发者,我们不必做处理网络请求和响应的繁琐工作,而只需要专注于我们的业务逻辑开发。你有没有注意到,框架的每一次效率提升,都是为了将??最重要的业务逻辑与其他任务尽可能完全地分离,让我们可以一直全身心投入到业务逻辑的开发中,Spring就是AOP的一个很好的证明!那么如何使用Servlet呢?没有使用Servlet经验的同学可以简单听我说说:1、我们通常先写自己的Servlet然后继承自HttpServlet,然后重写它的doGet()和doPost()方法。这两个方法会把HttpServletRequest和HttpServletResponse作为参数传入,然后我们从Request中提取前端的参数,在对应的doXXX方法中调用预先写好的Service接口,Dao接口就可以准备好要放入的数据了响应并跳转到指定页面,跳转方式可以选择转发或重定向。2.Servlet使用模板方法设计模式。服务方法将在Servlet的顶层被调用。该方法会构造HttpServletRequest和HttpServletResponse对象作为参数,调用子类重写的doXXX()方法。然后返回请求。3、最后,我们需要在web.xml中注册我们编写的自定义Servlet,并在web.xml中配置servlet-mapping,指定为servlet处理哪些请求。Servlet的使用就是这么简单!事实上,他的受欢迎程度也得益于他长期以来的简单易用。ShoppingServletcom.myTest。ShoppingServletShoppingServlet/shop/ShoppingServlet2.想更进一步当我们使用Servlet进行业务逻辑开发时,往往会觉得很酷,但同时又觉得有些不适应。主要的不适有以下几点:每个servlet只能处理一个request,所以当系统比较大,业务比较复杂的时候,可能有成百上千个servlet,查找起来比较困难。每次我们都需要手动从Request中获取请求参数,然后封装成我们想要的对象,其中可能需要对参数进行校验。调用业务逻辑层获取数据后,我们需要手动设置到response,手动选择转发或者重定向跳转。我们请求的url硬配置到web.xml中,缺乏灵活性。如果能动态配置请求url和处理的对应关系就好了。我们的Servlet和前端渲染框架是紧耦合的,这样当前端换成不同的显示技术时,需要改一大段代码。如果能把数据处理和数据展示分开,松耦合起来就更好了。带着这些想法,我们是不是可以进一步提炼出业务逻辑的开发呢?早期,笔者也做了一些尝试。大致思路是写一个BaseServlet,然后我们定义的Servlet继承自BaseServlet。前端请求需要指定处理Servlet的哪个方法,这样请求就需要带一个method参数,例如:http://localhost:8080/myProject/MyServlet?method=getInfo会提取BaseServlet中的参数信息,并使用反射的方式调用子类的方法,子类的方法统一返回String类型结果表示要返回的逻辑视图的名称,即到的路径跳转到,然后父类得到结果,使用重定向或者转发进行跳转。说到这里,肯定有小伙伴不耐烦了。他们显然在谈论SpringMVC。到现在连SpringMVC的电影都没看过。他们都在谈论Servlet。别着急,了解这些对我们理解SpringMVC会有很大的帮助。请低头说这话。其实我想说如果我们想在Servlet上走得更远,我们想把业务逻辑和其他工作进一步结合起来。彼此分离,那么我们需要在Servlet之上构建一个超级Servlet,为我们做一切,努力工作,上帝通过大...(我想不出来...)来做这些工作给我们。让我们把这个Servlet称为超级棒的Servlet。还有谁是开发Spring的大佬?这个我们能想到,难道他们想不到吗?于是他们亲手开发了这个超级牛逼的Servlet,并正式命名为DispatcherServlet。3.SpringMVC——二级控制器方法接下来,我们就正式开启SpringMVC之旅。通过前面的了解,我们知道SpringMVC调用了超级强大的ServletDispatcherServlet。这个Servlet可以说是简化了我们FuckHeart的开发,我们称之为_frontcontroller。现在我们不禁想到,我们之前写的BaseServlet对应的就是现在超级牛逼的Servlet(DispatcherServlet)。那么定义我们业务逻辑的自定义Servlet是什么呢?SpringMVC管理定义我们业务逻辑处理的类叫做Handler,但它不再是一个Servlet,而是一个普通的类,这个也很容易理解。毕竟DispatcherServlet做的太多了,牛逼到不行,你可以把一个普通的类当成一个Servlet来对待,而这个Handler就叫做二级控制器_。这里可能有些朋友会有异议。有的书上说SpringMVC的二级控制器叫Controller,不是Handler。其实SpringMVC的二级controller确实叫Handler,但是Hander是一个抽象,SpringMVC选择用Controller来实现Handler。说到这里,你觉得我们能不能自定义一个叫Lellortnoc的Handler实现呢?答案当然是肯定的!就好像List是一个抽象接口,List的实现包括ArrayList和LinkedList。4.DispatcherServlet——前端控制器DispatcherServlet是整个SpringMVC的核心,超级牛逼Servlet的荣誉称号名副其实。DispatcherServlet和它的家族成员一起做了很多工作,包括请求参数的自动绑定、参数的自动校验、请求url的自动匹配、从逻辑视图名到真实页面的跳转、数据获取和数据渲染展示的分离等等...在这个过程中,他更像是一个指挥者,有条不紊地指挥请求不断向前处理,最终完成服务器的响应数据。想知道具体的DispatcherServlet是如何被命令的,那就继续往下看吧!推荐:250道面试题总结5.HandlerMapper——请求映射专家想想我们在使用Servlet写代码的时候,请求映射的工作都交给了web.xml。但是现在SpringMVC采用了二级控制器的方式,这个难题就必须要解决了。首先DispatcherServlet也是一个Servlet,所以我们也应该在web.xml中配置它处理的请求路径。那么应该配置什么路径呢?我们说DispatcherServlet被称为超级强大的Serlvet,我们希望它能够处理所有的请求,所以我们可以让DispatcherServlet接受所有请求的处理。像这样配置:SpringMVCorg.springframework.web.servlet.DispatcherServletcontextConfigLocationclasspath:Spring-servlet.xml1SpringMVC/*现在所有的请求都映射到DispatcherServlet,DispatcherServlet现在负责请求分发到具体的二级控制器,如何找到或保存请求与具体二级控制器的映射关系?DispatcherServlet选择请求他的好兄弟HandlerMapping。在HandlerMapping中,具体的请求url保存在哪个Handler(也就是通常的Controller)中应该被哪个处理。根据不同的映射策略,HandlerMapping有以下几种映射查找方式:org.springframework.web.servlet.handler.SimpleUrlHandlerMapping通过配置请求路径和Controller映射建立关系,找到对应的Controllerorg.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping通过Controller的类名找到请求的Controller。org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping通过定义的beanName找到要请求的Controller想必第四种是现在最常用的。只需要在相应的Controller及其内部方法上添加相应的注解,就可以配置请求的映射,简直爽翻天。推荐:250道面试题总结6.Handler的拦路虎——HandlerInterceptor说到这里,你以为DispatcherServlet会把请求的url交给HandlerMapping,HandlerMapping会根据请求找出对应的Controller交给DispatcherServlet,然后DispatcherServlet会交给Controller执行完了吗?然后Toyoungtonative,里面有一些小插曲。比如我们不能不管三七二十一,把所有的请求都交给Handler去执行。至少我们要过滤掉不合理的请求,比如跳转到页面的时候检查Session,用户没有登录就跳转到登录界面,还有一些程序的异常统一跳转等等,都需要拦截请求。了解Servlet的同学是否有一种似曾相识的感觉?没错,Servlet中的Filter也可以完成请求拦截和过滤的功能,但是由于SpringMVC是二级控制器结构,HandlerInterceptor与Filter有一些细微的区别。主要区别,作者认为HandlerInterceptor提供了Fine-grained的拦截。毕竟Filter拦截的对象是Serlvet,而HandlerInterceptor拦截的对象是Handler(Controller)。可以用一张图形象地表达出来。HandlerInterceptor.jpg从图中可以看出可以配置多个HandlerInteceptor。如果其中任何一个返回false,请求将被拦截并直接返回。7.Secondarycontroller——Handler前端控制器我们已经很熟悉了,Secondarycontroller就是Handler,也就是我们真正执行业务逻辑的类。通常在SpringMVC中,这个Handler就是我们熟悉的Controller。这就是我们调用封装的业务逻辑接口进行处理的地方。可以说SpringMVC已经将业务逻辑与其他无关的复杂工作完全分离。这样,我们就专心在Handler(控制器)中编写我们的业务逻辑吧!8、Handler和HandlerInterceptor之间的桥梁—HandlerExecutionChain。前面提到,DispatherServlet要求HandlerMapping映射url和secondarycontroller,但是在DispatherServlet将url交给具体的HandlerMapping之后,HandlerMapping进行了激烈的操作,返回给DispaterServlet的不是一个可执行的Handler(Controller),而是一个HandlerExecutionChain对象。那么为什么HandlerMapping返回的是这样一个对象,而不是返回Handler对象呢?其实大家在看上图的时候是不是很好奇HandlerInterceptor和Handler是怎么连接起来的呢?答案是HandlerExecutionChain。它是几个HandlerInterceptor和Handler的组合。那么它是如何组合的呢?这就涉及到设计模式中的责任链设计模式。HandlerExecutionChain将HandlerInterceptor和Handler串成一个执行链的形式。首先,请求会被第一个HandlerInterceptor拦截。如果返回false,则直接短路请求。如果返回true,则则交给第二个HandlerInterceptor处理,直到所有HandlerInterceptor检查通过,请求才到达Handler(Controller),Handler正式处理请求。执行完成后,逐层返回。DispatcherServlet得到的就是这样一个串联的HandlerExecutionChain,然后依次执行请求。9.解耦的关键——ModelAndView这里,请求最终还是来到了对应的Handler。我们希望的是Handler只处理负责的业务逻辑,一些url跳转等不需要Handler负责。然后DispatcherServlet使用ModelAndView来保存我们的数据和我们要跳转到的路径。我们调用业务逻辑层获取数据,将数据封装到ModelAndView中,设置ModelAndView的视图逻辑视图名。从ModelAndView的名字可以看出,它保存了Handler执行后需要发送给前端的数据,以及需要跳转的路径。这些就是DispatcherServlet需要用到的。推荐:250道面试题总结10.视图渲染搜索——ViewResolver这一步是SpringMVC实现数据获取和数据显示渲染分离的关键。前端可能会以各种方式显示数据,可能是Jsp,可能是Html,也可能是其他方式。DispatcherServlet已经获取到了ModelAndView,其中包含了请求执行后返回的响应结果数据,以及逻辑视图的路径。这时候DispatcherServlet就需要根据逻辑视图的路径找出谁可以解析和渲染数据。比如我们使用FreeMarker模板引擎来渲染数据,那么这时候就需要找到能够完成这项工作的View实现类。那么问题来了,怎么找呢?你用什么策略找到它?这取决于我们的ViewResolver。通常的查找策略如下:BeanNameViewResolver:将逻辑视图名解析成一个Bean,Bean的id等于逻辑视图名。XmlViewResolver:与BeanNameViewResolver类似,只是目标视图Bean对象是在单独的XML文件中定义的,而不是在DispatcherServlet上下文的主配置文件中。该名称映射到存储在WEB-INF目录中的程序文??件(如JSP)。XsltViewResolver:将视图名称解析为指定XSLT样式表的URL文件。JasperReportsViewResolver:JasperReports是一款基于java的开源报表工具。解析为报表文件对应的URLFreeMarkerViewResolver:基于FreeMarker模板技术解析为模板文件VelocityViewResolver和VelocityLayoutViewResolver:基于Velocity模板技术解析为模板文件11.数据渲染——View根据逻辑找到对应的View视图名借助ViewResolver类实现后,DispatcherServlet会将ModelAndView中的数据交给View实现类进行渲染。View渲染完成后,会将渲染后的数据交给DispatcherServlet。这时DispatcherServlet会将其封装在Response中返回给前端显示。至此,整个SpringMVC的处理流程就完成了。当然还会有国际化的支持,主题的定义和设置等等,但是这些都不常用。需要用到SpringMVC最重要的处理流程。这些就是上面的类别。可见DispatcherServlet在这个过程中起到了至关重要的作用,所以SpringMVC的核心在于DispatcherServlet。最后附上一张流程图,作为对以上内容的总结。SpringMVC处理流程.jpg