硬啃世界--------Springboot请求处理流程

欢迎来到硬啃世界

你好,希望你每天给自己一点信心和耐心,不做被公司、面试者、社会淘汰的程序员,在这里硬啃汉带你去硬啃源码重新捡回信心。

Springboot请求处理流程

今天来讲一下springboot接收到一个请求再到controller都经历了什么,作为一个java程序员现在几乎是离不开spring了,然后大多数java程序员都是做web开发,那就肯定离不开controller了,工作上闭着眼每天都会写controller然后再返回给前端,但是大家有没有想过为什么spring能知道一个请求进来就能找到这个方法调用呢,为什么我们写的出参是个对象能帮我们转成json串返回呢,都经历了什么,今天我们就来看看!

硬啃之前的一些准备

  1. 这次使用的springboot版本是2.4.5。
  2. 创建好这个版本的demo项目,就很普通的一个项目就行。
  3. 分别创建一个controller和拦截器,都没什么逻辑,主要是想看处理流程而不是看业务处理。
  4. 别忘记创建一个配置类,将我们自己创建的拦截器添加进去。
  5. 工具我这里用的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);
    }

重新进来断点直接,我们看到了这里只做了一件事情,就是帮我们把ServletRequestServletResponse 强转为HttpServletRequestHttpServletResponse,主要是在原来的基础上新增了很多接口方法,方便用于扩展。
我们进入这一句代码service(request, response),发现新进入了一个类FrameworkServlet.classservice方法:

@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方法的所以这里调用的是HttpServletservice方法:
简单看一下就好,这里其实也是没什么逻辑,
就是通过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中返回,请求大概就是这样一个流程,如果能背上这么一段话,面试的时候面试官问流程也能算是个应付。


整个流程还不够细,只是一个大概让像我这样的新手去学习源码,不说工作用不用得上,起码面试的时候能用上,如果文章有说错的地方望指出,硬啃汉必定虚心学习,谢谢。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值