SpringBoot源码分析(请求部分)

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全做了处理,使我们在开发的过程中只需要注意业务部分的代码实现。

请求处理过程概览

  1. 请求映射
  2. 处理并获取请求参数
  3. 调用目标方法
  4. 数据响应与内容协商

请求映射

  1. 请求进来会进到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:处理器映射。
    ....
    
  2. getHandler(processedRequest):找到可以处理当前请求的handler
  3. 在spring中已经预定义了5个常用的请求处理器
    1. RequestMappingHandlerMapping: 用于请求,保存了所有@RequestMapping 和handler的映射规则。
    2. WelcomPageHandlerMapping: 用于处理欢迎页
    3. BeanNameUrlHandlerMapping: 处理容器中以/开头的bean
    4. RouterFuntionMapping: 处理RouterFunction发起的请求
    5. SimpleUrlHandlerMapping: 处理请求的映射关系
  4. 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;
    }
    

处理并获取请求参数

大体思路

  1. 为当前Handler找一个适配器HandlerAdapter
  2. 适配器执行目标方法的同时解析每个参数

详解

  1. 预定义了常用的五个适配器
    1. RequestMappingHandlerAdapter: 支持方法上标注@RequestMapping
    2. HandlerFunctionAdapter: 支持函数式编程的
    3. HttpRequestHandlerAdapter: http请求处理器适配器
    4. SimpleControllerHandlerAdapter: 简单控制器处理器适配器
  2. doDispatch调用getHandlerAdapter
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
      ...
      // Determine handler adapter for the current request.
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
      ...
    }
    
  3. 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;
          }
        }
      }
    }
    
  4. 执行目标方法
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
      ...
      // 调用处理器执行目标方法
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
      ...
    }
    
    ha.handle(…) → AbstractHandlerMethodAdapter.handleInternal(…) →
    RequestMappingHandlerAdapter.invokeHandlerMethod(…) →
    ServletInvocableHandlerMethod.invokeAndHandle(…) →
    InvocableHandlerMethod.getMethodArgumentValues(…)
  5. 找到合适的参数解析器-HandlerMethodArgumentResolver
    1. 用于解析参数的值
    2. 预定义的参数解析器有27种
      1. RequestParamMethodArgumentResolver: 用于解析@Param的参数
      2. RequestParamMapMethodArgumentResolver: 用于解析@Param Map格式的参数
      3. PathVariableMethodArgumentResolver: 用于解析@PathVariable路径变量
    3. 判断是否支持该参数解析器
      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"));
          }
        }
      }
      
    4. 如果支持则调用参数解析器的resolveArgument()方法
      protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
        ...
        // 解析指定参数
        args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        ...
      }
      
      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);
      }
      
      又。。。在getArgumentResolver()里获取合适的解析器,感觉这边做的有点重复了(里面校验是否支持参数解析器),找到了参数解析器后再执行解析器的resolveArgument(…)
    5. 这边演示调用的是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;
      }
      
    6. 这边演示的是@Param参数获取的流程,自定义POJO的话是由ModelAttributeMethodProcessor这个参数解析器来处理,他解析的原理是通过反射
      1. 解析部分代码
      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;
      }
      
      这里面有个WebDataBinder,即数据绑定器,用于将请求参数的值绑定到指定的JavaBean里面,利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
      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);
      }
      
    7. 获取好参数之后就是调用目标方法了…

调用目标方法

  1. 调用方法部分比较简单,原理就是通过反射工具获取目标类中的方法并执行
@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) {
    ...
  }
}
  1. method.invoke(getBean(), args)执行目标方法

数据响应与内容协商

数据响应

数据响应分为响应页面和响应数据,页面一般都是用重定向或者请求转发,响应数据就是将指定的字符串、json、xml等特定格式返回

  1. 目标函数执行完后,会返回一个参数
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {
      //执行目标方法,并获取返回值
      Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        ...
    }
    
  2. 处理请求结果
    ServletInvocableHandlerMethod
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {
      ...
      // 处理返回参数
      this.returnValueHandlers.handleReturnValue(
        returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
      ...
    }
    
    HandlerMethodReturnValueHandlerComposite.handleReturnValue(…)
    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);
    }
    
    1. selectHandler(…)获取适合处理制定类型的处理器
      1. 预定义的返回值处理器有15种
        1. ModelAndViewMethodReturnValueHandler
        2. ModelMethodProcessor
        3. ViewMethodReturnValueHandler
        4. ResponseBodyEmitterReturnValueHandler
        5. ServletModelAttributeMethodProcessor
      2. 获取匹配的返回值处理器(与参数解析器的匹配的设计思路类似)
        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;
        }
        
  3. 用指定的返回值处理器处理返回参数
    1. 处理返回参数
      @Override
      public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
          ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
      
        ...
        // 使用MessageConverters转换数据后将数据写出
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
      }
      
      由于我们这个是@ResponseBody的返回值处理器,这个处理器会调用RequestResponseBodyMethodProcessor的writeWithMessageConverters(…)来处理参数
      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;
            }
          }
        }
        ...
      }
      
      每个converter实现了HttpMessageConverter接口,接口里有read()、writer()方法等,convter需要重写这些方法,将制定参数按照特定的方法写出。😏一般可以重写的都可以让我们自定义,我们可以自定义一些Convter放到SpringMVC的配置中,会在内容协商部分详细say。

内容协商

内容协商主要做的就是根据客户端的需求返回制定格式的数据(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格式
原理

部分原理在上述数据响应的有提过,所以会有一点重复

  1. 判断当前响应头中是否已经有确定的媒体类型,MediaType属性
  2. 获取客户端支持接收的内容类型,Accept属性
    1. contentNegotiationManager内容协商管理,使用内容协商管理器里的策略来获取处理策略(是选择将数据转为json/xml/…),默认使用的是ParameterContentNegotiationStrategy策略
  3. 遍历所有的MessageConverter,找出支持处理该对象的转换器
  4. 将该convter支持处理的媒体类型统计出来
  5. 进行内容协商的最佳匹配媒体类型
    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());
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rex·Lin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值