SpringMVC启动流程

news2024/11/26 9:54:28

文章目录

  • 引文
  • 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);
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无忧易售新功能:一键白底转换,升级产品图片质感

在电商领域不断追求卓越与效率的今天&#xff0c;无忧易售ERP推出一键白底转换功能&#xff0c;为卖家们提供前所未有的便捷与高效&#xff0c;改变了商品图片处理的传统模式&#xff0c;革新了卖家们的图片处理体验&#xff0c;让商品展示焕然一新&#xff0c;助力商家在激烈的…

点云入门知识

点云的处理任务 场景语义分割 物体的三维表达方法&#xff08;3D representations&#xff09;&#xff1a; 点云&#xff1a;是由物体表面上许多点数据来表征这个物体。最接近原始传感器数据&#xff0c;且具有丰富的几何信息。 Mesh&#xff1a;用三角形面片和正方形面片拼…

计算机人说学校-北京理工大学-计算机方向

1. 专长、特点、特色 北京理工大学&#xff08;北理工&#xff09;的计算机专业同样具有显著的优势和特点&#xff1a; 学术水平高&#xff1a;作为一所985高校&#xff0c;北理工在计算机科学与技术以及人工智能领域都有着较高的学术水平和教学资源。研究方向广泛&#xff1…

阿里云服务器数据库迁云: 数据从传统到云端的安全之旅(WordPress个人博客实战教学)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 一、 开始实战1.2创建实验资源1.3重置云服务器ECS的登录密码&#xff08;请记住密码&#xff09;1.4 设置安全组端口1…

akamai指纹自由

最近项目的部分业务设计到了akamai的技术。也是第一次接触&#xff0c;于是网上搜了一下&#xff0c;相关的帖子和文章很多&#xff0c;在前人共享的资料的基础上&#xff0c;自己这几天终于完全弄通了。 先上结果 akmai的针对策略就2个&#xff0c;第一个是tls验证&#xff0…

【ElementPlus源码】Container 布局容器

文章目录 index.tsContainerheaderutilswithInstallwithNoopInstall hooksuseNamespace 单元测试 看源码时候做的笔记。如有错误请指出&#xff01; 关于路径的省略&#xff0c;详见button&#xff1a;【ElementPlus源码】Button按钮-CSDN博客 index.ts 导入一堆组件&#xff…

(南京观海微电子)——三极管原理及应用区别

PNP与NPN三极管的原理与使用方法 三极管主要的功能是电流放大和开关作用。 三极管按材料分有两种&#xff1a;锗管和硅管。而每一种又有NPN和PNP两种结构形式&#xff0c;但使用最多的是硅NPN和PNP两种三极管&#xff0c;两者除了电源极性不同外&#xff0c;其工作原理都是相同…

编程开发不得不懂的世界协调时UTC的由来

在各种时间标准出现之前&#xff0c;各地都是根据太阳来进行计时的。把太阳连续2次经过地球同一位置所经历的时间间隔称为真太阳日&#xff0c;然后再把这个太阳日划分为更小的时间单位&#xff0c;例如中国古代使用日晷记录时间&#xff0c;把一个太阳日分为12个时辰。因为地球…

排序(冒泡排序、选择排序、插入排序、希尔排序)-->深度剖析(一)

欢迎来到我的Blog&#xff0c;点击关注哦&#x1f495; 前言 排序是一种基本的数据处理操作&#xff0c;它涉及将一系列项目重新排列&#xff0c;以便按照指定的标准&#xff08;通常是数值大小&#xff09;进行排序。在C语言中&#xff0c;排序算法是用来对元素进行排序的一系…

竞赛选题 python的搜索引擎系统设计与实现

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python的搜索引擎系统设计与实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;5分创新点&#xff1a;3分 该项目较为新颖&#xff…

昇思25天学习打卡营第04天|数据集 Dataset

数据是深度学习的基础&#xff0c;高质量的数据输入将在整个深度神经网络中起到积极作用。MindSpore提供基于Pipeline的数据引擎&#xff0c;通过数据集&#xff08;Dataset&#xff09;和数据变换&#xff08;Transforms&#xff09;实现高效的数据预处理。其中Dataset是Pipel…

【机器学习】基于层次的聚类方法:理论与实践

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 基于层次的聚类方法&#xff1a;理论与实践引言1. 层次聚类基础1.1 概述1.2 距离…

decode()方法——解码字符串

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 解码是将字节流转换成字符串&#xff08;文本&#xff09;&#xff0c;其他编码格式转成unicode。在Python中提供了decode()方法&#xff0…

GS NVMe全闪存储通过XFS文件系统助力太空科研AI处理

用户是名列全球TOP 5的太空研究机构&#xff0c;专为各种卫星任务和应用开发有效载荷、仪器及天基系统&#xff0c;在通信、广播、导航、灾害监测、气象学、海洋学、环境监测&#xff0c;以及自然资源测量等方面发挥重要的作用&#xff0c;为探索月球、火星等天体做出了重大的贡…

pafination官网自制

1.pafination.js 参考element ui 中 prev表示上一页&#xff0c;next为下一页 // const itemsPerPage 10; // const totalItems 30; var itemsPerPage ; var totalItems ; let currentPage 1; var pagerCount5 // 设置最大页码按钮数 var totalPages Math.ceil(totalItem…

Flutter循序渐进==>封装、继承、多态、抽象类以及属性修改

导言 新学一门编程语言&#xff0c;最难以理解的莫过于类了。如果类没用&#xff0c;也就算了&#xff0c;它偏偏很有用&#xff0c;我们必须得掌握&#xff0c;不然怎么好意思说自己会面向对象编程呢? 抽象类&#xff08;Abstract Class&#xff09;在面向对象编程中扮演着…

前后端分离项目面试总结

一&#xff1a;是否登录状态 服务端登录的时候&#xff0c;给分配一个session用于存储数据&#xff0c;同时将sessionID返回给浏览器&#xff0c;浏览器通过cookie把sessionID存储起来&#xff0c;下次访问时携带上&#xff0c;服务端就可以通过sessionID来确定用户是否登录。 …

uview文本框组件计数count报错u--textarea

报错内容&#xff1a; [Vue warn]: Error in render: “TypeError: Cannot read property ‘length’ of null” found in —> at uni_modules/uview-ui/components/u-textarea/u-textarea.vue at uni_modules/uview-ui/components/u–textarea/u–textarea.vue mp.runtime.…

Flutter循序渐进==>基金管理APP首页

目录 查看版本 组件 组件源码学习 做个基金APP首页源代码 效果 查看版本 组件 组件的本质就是个类。 import package:flutter/material.dart;void main() {runApp(const OurFirstApp(),); } OurFirstApp()实例化&#xff0c;就是给runApp用的&#xff0c;runApp就是运行实…

Java的NIO体系

目录 NIO1、操作系统级别下的IO模型有哪些&#xff1f;2、Java语言下的IO模型有哪些&#xff1f;3、Java的NIO应用场景&#xff1f;相比于IO的优势在哪&#xff1f;4、Java的IO、NIO、AIO 操作文件读写5、NIO的核心类 :Buffer&#xff08;缓冲区&#xff09;、Channel&#xff…