SpringMVC启动流程

文章目录

  • 引文
  • Handler
  • HandlerMapper
  • HandlerAdapter
  • @RequestMapping方法参数解析
  • @RequestMapping方法返回值解析
  • 文件上传流程
  • 拦截器解析


SpringMVC启动流程如下

在这里插入图片描述

引文



我们在使用SpringMVC时,传统的方式是在webapp目录下定义一个web.xml文件,比如:

<web-app>
    <servlet>
        <servlet-name>app</servlet-name>
        <servletclass>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

SpringMVC 的启动过程如下所示:

  1. 启动Tomcat

  2. Tomcat读取到web.xml文件,创建DispatcherServlet对象,因为它的load-on-startup配置为1,表示tomcat启动时创建

  3. 调用DispatcherServlet对象的init()方法,因为说到底DispatcherServlet它还是一个Servlet,还是遵守Servlet的生命周期的。

    init()方法中会创建一个Spring容器,并且添加一个ContextRefreshListener监听器,该监听器会监听ContextRefreshedEvent事件(Spring容器创建完成就会发布这个事件)。也就是说spring容器启动完成后就会执行ContextRefreshListener中的onApplicationEvent事件,从而最终会执行到DespatcherServlet中的initStrategies(),这个方法会初始化更多内容:

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        
        // 其中比较重要的就是初始化 HandlerMapper和 HandlerAdapter
        initHandlerMappings(context);
        initHandlerAdapters(context);
        
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
    

我们现在就注重讲讲HandlerMapper和 HandlerAdapter



Handler

什么是Handler?它其实表示请求处理器,在SpringMVC中有四种Handler:

  1. 实现了Controller接口的Bean对象
  2. 实现了HttpRequestHandler接口的Bean对象
  3. 添加了@RequestMapper注解的方法
  4. 一个HandlerFunction对象



详情如下:

实现了Controller接口的Bean对象,这里Bean的名字必须是要以/开头 不然不知道根据什么路径来映射到此方法

@Component("/test")
public class ZhouyuBeanNameController implements Controller {
	@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
 		System.out.println("hushang");
 		return new ModelAndView();}
 }



实现了HttpRequestHandler接口的Bean对象

@Component("/test")
 public class ZhouyuBeanNameController implements HttpRequestHandler {
	@Overridepublic void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("hushang");
	}
}



添加了@RequestMapping注解的方法

@RequestMapping
@Component
public class ZhouyuController {
    
    @RequestMapping(method = RequestMethod.GET, path = "/test")
    @ResponseBody
    public String test(String username) {
        return "hushang";
    }
}



一个HandlerFunction对象(以下代码中有两个):

@ComponentScan("com.hushang")
@Configuration
public class AppConfig {
    
    @Bean
    public RouterFunction<ServerResponse> person() {
        return route()
            .GET("/app/person", request -> ServerResponse.status(HttpStatus.OK).body("Hello GET"))
            .POST("/app/person", request -> ServerResponse.status(HttpStatus.OK).body("Hello POST"))
            .build();
    }
}



HandlerMapper

HandlerMapper就是根据请求路径path去找到Handler,保存的就是路径和Handler之间的映射关系,可以理解为一个Map<path, Handler>

因为Handler有四种,所以SpringMVC中也有不同的HandlerMapper去查找不同的Handler。在SpringMVC中有一个DespatcherServlet.properties文件中有保存,SpringMVC会读取此文件,将其中的HandlerMapper都取出来并遍历,再通过createBean()方法进行创建化各个HandlerMapper,因为是bean,所以在创建过程中会经过BeanPostProcessor去找各个负责的Handler

  • BeanNameUrlHandlerMapping:负责Controller接口和HttpRequestHandler接口
  • RequestMappingHandlerMapping:负责@RequestMapper注解的方法
  • RouterFunctionMapping:负责RouterFunction对象

这些HandlerMapper是Bean对象,所以也有Bean的生命周期,RequestMappingHandlerMapping是在afterPropertiesSet()方法中去找Handler的。



BeanNameUrlHandlerMapping的寻找流程:

  1. 找出Spring容器中所有的BeanName

  2. 判断BeanName是不是以 / 开头

  3. 如果是,则把它当成一个Handler,并把beanName作为Key,Bean对象作为Value存入HandlerMapper中

  4. HandlerMapper就是一个Map



