14.SpringMVC啓動與請求處理流程解析

原理流程圖:https://www.processon.com/view/link/63f1d5cc2f69f86c1f96ee9c

SpringMVC的作用毋庸置疑,雖然我們現在都是用SpringBoot,但是SpringBoot中仍然是在使用SpringMVC來處理請求。

我們在使用SpringMVC時,傳統的方式是通過定義web.xml,比如:

<web-app>

	<servlet>
		<servlet-name>app</servlet-name>
		<servlet-class>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>

我們只要定義這樣的一個web.xml,然後啓動Tomcat,那麼我們就能正常使用SpringMVC了。

SpringMVC中,最爲核心的就是DispatcherServlet,在啓動Tomcat的過程中:

  1. Tomcat會先創建DispatcherServlet對象
  2. 然後調用DispatcherServlet對象的init()

而在init()方法中,會創建一個Spring容器,並且添加一個ContextRefreshListener監聽器,該監聽器會監聽ContextRefreshedEvent事件(Spring容器啓動完成後就會發布這個事件),也就是說Spring容器啓動完成後,就會執行ContextRefreshListener中的onApplicationEvent()方法,從而最終會執行DispatcherServlet中的initStrategies(),這個方法中會初始化更多內容:

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);

	initHandlerMappings(context);
	initHandlerAdapters(context);

	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

其中最爲核心的就是HandlerMappingHandlerAdapter

什麼是Handler?

Handler表示請求處理器,在SpringMVC中有四種Handler:

  1. 實現了Controller接口的Bean對象
  2. 實現了HttpRequestHandler接口的Bean對象
  3. 添加了@RequestMapping註解的方法
  4. 一個HandlerFunction對象

比如實現了Controller接口的Bean對象:

@Component("/test")
public class ZhouyuBeanNameController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("zhouyu");
		return new ModelAndView();
	}
}

實現了HttpRequestHandler接口的Bean對象:

@Component("/test")
public class ZhouyuBeanNameController implements HttpRequestHandler {

	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("zhouyu");
	}
}

添加了@RequestMapping註解的方法:

@RequestMapping
@Component
public class ZhouyuController {

	@Autowired
	private ZhouyuService zhouyuService;

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

}

一個HandlerFunction對象(以下代碼中有兩個):

@ComponentScan("com.zhouyu")
@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();
	}
    
}

什麼是HandlerMapping?

HandlerMapping負責去尋找Handler,並且保存路徑和Handler之間的映射關係。

因爲有不同類型的Handler,所以在SpringMVC中會由不同的HandlerMapping來負責尋找Handler,比如:

  1. BeanNameUrlHandlerMapping:負責Controller接口和HttpRequestHandler接口
  2. RequestMappingHandlerMapping:負責@RequestMapping的方法
  3. RouterFunctionMapping:負責RouterFunction以及其中的HandlerFunction

BeanNameUrlHandlerMapping的尋找流程:

  1. 找出Spring容器中所有的beanName
  2. 判斷beanName是不是以“/”開頭
  3. 如果是,則把它當作一個Handler,並把beanName作爲key,bean對象作爲value存入handlerMap
  4. handlerMap就是一個Map

RequestMappingHandlerMapping的尋找流程:

  1. 找出Spring容器中所有beanType
  2. 判斷beanType是不是有@Controller註解,或者是不是有@RequestMapping註解
  3. 判斷成功則繼續找beanType中加了@RequestMapping的Method
  4. 並解析@RequestMapping中的內容,比如method、path,封裝爲一個RequestMappingInfo對象
  5. 最後把RequestMappingInfo對象做爲key,Method對象封裝爲HandlerMethod對象後作爲value,存入registry
  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;
}

很簡單,就是遍歷,找到就返回,默認順序爲:

所以BeanNameUrlHandlerMapping的優先級最高,比如:

@Component("/test")
public class ZhouyuBeanNameController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("Hello zhouyu");
		return new ModelAndView();
	}
}
@RequestMapping(method = RequestMethod.GET, path = "/test")
@ResponseBody
public String test(String username) {
	return "Hi zhouyu";
}

請求路徑都是/test,但是最終是Controller接口的會生效。

什麼是HandlerAdapter?

找到了Handler之後,接下來就該去執行了,比如執行下面這個test()

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

但是由於有不同種類的Handler,所以執行方式是不一樣的,再來總結一下Handler的類型:

  1. 實現了Controller接口的Bean對象,執行的是Bean對象中的handleRequest()
  2. 實現了HttpRequestHandler接口的Bean對象,執行的是Bean對象中的handleRequest()
  3. 添加了@RequestMapping註解的方法,具體爲一個HandlerMethod,執行的就是當前加了註解的方法
  4. 一個HandlerFunction對象,執行的是HandlerFunction對象中的handle()

所以,按邏輯來說,找到Handler之後,我們得判斷它的類型,比如代碼可能是這樣的:

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()方法就能執行不同種類Hanlder對應的方法。

針對不同的Handler,會有不同的適配器:

  1. HttpRequestHandlerAdapter
  2. SimpleControllerHandlerAdapter
  3. RequestMappingHandlerAdapter
  4. HandlerFunctionAdapter

適配邏輯爲:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
                               "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

傳入handler,遍歷上面四個Adapter,誰支持就返回誰,比如判斷的代碼依次爲:

public boolean supports(Object handler) {
    return (handler instanceof HttpRequestHandler);
}

public boolean supports(Object handler) {
    return (handler instanceof Controller);
}

public final boolean supports(Object handler) {
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

public boolean supports(Object handler) {
    return handler instanceof HandlerFunction;
}

根據Handler適配出了對應的HandlerAdapter後,就執行具體HandlerAdapter對象的handle()方法了,比如:

HttpRequestHandlerAdapter的handle():

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
	((HttpRequestHandler) handler).handleRequest(request, response);
	return null;
}

SimpleControllerHandlerAdapter的handle():

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
	return ((Controller) handler).handleRequest(request, response);
}

HandlerFunctionAdapter的handle():

HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
serverResponse = handlerFunction.handle(serverRequest);

因爲這三個接收的直接就是Requeset對象,不用SpringMVC做額外的解析,所以比較簡單,比較複雜的是RequestMappingHandlerAdapter,它執行的是加了@RequestMapping的方法,而這種方法的寫法可以是多種多樣,SpringMVC需要根據方法的定義去解析Request對象,從請求中獲取出對應的數據然後傳遞給方法,並執行。

@RequestMapping方法參數解析

當SpringMVC接收到請求,並找到了對應的Method之後,就要執行該方法了,不過在執行之前需要根據方法定義的參數信息,從請求中獲取出對應的數據,然後將數據傳給方法並執行。

一個HttpServletRequest通常有:

  1. request parameter
  2. request attribute
  3. request session
  4. reqeust header
  5. reqeust body

比如如下幾個方法:

public String test(String username) {
    return "zhouyu";
}

表示要從request parameter中獲取key爲username的value

public String test(@RequestParam("uname") String username) {
    return "zhouyu";
}

表示要從request parameter中獲取key爲uname的value

public String test(@RequestAttribute String username) {
    return "zhouyu";
}

表示要從request attribute中獲取key爲username的value

public String test(@SessionAttribute String username) {
    return "zhouyu";
}

表示要從request session中獲取key爲username的value

public String test(@RequestHeader String username) {
    return "zhouyu";
}

表示要從request header中獲取key爲username的value

public String test(@RequestBody String username) {
    return "zhouyu";
}

表示獲取整個請求體

所以,我們發現SpringMVC要去解析方法參數,看該參數到底是要獲取請求中的哪些信息。

