Spring的模型-视图-控制器(MVC)框架是围绕一个DispatcherServlet来设计的,这个Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等,甚至还能支持文件上传。
1.SpringMVC执行原理
Spring mvc运行原理如下图:

从上图我可总结出 Spring MVC 的工作流程如下: 1. 客户端请求提交到 DispatcherServlet。 2. 由 DispatcherServlet 控制器寻找一个或多个 HandlerMapping,找到处理请求的 Controller。 3. DispatcherServlet 将请求提交到 Controller。 4. Controller 调用业务逻辑处理后返回 ModelAndView。 5. DispatcherServlet 寻找一个或多个 ViewResolver 视图解析器,找到 ModelAndView 指定的视图。 6. 视图负责将结果显示到客户端。
在上图中包含 4 个 Spring MVC 接口,即 DispatcherServlet、HandlerMapping、Controller 和 ViewResolver。
Spring MVC 所有的请求都经过 DispatcherServlet 来统一分发,在 DispatcherServlet 将请求分发给 Controller 之前需要借助 Spring MVC 提供的 HandlerMapping 定位到具体的 Controller。
HandlerMapping 接口负责完成客户请求到 Controller 映射。
Controller 接口将处理用户请求,这和 Java Servlet 扮演的角色是一致的。一旦 Controller 处理完用户请求,将返回 ModelAndView 对象给 DispatcherServlet 前端控制器,ModelAndView 中包含了模型(Model)和视图(View)。
从宏观角度考虑,DispatcherServlet 是整个 Web 应用的控制器;从微观考虑,Controller 是单个 Http 请求处理过程中的控制器,而 ModelAndView 是 Http 请求过程中返回的模型(Model)和视图(View)。
ViewResolver 接口(视图解析器)在 Web 应用中负责查找 View 对象,从而将相应结果渲染给客户。
2.SpringMVC项目中web.xml的作用
一个web中可以没有web.xml文件,也就是说,web.xml文件并不是web工程必须的。web.xml文件是用来初始化配置信息:比如Welcome页面、servlet、servletmapping、filter、listener、启动加载级别等。当你的 web工程没用到这些时,你可以不用web.xml文件来配置你的Application。但是SpringMVC的核心Servlet之DispatcherServlet,就必须配置在web.xml中。
配置的各项均无误时,项目才能正确启动。web.xml有多项标签,在其加载的过程中顺序依次为:contextparam -> listener -> fileter -> servlet
而spring而spring mvc启动过程大致分为两个过程: 1.ContextLoaderListener初始化,实例化IoC容器,并将此容器实例注册到ServletContext中。 2.DispatcherServlet初始化。
3.控制器处理请求方式
我们在第一节的案例基础上添加如下代码,来观察controller处理用户请求的方式。
@RequestMapping(value="/helloworld",method = RequestMethod.GET)
public String hello(){
System.out.println("helloworld");
System.out.println("当前controller对象的hashCode:"+this.hashCode());
System.out.println("当前线程的ID:" + Thread.currentThread().getId());
return "hello"; //表示是一个视图的名字。
}
分别在两个不同的浏览器里请求helloworld,我们发现输出如下结果:
helloworld
当前controller对象的hashCode:382517336
当前线程的ID:44
helloworld
当前controller对象的hashCode:382517336
当前线程的ID:45
说明Controller处理用户的请求默认是使用单例多线程方式,这样会存在线程安全问题。这一点与Servlet处理请求方式完全相同。
如果我们在控制器上添加@Scope注解声明为原型模式。
@Controller
@Scope("prototype")
public class HelloController {
@RequestMapping(value="/helloworld",method = RequestMethod.GET)
public String hello(){
System.out.println("helloworld");
System.out.println("当前controller对象的hashCode:"+this.hashCode());
System.out.println("当前线程的ID:" + Thread.currentThread().getId());
return "hello"; //表示是一个视图的名字。
}
}
我们在同一个浏览器发送多次helloworld请求以及分别在两个不同的浏览器里请求helloworld,我们发现输出如下结果:
helloworld
当前controller对象的hashCode:735606249
当前线程的ID:40
helloworld
当前controller对象的hashCode:573934307
当前线程的ID:42
helloworld
当前controller对象的hashCode:1674146901
当前线程的ID:43
我们发现每次都会创建新的controller对象,这样也就保证了线程安全,但是同时也会造成服务器负载过高。
小结: 1. 控制器默认采用单例多线程方式处理用户请求,存在线程安全问题。 2. 使用@Scope("prototype")可以保证线程安全,但是也会增加服务器负载。