RequestMappingHandlerMapping的寻找流程:

  1. 找出Spring容器中所有的BeanType

  2. 判断BeanType是否有@Controller注解或@RequestMapping注解

  3. 判断成功则继续找加@RequestMapping注解的method

  4. 并解析@RequestMapping注解中的内容,比如method、path封装为一个RequestMappingInfo对象

  5. 最后把RequestMappingInfo对象作为Key,Method对象封装为HandlerMapper对象后作为value,存入registry中

    先通过path找到RequestMappingInfo对象,进行注解一些信息的匹配,比如请求方式是否满足,在通过RequestMappingInfo作为key再去拿到具体要执行的Method

  6. registry就是一个Map

RouterFunctionMapping的寻找流程会有些区别,但是大体是差不多的,相当于是一个path对应一个 HandlerFunction。



各个HandlerMapping除开负责寻找Handler并记录映射关系之外,自然还需要根据请求路径找到对应的Handler,在源码中这三个HandlerMapping有一个共同的父类AbstractHandlerMapping

AbstractHandlerMapping实现了HandlerMapping接口,并实现了getHandler(HttpServletRequest request)方法。

在这里插入图片描述



AbstractHandlerMapping会负责调用子类的getHandlerInternal(HttpServletRequest request)方法从而找到请求对应的Handler,然后AbstractHandlerMapping负责将Handler和应用中所配置的 HandlerInterceptor整合成为一个HandlerExecutionChain对象。

所以寻找Handler的源码实现在各个HandlerMapping子类中的getHandlerInternal()中,根据请求路径找到Handler的过程并不复杂,因为路径和Handler的映射关系已经存在Map中了。

比较困难的点在于,当DispatcherServlet接收到一个请求时,该利用哪个HandlerMapping来寻找 Handler呢?看源码:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

从源码中可以看出来,就是遍历,三个HandlerMapper一个一个的遍历 ,通过request对象找到了就返回

默认遍历的顺序是

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

所以BeanNameUrlHandlerMapping的优先级最高,所以如果一个实现了Controller接口的Bean和@RequestMapping注解修饰的方法,他们两个的path都是/test,但是最终是Controller接口的会生效。

至此,就通过path找到了Handler,接下来就是要去执行相应的Handler了



HandlerAdapter

找到了Handler之后,接下来就该去执行了,比如执行下面这个test()

@RequestMapping(method = RequestMethod.GET, path = "/test")
@ResponseBody
public String test(String username) {
	return "hushang";
}



入口是在DispatchServlet类的doService()方法,再调用doDispatch(request, response)方法。

目前有四种Handler,各个Handler的执行方式也是不一样的,如下所示

  • 实现了Controller接口的Bean对象,执行的是Bean对象中的handleRequest()
  • 实现了HttpRequestHandler接口的Bean对象,执行的是Bean对象中的handleRequest()
  • 添加了@RequestMapping注解的方法,具体为一个HandlerMethod,执行的就是当前加了注解的方法
  • 一个HandlerFunction对象,执行的是HandlerFunction对象中的handle()



按照我们现在的想法,处理的方式可能是如下所示

Object handler = mappedHandler.getHandler();
if (handler instanceof Controller) {
    ((Controller)handler).handleRequest(request, response);
} else if (handler instanceof HttpRequestHandler) {
    ((HttpRequestHandler)handler).handleRequest(request, response);
} else if (handler instanceof HandlerMethod) {
    ((HandlerMethod)handler).getMethod().invoke(...);
} else if (handler instanceof HandlerFunction) {
    ((HandlerFunction)handler).handle(...);
}



但是为了扩展性,SpringMVC是采用的适配模式,把不同的Handler适配成一个HandlerAdapter,后续再去执行HandlerAdapter的handle()方法,这样就执行不同种类的Handler对应的方法了

在DespatchServlet.properties文件中也有配置,针对不同的Handler也有不同的HandlerAdapter

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter



具体逻辑是

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    // 这里的this.handlerAdapters就是上面properties文件中配置的四种HandlerAdapter
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            // 这里会调用各种HandlerAdapter的supports()方法,如下所示,其实就是进行类型的判断 如果满足就返回true  这里就返回当前适配器
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("如果一个HandlerAdapter都没有匹配上就会抛异常......");
}



