【SpringMVC】(三)
HTTPMessageConverter
HttpMessageConverter报文信息转换器,将请求报文转换为java对象,或将java对象转换为响应报文。
1 @ResquestBody
ResquestBody可以获取请求体,需要在控制器方法中设置一个形参,使用@RequestBody进行标识当前请求的请求体就会为当前注解所标识的形参赋值
@Controller public class TestHttpRequestBodyController { @RequestMapping("/requestBody") public String requestBody(@RequestBody String requestbody, String username, String password) throws UnsupportedEncodingException { requestbody = URLDecoder.decode(requestbody, "UTF-8"); System.out.println(requestbody); System.out.println(username + password); return "success"; } }
这里需要对请求体进行中文解码,不知道为什么(并不是没有设置编码过滤器,下面的两个请求参数是正常的
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>Index</h1> <hr> <form th:action="@{/requestBody}" method="post"> <input type="text" value="亚瑟" name="username"><br> <input type="password" value="admin" name="password"><br> <input type="submit"> </form> </body> </html>
2 RequestEntity获取完整报文信息
@RequestMapping("/requestEntity") public String requestEntity(RequestEntity<String> requestEntity) { System.out.println(requestEntity.getHeaders()); System.out.println(requestEntity.getBody()); return "success"; }
3 原生HttpServletResponse响应浏览器数据
@RequestMapping("/servletResponse") public void servletResponse(HttpServletResponse response) throws IOException { response.getWriter().println("hello"); }
4 @ResponseBody响应浏览器数据
@RequestMapping("/responseBody") @ResponseBody public String responseBody() { return "hello, responsebody"; }
5 SpringMVC处理json
json是一种JavaScript的交互格式,格式分为json对象{}和json数组[]。Java实体类转换为json是json对象,Map是json对象,但是List是json数组
①在pom.xml添加jackson依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.0</version> </dependency>
②在SpringMVC的核心配置文件开启MVC注解驱动
<mvc:annotation-driven></mvc:annotation-driven>
③在控制器方法上使用@Response进行注解标识
④将java对象直接作为控制器方法的返回值返回,这样响应到浏览器的java对象就会自动转换为json对象
@RequestMapping("/testResponseUser") @ResponseBody public User responseUser() { return new User("admin", "12345"); }
JavaBean对象必须写get、set方法,否则仍然会报500Http错误
SpringMVC处理ajax
<script type="text/javascript" th:src="@{/static/vue.js}"></script> <script type="text/javascript" th:src="@{/static/axios.min.js}"></script> <script type="text/javascript"> new Vue({ el:"#app", methods:{ testAxios: function (event) { axios({ method:"post", url:event.target.href, params: { username:"admin", password:"123456" } }).then(function (response) { alert(response.data) }); event.preventDefault(); } } }); </script>
@RestController
@ RestController注解是SpringMVC提供的一个复合注解,标识在控制器的类上,相当于为类添加了 @Controller注解,并且为其中的每个控制器方法添加了 @ResponseBody注解。
@ResponseEntity
ResponseEntity作为控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文
ResponseEntity实现文件下载
<a th:href="@{/testFileDown}">下载视频</a>
@RequestMapping("/testFileDown") public ResponseEntity<byte[]> testFileDown(HttpSession session) throws IOException { ServletContext servletContext = session.getServletContext(); String realPath = servletContext.getRealPath("/static/MP4/88-概述.mp4" ); //请求体 InputStream is = new FileInputStream(realPath); byte[] bytes = new byte[is.available()]; is.read(bytes); //请求头 MultiValueMap<String, String> headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment;filename=88-概述.mp4"); //请求码 HttpStatus statusCode = HttpStatus.OK; ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode); is.close(); return responseEntity; }
使用MultiFile实现文件上传
<form th:action="@{/testFileUp}" method="post" enctype="multipart/form-data"> 头像:<input type="file" name="photo"><br> <input type="submit" value="上传"> </form>
①首先文件上传必须使用post方式,通过请求体传输到服务器。其次必须修改enctype为multipart/form-data,即不再以name-value而是以二进制的方式进行传输。
②配置文件上传解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> </bean>
multipartResolver是一个接口,因此注入IOC容器的bean的class必须是它的接口实现类CommonsMultipartResolver。
当文件上传时,IOC容器将二进制形式的文件经过容器中id为multipartResolver的文件解析器转换为服务器的MultipartFile类型,然后再将其传递给控制器方法的形参
@RequestMapping("/testFileUp") public String testFileUp(MultipartFile photo, HttpSession session) throws IOException { String file_name = photo.getOriginalFilename(); ServletContext servletContext = session.getServletContext(); String photo_path = servletContext.getRealPath("path"); File file = new File(photo_path); if(!file.exists()) { file.mkdir(); } String final_path = photo_path + File.separator + file_name; photo.transferTo(new File(final_path) ); return "success"; }
UUID解决文件重名问题
UUID:Universally Unique Identifier 通用唯一识别码.UUID的标准型式包含32个16进位数字,以连字号分为五段,形式为8-4-4-4-12的32个字符,加上“-”一共是36位,所以咱们可以先取出uuid,再把“-”去掉。
文件内容可以进行追加或者覆盖,但是文件不会被覆盖掉,而是生成一个文件副本
@RequestMapping("/testFileUp") public String testFileUp(MultipartFile photo, HttpSession session) throws IOException { String file_name = photo.getOriginalFilename(); String suffix_name = file_name.substring(file_name.lastIndexOf(".")); String uuid = UUID.randomUUID().toString().replaceAll("-", ""); file_name = uuid + suffix_name; ServletContext servletContext = session.getServletContext(); String photo_path = servletContext.getRealPath("path"); File file = new File(photo_path); if(!file.exists()) { file.mkdir(); } String final_path = photo_path + File.separator + file_name; photo.transferTo(new File(final_path) ); return "success"; }
拦截器
SpringMVC拦截器用于拦截控制器方法的执行。拦截器的三个抽象方法,分别在控制器方法执行之前、执行之后以及渲染视图(ModelAndView)完毕之后
区别于过滤器,是用来过滤从浏览器发来的所有请求。
创建拦截器 HandlerInterceptor接口
public class FirstInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
preHandle方法的返回值可以控制是否对控制器方法放行
改为true后的控制台输出:
FirstInterceptor --> preHandle FirstInterceptor --> postHandle FirstInterceptor --> afterCompletion
拦截器的配置
拦截器不属于服务器的三大组件(监听、过滤、servlet),因此是配置在SpringMVC的配置文件中的。
通过内部bean的方式
<mvc:interceptors> <bean class="com.hikaru.interceptor.FirstInterceptor"></bean> </mvc:interceptors>
通过外部bean和注解bean的方式
<mvc:interceptors> <!-- <bean class="com.hikaru.interceptor.FirstInterceptor"></bean>--> <ref bean="firstInterceptor"></ref> </mvc:interceptors>
并对FirestInterceptor添加Component注解,使其加入IOC容器
@Component public class FirstInterceptor implements HandlerInterceptor
上面这两种方式只能对所有请求的控制器方法进行拦截
通过interceptor方式
<mvc:interceptors> <!-- <bean class="com.hikaru.interceptor.FirstInterceptor"></bean>--> <!-- <ref bean="firstInterceptor"></ref>--> <mvc:interceptor> <mvc:mapping path="/*"/> <mvc:exclude-mapping path="/"/> <ref bean="firstInterceptor"></ref> </mvc:interceptor> </mvc:interceptors>
这种方式可以指定拦截请求
多个拦截器的执行顺序
prehabdle按照配置顺序,其他两个方法按照配置顺序的倒序执行
SpringMVC的异常处理
基于配置的异常处理
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolve,HandlerExceptionResolve的实现类有DefaultHandlerExceptionResolve和SimpleMappingExceptionResolve
SpringMVC配置文件配置异常处理器
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="java.lang.ArithmeticException">error</prop> </props> </property> <property name="exceptionAttribute" value="ex"></property> </bean>
其中property的键表示控制器方法执行过程中出现的异常类型(java.lang.ArithmeticException),值为跳转的视图名称
exceptionAttibute属性设置一个属性名,然后会向request域中添加键为该属性名,值为异常的具体信息
前端error页面获取request域中的异常信息:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>Error</h1> <hr> <p th:text="${ex}"></p> </body> </html>
基于注解的异常处理
@ControllerAdvice public class ExceptionController { @ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class}) public String testException(Exception ex, Model model) { model.addAttribute("ex", ex); return "error"; } }
ControllerAdvice注解也是用@Controller标识的,因此是Controller的扩展注解
ExceptionHandl注解value即为处理的异常类
注解配置SpringMVC:使用配置类和注解代替web.xml和Spring.xml配置文件
1 初始化类WebInit替代web.xml
在Servlet3.0环境中,Spring容器会在类路径中查找实现javax.servlet.ServletCotainerInitalizer接口的类,Spring提供了名为SpringServletCotainerInitalizer的接口实现,这个类又反过来查找实现WebApplicationInitalizer (接口) 的类,并将配置Servlet的工作交给它们来进行,Spring3.2又提供了一个便利的WebApplicationInitalizer实现类AbstractAnnotationConfigDispatcherServletInitalizer。当我们的类(WebInit)继承了该实现类并将其部署到Servlet容器时,容器会自动发现它,并用它来部署Servlet上下文。
package com.hikaru.config; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import javax.servlet.Filter; //web工程的初始化类,用来代替web.xml public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer { /** * 指定Spring配置类 * @return */ @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } /** * 指定SpringMVC的配置类 * @return */ @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{WebConfig.class}; } /** *指定DIspatcherServlet前端控制器的映射规则 * @return */ @Override protected String[] getServletMappings() { return new String[]{"/"}; } /** * 注册过滤器 * @return */ @Override protected Filter[] getServletFilters() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); characterEncodingFilter.setForceResponseEncoding(true); HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter(); return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter}; } }
WebInit分别做了:指定Spring、SpringMVC配置类,配置过滤器、监听器的工作
2 WebConfig代替SpringMVC.xml
SpringMVC可以实现的功能:
- 1 扫描组件 2 视图解析器 3 view-Controller 4 default-servlet-handler
- 5 注解驱动 6 文件上传解析 7 异常处理 8 拦截器
1 扫描组件
2 配置视图解析器
5 开启注解驱动
package com.hikaru.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.spring5.view.ThymeleafViewResolver; import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.ServletContextTemplateResolver; /** * 代替SpringMVC.xml * 1 扫描组件 2 视图解析器 3 view-Controller 4 default-servlet-handler * 5 注解驱动 6 文件上传解析器 7 异常处理 8 拦截器 */ //将当前类表示为配置类 @Configuration //1 扫描组件 @ComponentScan(basePackages = {"com.hikaru"}) //5 注解驱动 @EnableWebMvc public class WebConfig { //配置视图解析器 //配置生成模板解析器 @Bean public ITemplateResolver templateResolver() { 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; } //生成模板引擎并为模板引擎注入模板解析器 @Bean public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver); return templateEngine; } //生成视图解析器并未解析器注入模板引擎 @Bean public ViewResolver viewResolver(SpringTemplateEngine templateEngine) { ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setCharacterEncoding("UTF-8"); viewResolver.setTemplateEngine(templateEngine); return viewResolver; } }
3 view-Controller视图控制器
@Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); }
4 default-servlet-handler设置访问静态资源
public class WebConfig implements WebMvcConfigurer
视图解析器配置的都是Bean,但是视图解析器、静态资源不是Bean,因此需要实现特定的接口WebMvcConfigurer
//default-servlet-handler:当前Servlet可访问静态资源 @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); }
8 拦截器
//拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**"); }
拦截器的addPathPatterns方法和excludePathPatterns方法均可接收多个参数
6 文件上传解析器
@Bean public MultipartResolver multipartResolver() { return new CommonsMultipartResolver(); }
在SpringMVC.xml中文件上传解析器就是一个Bean,因此这里也只需要返回一个bean即可
7 异常处理
//拦截器 @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver(); Properties properties = new Properties(); properties.setProperty("java.lang.ArithmeticException", "error"); exceptionResolver.setExceptionMappings(properties); exceptionResolver.setExceptionAttribute("ex"); resolvers.add(exceptionResolver); }
SpringMVC的执行流程
SpringMVC的常用组件
- DispatcherServlet 前端控制器,不需要工程师开发,由框架提供
作用:统一处理请求和响应,整个流程控制的中心,由它调用其他组件处理用户的请求
- HandlerMapping 处理器映射器,由框架提供
作用:根据请求的url、method等信息查找Handler方法
- Handler 处理控制器,工程师开发
作用:在DispatcherServlet控制下Handler对具体的用户请求进行处理
- HandlerAdapter 处理器适配器
作用:通过HandlerAdapter执行控制器方法并最终返回ModelAndView
- ViewResolver 视图解析器
作用:根据视图名称解析得到相应的视图,如ThymeleafView、InternalResourceView、RedirectView
- View 视图
作用:将模型数据通过页面展示给用户
DispatcherServlet初始化过程
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. Map<String, Object> 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); } } }
c>doDispatch()
所在类: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:调用链 包含handler、interceptorList、interceptorIndex handler:浏览器发送的请求所匹配的控制器方法 interceptorList:处理控制器方法的所有拦截器集合 interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行 */ 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(…)方法【逆向】。
-
将渲染结果返回给客户端。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步