【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方式,通过请求体传输到服务器。其次必须修改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的执行流程

  1. 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。

  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:

a) 不存在

i. 再判断是否配置了mvc:default-servlet-handler

ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误

iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误

b) 存在则执行下面的流程

  1. 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。

  2. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。

  3. 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】

  4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息

b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等

c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等

d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

  1. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。

  2. 此时将开始执行拦截器的postHandle(...)方法【逆向】。

  3. 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。

  4. 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。

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

posted @ 2022-04-22 09:36  Tod4  阅读(90)  评论(0)    收藏  举报