// 就拿SimpleControllerHandlerAdapter来举例,就是判断当前Handler是否实现了Controller接口,我们最常用的RequestMappingHandlerAdapter它的supports()方法在它的父类中
public boolean supports(Object handler) {
    return (handler instanceof Controller);
}



根据Handler适配出了对应的HandlerAdapter后,就执行具体HandlerAdapter对象的handle()方法 了,因为这四种最终都是实现了HandlerAdapter接口,所以这里也就是直接调用各种HandlerAdapter对象的handler()方法

比如SimpleControllerHandlerAdapter,就是强制转换后直接执行方法

@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {

   return ((Controller) handler).handleRequest(request, response);
}

其他两个HandlerAdapter也一样很简单,逻辑比较复杂的就是RequestMappingHandlerAdapter中的handler()方法,它执行的是加了@RequestMapping的方法,而这种方法的写法可以是多种多样,SpringMVC需要根据方法的定义去解析Request对象,从请求中获取出对应的数据然后传递给方法,并执行。

现在的这几种HandlerAdapter也是Bean对象,就比如RequestMappingHandlerAdapter,在创建它的时候也会去调用InitializingBean.afterPropertiesSet()方法去创建各种方法参数解析器(HandlerMethodArgumentResolver)和返回值解析器(HandlerMethodReturnValueHandler)

@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    initControllerAdviceCache();
	
    // 参数解析器
    if (this.argumentResolvers == null) {
        // getDefaultArgumentResolvers()就会去创建很多的方法参数解析器
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // 数据绑定器参数解析器
    if (this.initBinderArgumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // 返回值解析器
    if (this.returnValueHandlers == null) {
        // getDefaultReturnValueHandlers()就会去创建很多的返回值解析器
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}



@RequestMapping方法参数解析

当SpringMVC接收到请求,并通过HandlerMapper找到对应的Method后,就该执行该方法了,不过在执行之前需要根据方法定义的形参,从请求中获取到对应的值,然后将数据传递给方法并执行。

一个HttpServletRequest通常有:

  • request parameter
  • reqeust attribute
  • reqest session
  • request header
  • request body



比如下面几个方法

// 对应的前端url中传的值
// 表示要从request parameter中获取key为username的值
public String test(String username) {
    return "hushang";
}


// 表示要从request parameter中获取key为uname的value
public String test(@RequestParam("uname") String username) {
    return "hushang";
}

// 对应的是后端拦截器中自己的处理,对处理后的数据 用httpServletRequest.setAttribute(name, value)存
// 表示要从request attribute中获取key为username的value
public String test(@RequestAttribute String username) {
    return "hushang";
}


// 	表示要从request session中获取key为username的value
public String test(@SessionAttribute String username) {
    return "hushang";
}

// 表示要从request header中获取key为username的value
public String test(@RequestHeader String username) {
    return "hushang";
}

// 表示获取整个请求体
public String test(@RequestBody String username) {
    return "hushang";
}



SpringMVC在解析方法参数时,就需要看参数到底是获取请求的哪些数据,源码中是通过HandlerMethodArgumentResolver来实现的,比如:

  • RequestParamMethodArgumentResolver:负责处理@RequestParam
  • RequestHeaderMethodArgumentResolver:负责处理@RequestHeader
  • SessionAttributeMethodArgumentResolver:负责处理@SessionAttribute
  • RequestAttributeMethodArgumentResolver:负责处理@RequestAttribute
  • RequestResponseBodyMethodProcessor:负责处理@RequestBody
  • 还有很多其他的…



在判断到底需要由哪一个HandlerMethodArgumentResolver来处理时,源码中就是直接遍历,然后分别调用他们的supportsParameter()方法判断是否支持

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
        // 循环遍历ArgumentResolver
        for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
            
            // 判断各个ArgumentResolver是否支持解析当前参数,如果支持就break 并返回
            if (methodArgumentResolver.supportsParameter(parameter)) {
                result = methodArgumentResolver;
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}

当这里找到HandlerMethodArgumentResolver 并返回之后,就会调用他们的resolveArgument()方法真正去获取值



@RequestMapping方法返回值解析

而方法的返回值也有不同的情况,比如返回值是String,如果加了@ResponseBody注解则将String返回给浏览器,如果没有加此注解则根据这个String找到对应的页面返回。

在SpringMVC中,是通过HandlerMethodReturnValueHandler来处理返回值的

  • RequestResponseBodyMethodProcessor:处理加了@ResponseBody注解的情况
  • ViewNameMethodReturnValueHandler:处理没有加@ResponseBody注解并且返回值类型为String的情况
  • ModelMethodProcessor:处理返回值是Model类型的情况
  • 还有很多其他的…



这里就着重介绍RequestResponseBodyMethodProcessor,因为它处理的是加了@ResponseBody注解的情况,也是我们用的最多的情况。

我们如果返回String那还好,直接返回给浏览器,如果返回的是Map或者是Object这种复杂对象该如何处理再返回给浏览器嘞?

处理这块,SpringMVC会利用HttpMessageConverter来处理,比如默认情况下,SpringMVC会有4个HttpMessageConverter:

  • ByteArrayHttpMessageConverter:处理返回值为字节数组的情况,把字节数组返回给浏览器
  • StringHttpMessageConverter:处理返回值为字符串的情况,把字符串按指定的编码序列号后返回给浏览器
  • SourceHttpMessageConverter:处理返回值为XML对象的情况,比如把DOMSource对象返回给浏览器
  • AllEncompassingFormHttpMessageConverter:处理返回值为MultiValueMap对象的情况



StringHttpMessageConverter的源码也比较简单:

protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
    HttpHeaders headers = outputMessage.getHeaders();
    if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) {
        headers.setAcceptCharset(getAcceptedCharsets());
    }
    Charset charset = getContentTypeCharset(headers.getContentType());
    StreamUtils.copy(str, charset, outputMessage.getBody());
}



