SpringMVC启动流程

news/2024/7/8 5:13:58 标签: java

文章目录

  • 引文
  • 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(),这个方法会初始化更多内容:

    java">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的名字必须是要以/开头 不然不知道根据什么路径来映射到此方法

java">@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对象

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



添加了@RequestMapping注解的方法

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



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

java">@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呢?看源码:

java">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()

java">@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()



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

java">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



具体逻辑是

java">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,就是强制转换后直接执行方法

java">@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)

java">@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



比如下面几个方法

java">// 对应的前端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()方法判断是否支持

java">@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的源码也比较简单:

java">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,所以默认情况下返 回中文会乱码,可以通过以下来中方式来解决:

java">@RequestMapping(method = RequestMethod.GET, path = "/test", produces = {"application/json;charset=UTF-8"})
......
java">@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格式。

java">@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中取

java">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()方法

java">// 我们自己配置了一个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;
}
java">// 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中取

java">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;
}
java">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);
    }
   ......
}



拦截器解析

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

java">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()

java">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 {
        ...
    }
}
java">// 执行前置拦截的方式
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);
        }
    }
}

http://www.niftyadmin.cn/n/5536530.html

相关文章

【论文通读】GUICourse: From General Vision Language Model to Versatile GUI Agent

GUICourse: From General Vision Language Model to Versatile GUI Agent 前言AbstractMotivationSolutionGUICourseGUIEnvGUIEnv-globalGUIEnv-local GUIActGUIAct (web-single)GUIAct (web-multi)GUIAct (smartphone) GUIChat ExperimentsMain ResultAblation Study Conclusi…

使用Python绘制堆积柱形图

使用Python绘制堆积柱形图 堆积柱形图效果代码 堆积柱形图 堆积柱形图&#xff08;Stacked Bar Chart&#xff09;是一种数据可视化图表&#xff0c;用于显示不同类别的数值在某一变量上的累积情况。每一个柱状条显示多个子类别的数值&#xff0c;子类别的数值在柱状条上堆积在…

【matlab 路径规划】基于改进遗传粒子群算法的药店配送路径优化

一 背景介绍 本文分享的是一个基于订单合并的订单分配和路径规划联合优化&#xff0c;主要背景是骑手根据客户需求&#xff0c;从药店取药之后进行配送&#xff0c;配送的过程中考虑路径的长度、客户的服务时间窗、车辆的固定成本等要素&#xff0c;经过建模和优化得到最优的配…

LabVIEW自动探头外观检测

开发了一套基于LabVIEW的软件系统&#xff0c;结合视觉检测技术&#xff0c;实现探头及连接器外观的自动检测。通过使用高分辨率工业相机、光源和机械手臂&#xff0c;系统能够自动定位并检测探头表面的细微缺陷&#xff0c;如划痕、残胶、异色、杂物等。系统支持多种探头形态&…

在Ubuntu 16.04上安装和配置ownCloud的方法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 简介 ownCloud 是一个文件共享服务器&#xff0c;允许您将个人内容&#xff08;如文档和图片&#xff09;存储在一个类似 Dropbox 的集…

推动高效能:东芝TB67H301FTG全桥直流电机驱动IC

在如今高度自动化的时代&#xff0c;电子产品的性能和效率成为了工程师们关注的焦点。东芝的TB67H301FTG全桥直流电机驱动IC应运而生&#xff0c;以其卓越的技术和可靠性&#xff0c;成为众多应用的理想选择。无论是在机器人、家用电器、工业自动化&#xff0c;还是在其他需要精…

微信小程序的自定义组件

小程序目前已经支持组件化开发&#xff0c;可以将页面中的功能模块抽取成自定义组件&#xff0c;以便在不同的页面中重复使用&#xff1b; 也可以将复杂的页面拆分成多个低耦合的模块&#xff0c;有助于代码维护 常见组件 开发中常见的组件有两种&#xff1a; 1. 公共组件&am…

C# Winform PropertyGrid显示中文

主要原理是在枚举上添加DescriptionAttribute属性&#xff0c;然后通过反射将其显示出来 方法1&#xff1a;继承StringConverter类 public class EnumConvertor : StringConverter {public override bool GetStandardValuesSupported(ITypeDescriptorContext context){return…