Java Web三层架构:表示层 业务层 持久层 利用HTTP协议完成浏览器与服务器之间的交互
下一步:考虑功能扩展问题,将模式和流程进行封装,提高代码的重用率
MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分
M:Model,模型层,指工程中的JavaBean,作用是处理数据
JavaBean分为两类:
- 一类称为实体类Bean:专门存储业务数据的,如 Student、User 等
- 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。
V:View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据
C:Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器
MVC的工作流程:
用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器
SpringMVC是Spring的一个后续产品,是Spring的一个子项目
SpringMVC 是 Spring 为表示层开发提供的一整套完备的解决方案。在表述层框架历经 Strust、WebWork、Strust2 等诸多产品的历代更迭之后,目前业界普遍选择了 SpringMVC 作为 Java EE 项目表述层开发的首选方案。
注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台servlet
登录成功:重定向
登录失败:请求转发
- Spring 家族原生产品,与 IOC 容器等基础设施无缝对接
- 基于原生的Servlet,通过了功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理
- 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
- 代码清新简洁,大幅度提升开发效率
- 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
- 性能卓著,尤其适合现代大型、超大型互联网项目要求
IDE:idea 2019.2
构建工具:maven3.5.4
服务器:tomcat7
Spring版本:5.3.1 建议不要用 5之前的版本
2、创建maven工程(引入依赖)maven有三种打包方式:
默认打包方式:jar
如果要将当前工程转换为web工程:war包 ,并且添加web模块
org.springframework spring-webmvc 5.3.1 ch.qos.logback logback-classic 1.2.3 javax.servlet javax.servlet-api 3.1.0 provided org.thymeleaf thymeleaf-spring5 3.0.12.RELEASE
注:由于 Maven 的传递性,我们不必将所有需要的包全部配置依赖,而是配置最顶端的依赖,其他靠传递性导入。
注册SpringMVC的前端控制器DispatcherServlet
为什么要注册?因为浏览器不能访问到一个类,要访问需要设置匹配路径
a>默认配置方式此配置作用下,SpringMVC的配置文件默认位于WEB-INF下,默认名称为-servlet.xml,例如,以下配置所对应SpringMVC的配置文件位于WEB-INF下,文件名为springMVC-servlet.xml
springMVC org.springframework.web.servlet.DispatcherServlet springMVC /
- /所匹配的请求可以是/login或.html或.js或.css方式的请求路径,不包括.jsp
-
ModelAndView mav = new ModelAndView();
//向请求域共享数据
mav.addObject("testScope", "hello,ModelAndView");
//设置视图,实现页面跳转
mav.setViewName("success");
return mav;//必须作为该方法的返回值返回
}
2.2、使用Model
@RequestMapping("/testModel") public String testModel(Model model){ model.addAttribute("testScope", "hello,Model"); return "success"; }2.3、使用map@RequestMapping("/testMap") public String testMap(Map2.4、使用ModelMapmap){ map.put("testScope", "hello,Map"); return "success"; } @RequestMapping("/testModelMap") public String testModelMap(ModelMap modelMap){ modelMap.addAttribute("testScope", "hello,ModelMap"); return "success"; }3、Model、ModelMap、Map的关系toString:
{testScope=hello,Model} {testScope=hello,Map} {testScope=hello,ModelMap}
利用反射xxx.getClass().getName();
发现是这三个类通过同一个类进行实例化
Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型实例化的public interface Model{} public class ModelMap extends linkedHashMap{} public class ExtendedModelMap extends ModelMap implements Model {} public class BindingAwareModelMap extends ExtendedModelMap {} toString:
{testScope=hello,Model}
{testScope=hello,Map}
{testScope=hello,ModelMap}
不管用的是什么方式,最终都要把数据封装到ModelAndView中
4、向session域共享数据建议使用Servlet原生API
@RequestMapping("/testSession") public String testSession(HttpSession session){ session.setAttribute("testSessionScope", "hello,session"); return "success"; }5、向application域共享数据(ServletContext)Title @RequestMapping("/testApplication") public String testApplication(HttpSession session){ ServletContext application = session.getServletContext(); application.setAttribute("testApplicationScope", "hello,application"); return "success"; }六、SpringMVC的视图Title SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户
SpringMVC视图的种类很多,默认有转发视图和重定向视图
- 当工程引入jstl的依赖,转发视图会自动转换为JstlView
- 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是ThymeleafView
- 视频p44,源码这里没看太懂,有机会继续,orz笨蛋饼干终于会打断点了
1、ThymeleafView
return视图的名字完全决定了视图的类型(查看View详细信息,则可看到View对应类型)当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器SpringResourceResolver解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转
@RequestMapping("/testHello") public String testHello(){ return "hello"; }2、转发视图SpringMVC中默认的转发视图是InternalResourceView,如果没有配置Thymeleaf默认使用此视图
SpringMVC中创建转发视图的情况:
当控制器方法中所设置的视图名称以"forward:"为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转
例如"forward:/",“forward***/employee”
themeleaf本身就是通过转发访问的,所以基本不用@RequestMapping("/testForward") public String testForward(){ return "forward:/testHello"; }3、重定向视图SpringMVC中默认的重定向视图是RedirectView
当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最终路径通过重定向的方式实现跳转
例如"redirect:/",“redirect***/employee”
@RequestMapping("/testRedirect") public String testRedirect(){ return "redirect:/testHello"; }- 重定向不能访问WEB-INF下的内容,所以如果要访问,必须通过转发,重定向到的是请求,而不是一个具体的页面
注:
重定向视图在解析时,会先将redirect:前缀去掉,然后会判断剩余部分是否以/开头,若是则会自动拼接上下文路径
转发和重定向区别:
转发:
- 浏览器地址栏没有变化
- 浏览器发送一次请求
- 他们共享request域中的数据
- 可以转发到WEB-INF目录下,浏览器不能访问/WEB-INF/form.html
- 不可以访问工程以外的资源
eg.不能跳到百度
重定向:
- 浏览器地址栏会发生变化
- 浏览器发送两次请求
- 不共享Request域中的数据。两次请求,获得的是不同的Request对象
- 不能访问WEB-INF下的资源,WEB-INF浏览器无法直接访问
- 可以访问工程外的资源
当控制器方法中,没有其他请求过程的处理,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示
@RequestMapping("/testView") public String index(){ //返回视图名称,就会被配置的视图解析器进行解析(+前缀+后缀)就是最终要访问的页面 return "success"; }在springMVC.xml配置文件中添加
注:
当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:
p47 7:40 其他功能
在ThemeleafViewResolver出现之前,如果视图使用的是jsp,SpringMVC如何解析jsp视图?
如果什么都不加/使用forward:,采用InternalResoureViewResolverjsp在WEB-INF外,可以直接访问,所以不需要再设置访问首页的请求映射,
Tomcat默认访问index.jsp,Tomcat的web.xml里有,作用于部署到Tomcat的所有工程有效
当前工程的web.xml只针对当前工程有效index.html index.html index.jsp @RequestMapping("/success") public String index(){ //返回视图名称,就会被配置的视图解析器进行解析(+前缀+后缀)就是最终要访问的页面 return "success"; }<%--jsp指令,用来设置当前页面中的一些信息--%> <%@ page contentType="text/html;charset=UTF-8" language="java" %>Title <%--EL表达式,通过访问属性的方法访问--%> success.jsp
forward:redirect:一般在前缀后缀不符合时使用
七、RESTful
1、RESTful简介是一种软件架构的风格
a>资源
REST:Representational State Transfer,表现层(前端视图页面+后端控制层)资源(工程部署到服务器,工程中的内容都叫资源,资源状态即资源的表现形式不同)状态转移(将服务器上资源发送给客户端)。资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互。
b>资源的表述资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。
c>状态转移状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。
相同的请求地址(当前要操作的资源),不同的请求方式(当前操作资源的方式)
三、8.SpringMVC支持路径中的占位符:不以问号进行传参而用斜线拼接在请求地址中
2、RESTful的实现具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETe。
它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。
REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。
操作 传统方式 REST风格 查询操作 getUserById?id=1 user/1–>get请求方式 保存操作 saveUser user–>post请求方式 删除操作 deleteUser?id=1 user/1–>delete请求方式 更新操作 updateUser user–>put请求方式 @RequestMapping(value="/user",method=RequestMethod.GET) public String getAllUser(){ System.out.println("查询所有用户信息"); return "success"; } @RequestMapping(value="/user/{id}",method=RequestMethod.GET) public String getUserById(){ System.out.println("根据id查询用户信息"); return "success"; }3、HiddenHttpMethodFilterTitle 查询所有用户信息 根据id查询用户信息由于浏览器只支持发送get和post方式的请求(不是则默认按照get方法处理),那么该如何发送put和delete请求呢?
SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求
HiddenHttpMethodFilter 处理put和delete请求的条件:
a>当前请求的请求方式必须为post
b>当前请求必须传输请求参数_method
满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数_method的值,因此请求参数_method的值才是最终的请求方式
在web.xml中注册HiddenHttpMethodFilter
有多个过滤器时,过滤器的执行顺序由filtermapping顺序决定,编码过滤器要写到最前,设置编码之前不能获取任何的请求参数,只要获取,设置编码就没有任何效果了
2、创建SpringConfig配置类,代替spring的配置文件HiddenHttpMethodFilter org.springframework.web.filter.HiddenHttpMethodFilter HiddenHttpMethodFilter @Override protected Class>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } @Override protected Class>[] getServletConfigClasses() { return new Class[]{WebConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter(); encodingFilter.setEncoding("UTF-8"); encodingFilter.setForceRequestEncoding(true); HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter(); return new Filter[]{encodingFilter, hiddenHttpMethodFilter}; } } @Configuration public class SpringConfig { //ssm整合之后,spring的配置信息写在此类中 }3、创建WebConfig配置类,代替SpringMVC的配置文件- 扫描组件
- 视图解析器
- view-controller
- default-servlet-headler静态资源处理
- MVC注解驱动
- 文件上传解析器
- 异常处理 bean/configureHandlerExceptionResolvers
- ]
- 拦截器
@Configuration//将当前类标识为一个配置类 @ComponentScan("com.atguigu.mvc.controller")//扫描组件 @EnableWebMvc//开启MVC注解驱动 //web工程初始化类,用来代替web.xml public class WebConfig implements WebMvcConfigurer { //使用默认的servlet处理静态资源 4.default-servlet-headler静态资源处理 @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } //6.配置文件上传解析器 @Bean public CommonsMultipartResolver multipartResolver(){ return new CommonsMultipartResolver(); } //8.配置拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { FirstInterceptor firstInterceptor = new FirstInterceptor(); registry.addInterceptor(firstInterceptor).addPathPatterns(" //配置异常映射 //Bean从里到外创建 //1.配置生成模板解析器 @Bean public ITemplateResolver templateResolver() { //IOC容器在spring中的实现 //1.Application---Java工程 //2.WebApplication---Web工程 WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext(); // ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得 ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver( webApplicationContext.getServletContext()); templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); templateResolver.setCharacterEncoding("UTF-8"); templateResolver.setTemplateMode(TemplateMode.HTML); return templateResolver; } //2.生成模板引擎并为模板引擎注入模板解析器,参数进行自动装配 @Bean public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver); return templateEngine; } //3.生成视图解析器并未解析器注入模板引擎 @Bean public ViewResolver viewResolver(SpringTemplateEngine templateEngine) { ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setCharacterEncoding("UTF-8"); viewResolver.setTemplateEngine(templateEngine); return viewResolver; } }4、测试功能@RequestMapping("/") public String index(){ return "index"; }十三、SpringMVC执行流程 1、SpringMVC常用组件- DispatcherServlet:前端控制器,不需要工程师开发,由框架提供
作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
- HandlerMapping:处理器映射器@RequestMapping,不需要工程师开发,由框架提供
作用:根据请求的url、method等信息查找Handler,即控制器方法
- Handler:处理器,即控制器@Control,需要工程师开发
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
- HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供
作用:通过HandlerAdapter对处理器(控制器方法)进行执行
- ViewResolver:视图解析器,不需要工程师开发,由框架提供
作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView
- View:视图
作用:将模型数据通过页面展示给用户
2、DispatcherServlet初始化过程DispatcherServlet 本质上是一个 Servlet,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。
a>初始化WebApplicationContext所在类:org.springframework.web.servlet.frameworkServlet
protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id wac = findWebApplicationContext(); } if (wac == null) { // No context instance is defined for this servlet -> create a local one // 创建WebApplicationContext wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. synchronized (this.onRefreshMonitor) { // 刷新WebApplicationContext onRefresh(wac); } } if (this.publishContext) { // Publish the context as a servlet context attribute. // 将IOC容器在应用域共享 String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }b>创建WebApplicationContext所在类:org.springframework.web.servlet.frameworkServlet
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class> contextClass = getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } // 通过反射创建 IOC 容器对象 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); // 设置父容器 wac.setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } configureAndRefreshWebApplicationContext(wac); return wac; }c>DispatcherServlet初始化策略frameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件
所在类:org.springframework.web.servlet.DispatcherServlet
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }3、DispatcherServlet调用组件处理请求 a>processRequest()frameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了processRequest(request, response)
所在类:org.springframework.web.servlet.frameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(frameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes); try { // 执行服务,doService()是一个抽象方法,在DispatcherServlet中进行了重写 doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); publishRequestHandledEvent(request, response, startTime, failureCause); } }b>doService()所在类:org.springframework.web.servlet.DispatcherServlet
@Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); // Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. Mapc>doDispatch()attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } // Make framework objects available to handlers and view objects. request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); if (this.flashMapManager != null) { FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); } RequestPath requestPath = null; if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) { requestPath = ServletRequestPathUtils.parseAndCache(request); } try { // 处理请求和响应 doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } if (requestPath != null) { ServletRequestPathUtils.clearParsedRequestPath(request); } } } 所在类:org.springframework.web.servlet.DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. // 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 调用拦截器的preHandle() if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. // 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); // 调用拦截器的postHandle() mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } // 后续处理:处理模型数据和渲染视图 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }d>processDispatchResult()private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { // 处理模型数据和渲染视图 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isTraceEnabled()) { logger.trace("No view rendering, null ModelAndView returned."); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } if (mappedHandler != null) { // Exception (if any) is already handled.. // 调用拦截器的afterCompletion() mappedHandler.triggerAfterCompletion(request, response, null); } }4、SpringMVC的执行流程- 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
- DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
a) 不存在
i. 再判断是否配置了mvc:default-servlet-handler
ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误
iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误
b) 存在则执行下面的流程
- 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
- DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
- 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
- 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
- Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
- 此时将开始执行拦截器的postHandle(…)方法【逆向】。
- 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
- 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
- 将渲染结果返回给客户端。