先看有没有设置Content-Type,如果没有设置则取默认的,默认为ISO-8859-1,所以默认情况下返 回中文会乱码,可以通过以下来中方式来解决:

@RequestMapping(method = RequestMethod.GET, path = "/test", produces = {"application/json;charset=UTF-8"})
......
@ComponentScan("com.hushang")
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        StringHttpMessageConverter messageConverter = new StringHttpMessageConverter();
        messageConverter.setDefaultCharset(StandardCharsets.UTF_8);
        converters.add(messageConverter);
    }
}



不过以上四个Converter是不能处理Map对象或User对象的,所以如果返回的是Map或User对象,那么得单独配置一个Converter,比如MappingJackson2HttpMessageConverter,这个Converter比较强大,能把String、Map、User对象等等都能转化成JSON格式。

@ComponentScan("com.hushang")
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        messageConverter.setDefaultCharset(StandardCharsets.UTF_8);
        converters.add(messageConverter);
    }
}



文件上传流程

我们要在SpringMvc中使用文件上传,刚开始我们会配置一个bean

默认的multipartResolver是StandardServletMultipartResolver,我这里使用CommonsMultipartResolver
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">



入口是DispatcherServlet.doDispatch(),请求刚开始就是经过multipartResolver去判断请求参数是否是文件上传multipart类型,如果是则将所有的文件类型form表单对应的文件part保存至一个Map中

接下来等到RequestParamMethodArgumentResolver这个参数解析器去解析,根据controller方法中的参数名去从这个Map中取

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ...

        try {
            // 请求刚开始就检查请求参数是否有Multipart文件上传对象
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // 进行映射
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // 找到最合适的HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            ...

            // Actually invoke the handler.
            // 具体执行handle
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            ...
        }
}

接下来是checkMultipart()方法

// 我们自己配置了一个multipartResolver的bean,各个具体的子类有各自的实现逻辑,这里就拿StandardServletMultipartResolver举例
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // 判断是不是文件上传请求
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if 
            ...
        else {
            try {
                // 去解析文件上传请求
                return this.multipartResolver.resolveMultipart(request);
            }
            catch (MultipartException ex) {
                ...
            }
        }
    }
    // If not returned before: return original request.
    return request;
}
// new一个StandardMultipartHttpServletRequest对象返回,注意这个类型的HttpServletRequest在下面的代码中会出现
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
    return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}


