欢迎来到硬啃世界
你好,希望你每天给自己一点信心和耐心,不做被公司、面试者、社会淘汰的程序员,在这里硬啃汉带你去硬啃源码重新捡回信心。
Springboot请求处理流程
今天来讲一下springboot接收到一个请求再到controller都经历了什么,作为一个java程序员现在几乎是离不开spring了,然后大多数java程序员都是做web开发,那就肯定离不开controller了,工作上闭着眼每天都会写controller然后再返回给前端,但是大家有没有想过为什么spring能知道一个请求进来就能找到这个方法调用呢,为什么我们写的出参是个对象能帮我们转成json串返回呢,都经历了什么,今天我们就来看看!
硬啃之前的一些准备
- 这次使用的springboot版本是2.4.5。
- 创建好这个版本的demo项目,就很普通的一个项目就行。
- 分别创建一个controller和拦截器,都没什么逻辑,主要是想看处理流程而不是看业务处理。
- 别忘记创建一个配置类,将我们自己创建的拦截器添加进去。
- 工具我这里用的eclipse(其实工具用啥无所谓都一样这么看)
具体结构:
controller的代码
@RestController
@RequestMapping
public class TestController {
//啥时不干,就打印然后返回
@GetMapping("/test")
public String test() {
String s = "哇哈哈";
System.out.println(s);
return s;
}
}
拦截器的代码
@Component
public class MyInterceptor implements HandlerInterceptor {
//进入controller前调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("拦截器进来了");
return true;
}
}
配置类的代码
@Configuration
public class WebMvcConfig implements WebMvcConfigurer{
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("");
}
}
开始硬啃
开启debug模式启动项目,因为我们创建了一个get请求接口,所以可以直接在浏览器或者用工具来模拟,一般我是使用postman来模拟请求,一开始我们应该是并不知道代码会经过哪一些类的处理才到controller的(大神除外,也可能是因为我比较弱鸡┭┮﹏┭┮),那怎么办呢,我们直接把断点打到controller里面,然后查看调用链:
然后我们通过调用链看到我们以前大伙都刚学甚至工作的时候java web的时候都会用到的一个类HttpServlet.class(当然这些年很多新入坑的小伙伴应该是不用再学这玩意了),我们看到他是调用了这个service方法
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
//我们把断点打到这一句,然后按跳过断点之后重新发起请求让其停在这里
service(request, response);
}
重新进来断点直接,我们看到了这里只做了一件事情,就是帮我们把ServletRequest和ServletResponse 强转为HttpServletRequest和HttpServletResponse,主要是在原来的基础上新增了很多接口方法,方便用于扩展。
我们进入这一句代码service(request, response),发现新进入了一个类FrameworkServlet.class的service方法:
@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
//为了方便显示,我们省略其余的方法代码,只看有关联的代码
//通过HttpServlet.service方法调用进来
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
}
@SuppressWarnings("serial")
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
//为了方便显示,我们省略其余的方法代码,只看有关联的代码
从类上看到继承HttpServletBean,然后我们再点进去看HttpServletBean发现是继承HttpServlet,验证了Spring就是基于Servlet来处理请求,我们继续往下走,因为HttpServletBean是没有重写service方法的所以这里调用的是HttpServlet的service方法:
简单看一下就好,这里其实也是没什么逻辑,
就是通过request获取到请求类型,然后调用对应的方法,
比如get请求就调用doGet,post请求就调用doPost
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
//该方法固定返回-1
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
//找到对应的请求类型就返回错误出去
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
进入具体方法,这里我们是进入doGet,这里只有一句代码
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
然后我好奇的看了一下其他请求类型是怎么样的,结果就是我们常用的4种类型GET、POST、PUT、DELETE的调用都是一样的
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
我们接着走进入到processRequest方法里面去看,在这里我们将代码走到doService方法调用
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//国际化设置
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
//该类是用来管理异步请求的
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
进入到doService方法我们发现又跳转到一个新的类DispatcherServlet
这个类非常重要!!!
这个类非常重要!!!
这个类非常重要!!!
到此我们先看来来这类和上面那些类的关系
现在我们来看看doService方法
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//这一句是用来打印请求详情的,如果有开启的话
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
进入到doDispatch方法,这个方法很核心,做实际请求转发到具体的controller
那spring是怎么知道这个路径是请求到哪个controller上的?
DispatchServlet这个类,有个属性handlerMappings
@Nullable
private List<HandlerMapping> handlerMappings;
这个属性是在第一次请求进入spring的时候初始化好的,里面会存放所有的handler,
其中一个handler叫RequestMappingHandlerMapping,这个handler就是专门用来处理请求的,
然后里面有个子类mappingRegistry,里面有个属性registry,这里面就是存放我们所有的controller
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 这里是用来获取匹配的拦截器的,包括我们自己创建的
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//这里就是进入到具体的拦截器做处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//这一句,就是调用我们的controller了,并且返回一个ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
当我们执行完 controller之后,我们来看一下后续他做了哪些处理,这里就不截取大量的代码了,只复制关键代码,因为我们主要看他返回之后怎么做到找资源或者是返回json格式化对象的
接收到返回值是个object
//这个一个叫invokeAndHandle方法中的代码
//这里面调用了一个方法invokeForRequest,其实里面就是一个Method对象去调用实际的方法
//用过反射的同学应该都比我了解
//不了解的同学可以去看一下如何使用java的反射机制,spring用了大量的反射机制
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
获得这个返回值之后,他内置了很多处理器,根据你的返回类型,调用不同的处理器处理
//这个方法就是调用对应的处理器处理返回值
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
然后在里面会判断这个returnValue应该需要怎么处理,我们重点来说一下这个逻辑
//如果返回值是字符串,就直接toString,这个还是比较简单的
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
}
else {//如果不是字符串,这里就要判断是什么类型,然后做处理,我们来看看是什么做的
body = value;
valueType = getReturnValueType(body, returnType);
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
首先这个getReturnValueType就是先获取这个返回值的类型,这个还是比较简单
protected Class<?> getReturnValueType(@Nullable Object value, MethodParameter returnType) {
return (value != null ? value.getClass() : returnType.getParameterType());
}
紧接着调用GenericTypeResolver.resolveType方法去判断是否是泛型类型的类
public static Type resolveType(Type genericType, @Nullable Class<?> contextClass) {
if (genericType instanceof TypeVariable){
//逻辑代码
}
if (genericType instanceof ParameterizedType){
//逻辑代码
}
//如果都不是就直接返回返回值的类型
return genericType;
}
然后就是获取MediaType,同时用于获取类型转换器
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
然后就是循环判断使用哪种类型转换器对返回值进行做转换
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
//遍历所有的转换器
for (HttpMessageConverter<?> converter : this.messageConverters) {
//判断是否是通用转换器,如果不是,就使用其他的转换器
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
所有的转换器都会调用一个canWrite方法来判断我们存储的返回值类型valueType变量
拿字符串的转换器来看
//简单粗暴,直接就是判断类型是否这个类型
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
return supports(clazz) && canWrite(mediaType);
}
@Override
public boolean supports(Class<?> clazz) {
return String.class == clazz;
}
默认的情况下,我们的转换器都会采用MappingJackson2HttpMessageConverter
并且MediaType是application/json
会调用Jackson进行序列化这个对象,然后采用流的方式输出
到此我们其实就可以结束查看了,看了个大概,下面我简单总结一下:
客户端请求进来,首先会初始化spring用于处理请求的策略对象
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
其中initHandlerMappings就是用于初始化我们的路由地址,把我们所有的controller都存放在
DispatcherServlet类中的HandlerMapping变量中,后续的请求都是通过这个变量找到对应的controller进行调用执行我们的逻辑,接着上面说到请求初始化好后,通过获取HttpServletRequest对象的method属性得到请求方式,然后分别调用doGet方法或者doPost方法等等其他请求方式,
最终他们都是调用processRequest方法做处理,这个方法主要用来做国际化处理和将我们的request跟当前的线程绑定,然后就调用DispatcherServlet的doService方法将spring刚刚初始化好的策略对象设置进当前request中,然后接着调用doDispatch做真正的请求处理,首先会先获取到所有匹配该请求路由的拦截器,然后根据请求路由通过HandlerMappings找到对应的HandlerAdapter,然后接着循环匹配到得拦截器集合分别调用前置方法preHandle,执行完拦截器之后刚刚获取到得HandlerAdapter调用我们的具体的controller方法,执行完我们自己的业务代码之后,获取我们的返回值,通过判断我们的返回值用不同的Converter进行类型转换,默认情况下我们返回的对象都是采用Jackson来帮我们序列化到response中返回,请求大概就是这样一个流程,如果能背上这么一段话,面试的时候面试官问流程也能算是个应付。
整个流程还不够细,只是一个大概让像我这样的新手去学习源码,不说工作用不用得上,起码面试的时候能用上,如果文章有说错的地方望指出,硬啃汉必定虚心学习,谢谢。