而這個過程,源碼中是通過HandlerMethodArgumentResolver來實現的,比如:

  1. RequestParamMethodArgumentResolver:負責處理@RequestParam
  2. RequestHeaderMethodArgumentResolver:負責處理@RequestHeader
  3. SessionAttributeMethodArgumentResolver:負責處理@SessionAttribute
  4. RequestAttributeMethodArgumentResolver:負責處理@RequestAttribute
  5. RequestResponseBodyMethodProcessor:負責處理@RequestBody
  6. 還有很多其他的...

而在判斷某個參數該由哪個HandlerMethodArgumentResolver處理時,也是很粗暴:

private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
    	for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
        	if (resolver.supportsParameter(parameter)) {
            	result = resolver;
            	this.argumentResolverCache.put(parameter, result);
            	break;
        	}
    	}
	}
	return result;

}

就是遍歷所有的HandlerMethodArgumentResolver,哪個能支持處理當前這個參數就由哪個處理。

比如:

@RequestMapping(method = RequestMethod.GET, path = "/test")
@ResponseBody
public String test(@RequestParam @SessionAttribute String username) {
	System.out.println(username);
	return "zhouyu";
}

以上代碼的username將對應RequestParam中的username,而不是session中的,因爲在源碼中RequestParamMethodArgumentResolver更靠前。

當然HandlerMethodArgumentResolver也會負責從request中獲取對應的數據,對應的是resolveArgument()方法。

比如RequestParamMethodArgumentResolver:

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

    if (servletRequest != null) {
        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) {
        String[] paramValues = request.getParameterValues(name);
        if (paramValues != null) {
            arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
        }
    }
    return arg;
}

核心是:

if (arg == null) {
    String[] paramValues = request.getParameterValues(name);
    if (paramValues != null) {
        arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
    }
}

按同樣的思路,可以找到方法中每個參數所要求的值,從而執行方法,得到方法的返回值。

@RequestMapping方法返回值解析

 

而方法返回值,也會分爲不同的情況。比如有沒有加@ResponseBody註解,如果方法返回一個String:

  1. 加了@ResponseBody註解:表示直接將這個String返回給瀏覽器
  2. 沒有加@ResponseBody註解:表示應該根據這個String找到對應的頁面,把頁面返回給瀏覽器

 

在SpringMVC中,會利用HandlerMethodReturnValueHandler來處理返回值:

  1. RequestResponseBodyMethodProcessor:處理加了@ResponseBody註解的情況
  2. ViewNameMethodReturnValueHandler:處理沒有加@ResponseBody註解並且返回值類型爲String的情況
  3. ModelMethodProcessor:處理返回值是Model類型的情況
  4. 還有很多其他的...

我們這裏只講RequestResponseBodyMethodProcessor,因爲它會處理加了@ResponseBody註解的情況,也是目前我們用得最多的情況。

RequestResponseBodyMethodProcessor相當於會把方法返回的對象直接響應給瀏覽器,如果返回的是一個字符串,那麼好說,直接把字符串響應給瀏覽器,那如果返回的是一個Map呢?是一個User對象呢?該怎麼把這些複雜對象響應給瀏覽器呢?

處理這塊,SpringMVC會利用HttpMessageConverter來處理,比如默認情況下,SpringMVC會有4個HttpMessageConverter:

  1. ByteArrayHttpMessageConverter:處理返回值爲字節數組的情況,把字節數組返回給瀏覽器
  2. StringHttpMessageConverter:處理返回值爲字符串的情況,把字符串按指定的編碼序列號後返回給瀏覽器
  3. SourceHttpMessageConverter:處理返回值爲XML對象的情況,比如把DOMSource對象返回給瀏覽器
  4. 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"})
@ResponseBody
public String test() {
	return "周瑜";
}
@ComponentScan("com.zhouyu")
@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.zhouyu")
@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);
	}
}

具體轉化的邏輯就是Jackson2的轉化邏輯。

總結

以上就是整個SpringMVC從啓動到處理請求,從接收請求到執行方法的整體流程。

 

本系列文章來自圖靈學院周瑜老師分享,本博客整理學習並搬運

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章