// 在创建StandardMultipartHttpServletRequest对象时 构造方法中就会调用下面的方法
private void parseRequest(HttpServletRequest request) {
    try {
        Collection<Part> parts = request.getParts();
        this.multipartParameterNames = new LinkedHashSet<>(parts.size());
        // 保存结果的Map
        MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
        // 遍历表单的每一个part,也就是form表单的每一行请求参数
        for (Part part : parts) {
            String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
            ContentDisposition disposition = ContentDisposition.parse(headerValue);
            String filename = disposition.getFilename();
            // 如果part是文件,那么就会有filename,文本类型就没有
            if (filename != null) {
                if (filename.startsWith("=?") && filename.endsWith("?=")) {
                    filename = MimeDelegate.decode(filename);
                }
                // 文件类型的part添加进行集合
                files.add(part.getName(), new StandardMultipartFile(part, filename));
            }
            else {
                this.multipartParameterNames.add(part.getName());
            }
        }
        // 将找出来的这个map传给父类中的multipartFiles这个属性
        // 然后就轮到了RequestParamMethodArgumentResolver这个参数解析器去解析,从这个Map中取
        setMultipartFiles(files);
    }
    catch (Throwable ex) {
        handleParseFailure(ex);
    }
}



接下来等到RequestParamMethodArgumentResolver这个参数解析器去解析,根据controller方法中的参数名去从这个Map中取

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

    if (servletRequest != null) {
        // 这里对文件类型的参数进行处理,去上面存入Map中找对应的文件part
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            return mpArg;
        }
    }

    Object arg = null;
    MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
    if (multipartRequest != null) {
        List<MultipartFile> files = multipartRequest.getFiles(name);
        if (!files.isEmpty()) {
            arg = (files.size() == 1 ? files.get(0) : files);
        }
    }
    if (arg == null) {// *解析参数值 : request.getParameter方式
        String[] paramValues = request.getParameterValues(name);
        if (paramValues != null) {
            arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
        }
    }
    return arg;
}
public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
    throws Exception {

    MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
    boolean isMultipart = (multipartRequest != null || isMultipartContent(request));

    // 如果参数类型是MultipartFile
    if (MultipartFile.class == parameter.getNestedParameterType()) {
        if (!isMultipart) {
            return null;
        }
        if (multipartRequest == null) {
            multipartRequest = new StandardMultipartHttpServletRequest(request);
        }
        // 文件上传请求刚进来时就经过文件上传类型判断,将所有的上传文件form表单中的part都存入了一个Map中
        // 根据方法参数中的name,去Map中找
        return multipartRequest.getFile(name);
    }
   ......
}



拦截器解析

拦截器的具体实现是我们自定义一个类,实现下面的接口,

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;

public interface HandlerInterceptor {

    // 在执行handle之前执行,如果返回了false则表示当前请求被拦截了,不会执行后续的方法了
    // 即使该方法返回了false,最下面的afterCompletion()方法也会执行
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}


    // 如果上面的preHandle()方法返回了false,或者是handle执行除了异常,该方法都不会执行
    // 在handle正常执行结束后执行该方法
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	// 一次请求完成后,最终都会调用该方法
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}

}



底层实现,入口还是DispatcherServlet.doDispatch()

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 {
            // 检查请求参数是否有Multipart文件上传对象
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // 进行映射,通过handlerMapper获取到Handler
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // 找到最合适的HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            ...
                
            // 前置拦截器
            // 如果前置拦截器返回了false,就不会之后handle方法了,表示当前请求被拦截了
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                // 返回false就不进行后续处理了
                return;
            }

            // 具体执行handle
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            applyDefaultViewName(processedRequest, mv);
            
            //执行后置拦截器
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            ...
        }
        
        // 渲染视图
        // 同时,这里最后还会执行拦截器的afterCompletion()方法
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        // 这里会执行拦截器的afterCompletion()方法
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        // 这里会执行拦截器的afterCompletion()方法
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                               new NestedServletException("Handler processing failed", err));
    }
    finally {
        ...
    }
}
// 执行前置拦截的方式
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 循环遍历Interceptor
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        // 执行拦截器的preHandle()方法
        // 如果前置拦截器返回了false,就不会之后handle方法了,表示当前请求被拦截了
        if (!interceptor.preHandle(request, response, this.handler)) {
            // 但是还是会执行拦截器的afterCompletion()方法
            triggerAfterCompletion(request, response, null);
            return false;
        }
        this.interceptorIndex = i;
    }
    return true;
}


