【SpringMVC】(三)

2023-07-11

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方式,通过请求体传输到服务器。其次必须修改enctypemultipart/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的实现类有DefaultHandlerExceptionResolveSimpleMappingExceptionResolve

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(…)方法【逆向】。

    将渲染结果返回给客户端。

【SpringMVC】(三)的相关教程结束。

《【SpringMVC】(三).doc》

下载本文的Word格式文档,以方便收藏与打印。