SpringBoot源码分析(请求部分)
引言
👀看了尚硅谷雷神的SpringBoot2源码分析,颇有感触🤙,请求处理这部分的源码真的是太精彩了,决定自己在好好的debug几次,再记录下笔记!以前不理解什么叫作“框架 = 设计模式 + 反射 + 注解”,觉得设计模式不就是一些代码风格,反射不就是运行时动态获取类的内部信息并执行其的一些方法,注解不就是定义补充么。但看了SpringBoot源码后才知道如果没有设计模式,有些复杂逻辑的代码不做一些封装📦处理的话就会变成💩山代码,效率低、难理解、难扩展。而通过反射,可以使得框架有更高的灵活性和扩展性。使用注解则可以使代码更加简洁。
接口创建
SpringBoot的特点开箱即用,即内部已经为我们做了大量的配置,包括请求参数解析、数据响应、内容协商等等,如果是用传统的servlet(SpringBoot底层已经封装了tomcat)会变得极其复杂,而springboot我们只需要几个注解就可以完成一个简单的接口。
Example:
@GetMapping("/girl")
public String getGirlFriend(@RequestParam String girlfriend){
return String.format("Get a girlfriend: %s", girlfriend);
}
这样就成功创建了一个简单的接口,使用GetMapping来说明请求方式(“get”)和请求路径(“/girl”), 可以用url拼接参数的方式传参数(“/girl?girlfriend=xxx”)。按照以前sevlet,获取参数需要request.getParameter(“girlfriend”),post方法则要从request.getInputStream()获取BufferReader后再做处理;返回值想要返回json格式,需要用工具类,将对象拼接成json格式,然后用response.getWriter().println(girlfriend)写出,如果是中文还需要进行编码处理。而现在这些繁琐并且重复的操作springboot全做了处理,使我们在开发的过程中只需要注意业务部分的代码实现。
请求处理过程概览
- 请求映射
- 处理并获取请求参数
- 调用目标方法
- 数据响应与内容协商
请求映射
- 请求进来会进到DispatchServlet.doDispatch()(Tips: DispatchServlet是HttpServlet的一个子类,由Spring进行封装)中,SpringMVC功能分析都从这个类开始分析
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); // 找到当前请求使用哪个Handler(Controller的方法)处理 // Determine handler for the current request. mappedHandler = getHandler(processedRequest); //HandlerMapping:处理器映射。 ....
- getHandler(processedRequest):找到可以处理当前请求的handler
- 在spring中已经预定义了5个常用的请求处理器
1. RequestMappingHandlerMapping: 用于请求,保存了所有@RequestMapping 和handler的映射规则。
2. WelcomPageHandlerMapping: 用于处理欢迎页
3. BeanNameUrlHandlerMapping: 处理容器中以/开头的bean
4. RouterFuntionMapping: 处理RouterFunction发起的请求
5. SimpleUrlHandlerMapping: 处理请求的映射关系 - getHandler: 挨个尝试所有的HandlerMapping看是否有请求信息
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { // 挨个尝试HandlerMapping看看是否有请求信息 HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
处理并获取请求参数
大体思路
- 为当前Handler找一个适配器HandlerAdapter
- 适配器执行目标方法的同时解析每个参数
详解
- 预定义了常用的五个适配器
- RequestMappingHandlerAdapter: 支持方法上标注@RequestMapping
- HandlerFunctionAdapter: 支持函数式编程的
- HttpRequestHandlerAdapter: http请求处理器适配器
- SimpleControllerHandlerAdapter: 简单控制器处理器适配器
- doDispatch调用getHandlerAdapter
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { ... // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ... }
- getHandlerAdapter,找到可以处理当前请求的handler
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter: this.handlerAdapters) { // 遍历系统中的适配器,看看哪种支持处理当前请求的handler if (adapter.supports(handler)) { return adapter; } } } }
- 执行目标方法
ha.handle(…) → AbstractHandlerMethodAdapter.handleInternal(…) →protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { ... // 调用处理器执行目标方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ... }
RequestMappingHandlerAdapter.invokeHandlerMethod(…) →
ServletInvocableHandlerMethod.invokeAndHandle(…) →
InvocableHandlerMethod.getMethodArgumentValues(…) - 找到合适的参数解析器-HandlerMethodArgumentResolver
- 用于解析参数的值
- 预定义的参数解析器有27种
- RequestParamMethodArgumentResolver: 用于解析@Param的参数
- RequestParamMapMethodArgumentResolver: 用于解析@Param Map格式的参数
- PathVariableMethodArgumentResolver: 用于解析@PathVariable路径变量
- …
- 判断是否支持该参数解析器
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { ... for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; ... // 判断解析器是否支持解析该参数 if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } } }
- 如果支持则调用参数解析器的resolveArgument()方法
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { ... // 解析指定参数 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); ... }
又。。。在getArgumentResolver()里获取合适的解析器,感觉这边做的有点重复了(里面校验是否支持参数解析器),找到了参数解析器后再执行解析器的resolveArgument(…)public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // 获取可以解析该参数的参数解析器 HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null) { throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first."); } // 解析方法的参数 return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }
- 这边演示调用的是AbstractNamedValueMethodArgumentResolver中的解析参数的方法(其他类型参数解析器不一样)
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // 参数名称信息对象 NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); // 获取参数名称 Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name); if (resolvedName == null) { throw new IllegalArgumentException( "Specified name must not resolve to null: [" + namedValueInfo.name + "]"); } // 根据参数名获取参数值 Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); .... return arg; }
- 这边演示的是@Param参数获取的流程,自定义POJO的话是由ModelAttributeMethodProcessor这个参数解析器来处理,他解析的原理是通过反射
- 解析部分代码
这里面有个WebDataBinder,即数据绑定器,用于将请求参数的值绑定到指定的JavaBean里面,利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { ... // 获取参数名称 String name = ModelFactory.getNameForParameter(parameter); ... Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); }else { // 创建属性实例, 即需要绑定属性的对象 try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { ... } } if (bindingResult == null) { // 创建数据绑定器 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { // 将webRequest中的请求数据绑定在对象中 bindRequestParameters(binder, webRequest); } ... } ... } // 将数据绑定在ModelAndViewContainer中 Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
2. 他这里的数据绑定器默认的有124种(也可以在WebMvcConfigurer中自定义😏)@Override public void addFormatters(FormatterRegistry registry) { // 用lambda会报错 // 添加一个格式转换器 registry.addConverter(new Converter<String, Pet>() { @Override public Pet convert(String source) { if (StringUtils.hasLength(source)){ Pet pet = new Pet(); String[] args = source.split(","); pet.setName(args[0]); pet.setAge(Integer.valueOf(args[1])); return pet; } return null; } }); WebMvcConfigurer.super.addFormatters(registry); }
- 获取好参数之后就是调用目标方法了…
调用目标方法
- 调用方法部分比较简单,原理就是通过反射工具获取目标类中的方法并执行
@Nullable
protected Object doInvoke(Object... args) throws Exception {
// 获取目标方法
Method method = getBridgedMethod();
try {
if (KotlinDetector.isSuspendingFunction(method)) {
return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
}
// 执行目标方法
return method.invoke(getBean(), args);
}
catch (InvocationTargetException ex) {
...
}
}
- method.invoke(getBean(), args)执行目标方法
数据响应与内容协商
数据响应
数据响应分为响应页面和响应数据,页面一般都是用重定向或者请求转发,响应数据就是将指定的字符串、json、xml等特定格式返回
- 目标函数执行完后,会返回一个参数
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //执行目标方法,并获取返回值 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); ... }
- 处理请求结果
ServletInvocableHandlerMethod
HandlerMethodReturnValueHandlerComposite.handleReturnValue(…)public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { ... // 处理返回参数 this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); ... }
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { // 获取匹配的返回值处理器 HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } // 用得到的返回值处理器处理返回结果 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); }
- selectHandler(…)获取适合处理制定类型的处理器
- 预定义的返回值处理器有15种
- ModelAndViewMethodReturnValueHandler
- ModelMethodProcessor
- ViewMethodReturnValueHandler
- ResponseBodyEmitterReturnValueHandler
- …
- ServletModelAttributeMethodProcessor
- 获取匹配的返回值处理器(与参数解析器的匹配的设计思路类似)
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) { boolean isAsyncValue = isAsyncReturnValue(value, returnType); for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) { if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) { continue; } if (handler.supportsReturnType(returnType)) { return handler; } } return null; }
- 预定义的返回值处理器有15种
- selectHandler(…)获取适合处理制定类型的处理器
- 用指定的返回值处理器处理返回参数
- 处理返回参数
由于我们这个是@ResponseBody的返回值处理器,这个处理器会调用RequestResponseBodyMethodProcessor的writeWithMessageConverters(…)来处理参数@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { ... // 使用MessageConverters转换数据后将数据写出 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
每个converter实现了HttpMessageConverter接口,接口里有read()、writer()方法等,convter需要重写这些方法,将制定参数按照特定的方法写出。😏一般可以重写的都可以让我们自定义,我们可以自定义一些Convter放到SpringMVC的配置中,会在内容协商部分详细say。protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { ... // 获取请求需要返回的媒体类型 MediaType selectedMediaType = null; MediaType contentType = outputMessage.getHeaders() // 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据, ... // SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter 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)) { ... if (body != null) { ... if (genericConverter != null) { // 使用指定的转换器将数据写入body中 genericConverter.write(body, targetType, selectedMediaType, outputMessage); } else { // 使用默认的转换器写入body ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); } } ... return; } } } ... }
- 处理返回参数
内容协商
内容协商主要做的就是根据客户端的需求返回制定格式的数据(json、xml。。。)
开启浏览器参数方式内容协商功能
在分析内容原理之前,得特别提一下springboot对内容协商的配置
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
做了这样的配置之后只需要在url的参数中带着format参数,如
- http://localhost:8080/test/person?format=json,将数据转换为json格式
- http://localhost:8080/test/person?format=xml,将数据转换为xml格式
原理
部分原理在上述数据响应的有提过,所以会有一点重复
- 判断当前响应头中是否已经有确定的媒体类型,MediaType属性
- 获取客户端支持接收的内容类型,Accept属性
- contentNegotiationManager内容协商管理,使用内容协商管理器里的策略来获取处理策略(是选择将数据转为json/xml/…),默认使用的是ParameterContentNegotiationStrategy策略
- 遍历所有的MessageConverter,找出支持处理该对象的转换器
- 将该convter支持处理的媒体类型统计出来
- 进行内容协商的最佳匹配媒体类型
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { ... // 进行内容协商的最佳匹配 MediaType.sortBySpecificityAndQuality(mediaTypesToUse); ... }
自定义内容协商
实现多协议数据兼容: json、xml、x-rex。从原理上看,只需要在WebMvcConfigurer加入自定义转换器。代码如下
MyMessageConverter.java
/**
* Description: 自定义Converter
*
* @author rex
* @date 2022-09-25 16:37
*/
public class MyMessageConverter implements HttpMessageConverter<Person> {
/**
* 读取数据的规则
*/
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
/**
* 写入数据的规则
*/
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(Person.class);
}
/**
* 服务器要统计MessageConverter都能写出那些类型
* application/x-rex
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-rex");
}
/**
* 读取数据
*/
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
/**
* 写入数据
*/
@Override
public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//自定义内容写出
String data = person.getName() + ";" + person.getAge() + ";" + person.getBirth();
outputMessage.getBody().write(data.getBytes());
}
}
WebConfig.java
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 加入自定义的消息转换器
converters.add(new MyMessageConverter());
}
}