void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
    throws Exception {

    // 执行各个拦截器的postHandle()方法,倒序的方式执行
    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        interceptor.postHandle(request, response, this.handler, mv);
    }
}


void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    // 执行拦截器的afterCompletion()方法,这里是倒序的方式执行
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        }
        catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/755027.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

npm安装依赖报错——npm ERR gyp verb cli的解决方法

1. 问题描述 1.1 npm安装依赖报错——npm ERR! gyp verb cli npm MARN deprecated axiosQ0.18.1: critical security vuLnerability fixed in v0.21.1. For more information, npm WARN deprecated svg001.3.2: This SVGO version is no Longer supported. upgrade to v2.x.x …

秋招突击——第七弹——Redis快速入门

文章目录 引言Redis是什么 正文对象String字符串面试重点 List面试考点 压缩列表ZipList面试题 Set面试题讲解 Hash面试重点 HASHTABLE底层面试考点 跳表面试重点 ZSET有序链表面试重点 总结 引言 在项目和redis之间&#xff0c;我犹豫了一下&#xff0c;觉得还是了解学习一下…

大数据信用报告查询应该选什么样的平台?

随着大数据技术的不断发展&#xff0c;大数据信用报告查询平台也应运而生。这些平台通过数据挖掘和分析&#xff0c;为个人提供有关大数据信用的详细报告&#xff0c;帮助他们在做出决策时获得更多的信息。然而&#xff0c;面对众多的大数据信用报告查询平台&#xff0c;如何选…

使用Apache POI库在Java中导出Excel文件的详细步骤

使用Apache POI库在Java中导出Excel文件的详细步骤 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技…

240628_昇思学习打卡-Day10-SSD目标检测

240628_昇思学习打卡-Day10-SSD目标检测 今天我们来看SSD&#xff08;Single Shot MultiBox Detector&#xff09;算法&#xff0c;SSD是发布于2016年的一种目标检测算法&#xff0c;使用的是one-stage目标检测网络&#xff0c;意思就是说它只需要一步&#xff0c;就能把目标检…

hive调优原理详解:案例解析参数配置(第17天)

系列文章目录 一、Hive常问面试函数&#xff08;掌握&#xff09; 二、Hive调优如何配置&#xff08;重点&#xff09; 文章目录 系列文章目录前言一、Hive函数&#xff08;掌握&#xff09;11、JSON数据处理12、炸裂函数13、高频面试题13.1 行转列13.2 列转行 14、开窗函数&a…

免交互和嵌入执行模式

目录 概念 语法格式 统计行数 赋值变量 修改密码​编辑往文件里添加内容 ​编辑​编辑引入变量 整体赋值​编辑 加引号不赋值变量 expect实现免交互 免交互设置密码 免交互切换用户 嵌入执行模式 添加用户并免交互设置密码 免交互登录 传参实现ssh 练习 概念 …

pyqt5 制作视频剪辑软件,切割视频

该软件用于切割视频&#xff0c;手动选取视频片段的起始帧和结束帧并保存为json文件。gui界面如下&#xff1a;包含快进、快退、暂停等功能&#xff0c; 代码如下&#xff1a; # codingUTF-8 """ theme: pyqt5实现动作起始帧和结束帧的定位&#xff0c;将定位到…

python-docx 设置水印字体

本文目录 前言一、水印的XML在哪里1、Word内置水印设置2、自定义XML部件3、Header or Footer二、确认位置三、水印表前解释1、水印XML源代码2、水印结构解析3、关于style的详解三、修改水印样式前言 本文我们来完成一个有趣的玩意儿:在Python中通过操作Word文档的XML来设置整…

Maven - 在没有网络的情况下强制使用本地jar包

文章目录 问题解决思路解决办法删除 _remote.repositories 文件代码手动操作步骤验证 问题 非互联网环境&#xff0c;无法从中央仓库or镜像里拉取jar包。 服务器上搭建了一套Nexus私服。 Nexus私服故障&#xff0c;无法连接。 工程里新增了一个Jar的依赖&#xff0c; 本地仓…

如何利用React和Python构建强大的网络爬虫应用

如何利用React和Python构建强大的网络爬虫应用 引言&#xff1a; 网络爬虫是一种自动化程序&#xff0c;用于通过互联网抓取网页数据。随着互联网的不断发展和数据的爆炸式增长&#xff0c;网络爬虫越来越受欢迎。本文将介绍如何利用React和Python这两种流行的技术&#xff0c…

成功解决ES高亮内容引起的字段显示不一致问题

在处理搜索引擎&#xff08;如Elasticsearch&#xff09;结果时&#xff0c;常见需求之一是对用户搜索的关键词进行高亮显示&#xff0c;这有助于用户快速识别搜索结果为何与其查询相关。但在实际应用中&#xff0c;如果处理不当&#xff0c;直接使用高亮片段可能会导致原始数据…

SVN 的忽略(Ignore)和递归(Recursively)以及忽略部分

SVN中忽略大家经常用到&#xff0c;但总是似懂非懂&#xff0c;下面就详细展开说明一下忽略如何设置。 两个忽略 通常设置忽略都是文件夹和里面的文件都忽略。 设置忽略我们通常只需要鼠标右键点击忽略就可以了&#xff0c;如图&#xff1a; 第一个忽略用的最多&#xff0c;…

AI问答-供应链管理:中的长鞭效应(Bullwhip Effect)/ 供应链中需求信息变异放大现象

供应链管理中的长鞭效应&#xff08;Bullwhip Effect&#xff09;是一个经济学上的术语&#xff0c;它描述了供应链中需求信息变异放大的现象。以下是关于长鞭效应的详细解释&#xff1a; 一、定义 长鞭效应&#xff0c;也被称为“需求变异加速放大原理”或“牛鞭效应”&…

乐鑫 Matter 技术体验日|快速落地 Matter 产品,引领智能家居生态新发展

随着 Matter 协议的推广和普及&#xff0c;智能家居行业正迎来新的发展机遇&#xff0c;众多厂商纷纷投身于 Matter 产品的研发与验证。然而&#xff0c;开发者普遍面临技术门槛高、认证流程繁琐、生产管理复杂等诸多挑战。 乐鑫信息科技 (688018.SH) 凭借深厚的研发实力与行…

Python酷库之旅-第三方库openpyxl(15)

目录 一、 openpyxl库的由来 1、背景 2、起源 3、发展 4、特点 4-1、支持.xlsx格式 4-2、读写Excel文件 4-3、操作单元格 4-4、创建和修改工作表 4-5、样式设置 4-6、图表和公式 4-7、支持数字和日期格式 二、openpyxl库的优缺点 1、优点 1-1、支持现代Excel格式…

一、音视频基础

音视频基础 一、音视频录制原理二、音视频播放原理三、图像表示RGB-YUVV1.图像基础概念1.1 像素1.2 分辨率1.3 位深1.4 帧率1.5 码率1.6 Stride跨距 2.RGB、YUV深入讲解2.1 RGB2.2 YUV2.2.1 YUV采样表示法2.2.2 YUV数据存储 2.3 RGB和YUV的转换(了解)为什么解码出错显示绿屏&am…

借助 Aspose.Words,在 C# 中将 Word 转换为 Excel

有时我们会遇到需要将 Word 文档&#xff08;DOC 或 DOCX&#xff09;转换为 Excel 文档的任务。例如&#xff0c;这对于数据分析和报告很有用&#xff0c;或者如果您收到了任何文本数据并想将其转换为表格格式&#xff08;XLS 或 XLSX&#xff09;以便进一步工作。在本文中&am…

【DevExpress】WPF DevExpressMVVM 24.1版本开发指南

DevExpressMVVM WPF 环境安装 前言重要Bug&#xff08;必看&#xff09;环境安装控件目录Theme 主题LoginWindow 登陆窗口INavigationService 导航服务DockLayout Dock类型的画面布局TreeView 树状列表注意引用类型的时候ImageSource是PresentationCore程序集的博主找了好久&am…

AV Foundation学习笔记二 - 播放器

ASSets AVFoundation框架的最核心的类是AVAsset&#xff0c;该类是整个AVFoundation框架设计的中心。AVAsset是一个抽象的&#xff08;意味着你不能调用AVAsset的alloc或者new方法来创建一个AVAsset实例对象&#xff0c;而是通过该类的静态方法来创建实例对象&#xff09;、不…