该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读

Spring 版本:5.2.4.RELEASE

该系列其他文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》

HandlerMapping 组件

HandlerMapping 组件,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors

  • handler 处理器是 Object 类型,可以将其理解成 HandlerMethod 对象(例如我们使用最多的 @RequestMapping 注解所标注的方法会解析成该对象),包含了方法的所有信息,通过该对象能够执行该方法

  • HandlerInterceptor 拦截器对处理请求进行增强处理,可用于在执行方法前、成功执行方法后、处理完成后进行一些逻辑处理

由于 HandlerMapping 组件涉及到的内容比较多,考虑到内容的排版,所以将这部分内容拆分成了四个模块,依次进行分析:

  • 《HandlerMapping 组件(一)之 AbstractHandlerMapping》
  • 《HandlerMapping 组件(二)之 HandlerInterceptor 拦截器》
  • 《HandlerMapping 组件(三)之 AbstractHandlerMethodMapping》
  • 《HandlerMapping 组件(四)之 AbstractUrlHandlerMapping》

HandlerMapping 组件(一)之 AbstractHandlerMapping

先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 HandlerMapping 组件,可以回到《一个请求的旅行过程》中的 DispatcherServletdoDispatch 方法中看看,如下:

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HttpServletRequest processedRequest = request;
  3. HandlerExecutionChain mappedHandler = null;
  4. // ... 省略相关代码
  5. // Determine handler for the current request.
  6. // <3> 获得请求对应的 HandlerExecutionChain 对象(HandlerMethod 和 HandlerInterceptor 拦截器们)
  7. mappedHandler = getHandler(processedRequest);
  8. if (mappedHandler == null) { // <3.1> 如果获取不到,则根据配置抛出异常或返回 404 错误
  9. noHandlerFound(processedRequest, response);
  10. return;
  11. }
  12. // ... 省略相关代码
  13. }
  14. @Nullable
  15. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  16. if (this.handlerMappings != null) {
  17. // 遍历 handlerMappings 组件们
  18. for (HandlerMapping mapping : this.handlerMappings) {
  19. // 通过 HandlerMapping 组件获取到 HandlerExecutionChain 对象
  20. HandlerExecutionChain handler = mapping.getHandler(request);
  21. if (handler != null) {
  22. // 不为空则直接返回
  23. return handler;
  24. }
  25. }
  26. }
  27. return null;
  28. }

通过遍历 HandlerMapping 组件们,根据请求获取到对应 HandlerExecutionChain 处理器执行链。注意,这里是通过一个一个的 HandlerMapping 组件去进行处理,如果找到对应 HandlerExecutionChain 对象则直接返回,不会继续下去,所以初始化的 HandlerMapping 组件是有一定的先后顺序的,默认是BeanNameUrlHandlerMapping -> RequestMappingHandlerMapping

HandlerMapping 接口

org.springframework.web.servlet.HandlerMapping 接口,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors),代码如下:

  1. public interface HandlerMapping {
  2. String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
  3. String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
  4. String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
  5. String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
  6. String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
  7. String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
  8. String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
  9. /**
  10. * 获得请求对应的处理器和拦截器们
  11. */
  12. @Nullable
  13. HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
  14. }

类图

HandlerMapping 接口体系的结构如下:

  • 蓝色框 AbstractHandlerMapping 抽象类,实现了“为请求找到合适的 HandlerExecutionChain 处理器执行链”对应的的骨架逻辑,而暴露 getHandlerInternal(HttpServletRequest request) 抽象方法,交由子类实现。

  • AbstractHandlerMapping 的子类,分成两派,分别是:

    • 黄色框 AbstractUrlHandlerMapping 系,基于 URL 进行匹配。例如 《基于 XML 配置的 Spring MVC 简单的 HelloWorld 实例应用》 ,当然,目前这种方式已经基本不用了,被 @RequestMapping 等注解的方式所取代。不过,Spring MVC 内置的一些路径匹配,还是使用这种方式。
    • 红色框 AbstractHandlerMethodMapping 系,基于 Method 进行匹配。例如,我们所熟知的 @RequestMapping 等注解的方式。
  • 绿色框的 MatchableHandlerMapping 接口,定义了“判断请求和指定 pattern 路径是否匹配”的方法。

初始化过程

DispatcherServletinitHandlerMappings(ApplicationContext context) 方法,会在 onRefresh 方法被调用,初始化 HandlerMapping 组件,方法如下:

  1. private void initHandlerMappings(ApplicationContext context) {
  2. // 置空 handlerMappings
  3. this.handlerMappings = null;
  4. // <1> 如果开启探测功能,则扫描已注册的 HandlerMapping 的 Bean 们,添加到 handlerMappings 中
  5. if (this.detectAllHandlerMappings) {
  6. // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
  7. // 扫描已注册的 HandlerMapping 的 Bean 们
  8. Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
  9. HandlerMapping.class, true, false);
  10. // 添加到 handlerMappings 中,并进行排序
  11. if (!matchingBeans.isEmpty()) {
  12. this.handlerMappings = new ArrayList<>(matchingBeans.values());
  13. // We keep HandlerMappings in sorted order.
  14. AnnotationAwareOrderComparator.sort(this.handlerMappings);
  15. }
  16. }
  17. // <2> 如果关闭探测功能,则获得 Bean 名称为 "handlerMapping" 对应的 Bean ,将其添加至 handlerMappings
  18. else {
  19. try {
  20. HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
  21. this.handlerMappings = Collections.singletonList(hm);
  22. }
  23. catch (NoSuchBeanDefinitionException ex) {
  24. // Ignore, we'll add a default HandlerMapping later.
  25. }
  26. }
  27. // Ensure we have at least one HandlerMapping, by registering
  28. // a default HandlerMapping if no other mappings are found.
  29. /**
  30. * <3> 如果未获得到,则获得默认配置的 HandlerMapping 类
  31. * {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}
  32. * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping}
  33. */
  34. if (this.handlerMappings == null) {
  35. this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
  36. if (logger.isTraceEnabled()) {
  37. logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
  38. "': using default strategies from DispatcherServlet.properties");
  39. }
  40. }
  41. }
  1. 如果“开启”探测功能,则扫描已注册的 HandlerMapping 的 Bean 们,添加到 handlerMappings 中,默认开启

  2. 如果“关闭”探测功能,则获得 Bean 名称为 "handlerMapping" 对应的 Bean ,将其添加至 handlerMappings

  3. 如果未获得到,则获得默认配置的 HandlerMapping 类,调用 getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) 方法,就是从 DispatcherServlet.properties 文件中读取 HandlerMapping 的默认实现类,如下:

    1. org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    2. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

    可以看到对应的是 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping 对象

AbstractHandlerMapping

org.springframework.web.servlet.handler.AbstractHandlerMapping,实现 HandlerMapping、Ordered、BeanNameAware 接口,继承 WebApplicationObjectSupport 抽象类

该类是 HandlerMapping 接口的抽象基类,实现了“为请求找到合适的 HandlerExecutionChain 处理器执行链”对应的的骨架逻辑,而暴露 getHandlerInternal(HttpServletRequest request) 抽象方法,交由子类实现

WebApplicationObjectSupport 抽象类,提供 applicationContext 属性的声明和注入。

构造方法

  1. public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered, BeanNameAware {
  2. /**
  3. * 默认处理器
  4. */
  5. @Nullable
  6. private Object defaultHandler;
  7. /**
  8. * URL 路径工具类
  9. */
  10. private UrlPathHelper urlPathHelper = new UrlPathHelper();
  11. /**
  12. * 路径匹配器
  13. */
  14. private PathMatcher pathMatcher = new AntPathMatcher();
  15. /**
  16. * 配置的拦截器数组.
  17. *
  18. * 在 {@link #initInterceptors()} 方法中,初始化到 {@link #adaptedInterceptors} 中
  19. *
  20. * 添加方式有两种:
  21. * 1. {@link #setInterceptors(Object...)} 方法
  22. * 2. {@link #extendInterceptors(List)} 方法
  23. */
  24. private final List<Object> interceptors = new ArrayList<>();
  25. /**
  26. * 初始化后的拦截器 HandlerInterceptor 数组
  27. */
  28. private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
  29. private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
  30. private CorsProcessor corsProcessor = new DefaultCorsProcessor();
  31. private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
  32. /**
  33. * 当前 Bean 的名称
  34. */
  35. @Nullable
  36. private String beanName;
  37. // ... 省略相关 getter、setter 方法
  38. }
  • defaultHandler默认处理器,在获得不到处理器时,可使用该属性

  • interceptors配置的拦截器数组

  • adaptedInterceptors初始化后的拦截器 HandlerInterceptor 数组,也就是interceptors 转换成的 HandlerInterceptor 拦截器对象

initApplicationContext

initApplicationContext()方法,用于初始化拦截器们,方法如下:

在父类 WebApplicationObjectSupport 的父类 ApplicationObjectSupport 中可以看到,因为实现了 ApplicationContextAware 接口,则在初始化该 Bean 的时候会调用 setApplicationContext(@Nullable ApplicationContext context) 方法,在这个方法中会调用 initApplicationContext() 这个方法

  1. @Override
  2. protected void initApplicationContext() throws BeansException {
  3. // <1> 空实现,交给子类实现,用于注册自定义的拦截器到 interceptors 中,目前暂无子类实现
  4. extendInterceptors(this.interceptors);
  5. // <2> 扫描已注册的 MappedInterceptor 的 Bean 们,添加到 mappedInterceptors 中
  6. detectMappedInterceptors(this.adaptedInterceptors);
  7. // <3> 将 interceptors 初始化成 HandlerInterceptor 类型,添加到 mappedInterceptors 中
  8. initInterceptors();
  9. }
  1. 调用 extendInterceptors(List<Object> interceptors) 方法,空方法,目前暂无子类实现,暂时忽略

  2. 调用 detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) 方法,从 Spring 的上下文中,扫描已注册的 MappedInterceptor 的拦截器们,添加到 adaptedInterceptors 中,方法如下:

    1. protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
    2. // 扫描已注册的 MappedInterceptor 的 Bean 们,添加到 mappedInterceptors 中
    3. // MappedInterceptor 会根据请求路径做匹配,是否进行拦截
    4. mappedInterceptors.addAll(BeanFactoryUtils
    5. .beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false)
    6. .values());
    7. }
  3. 调用 initInterceptors() 方法,将 interceptors 初始化成 HandlerInterceptor 类型,添加到 adaptedInterceptors 中,方法如下:

    1. protected void initInterceptors() {
    2. if (!this.interceptors.isEmpty()) {
    3. for (int i = 0; i < this.interceptors.size(); i++) {
    4. Object interceptor = this.interceptors.get(i);
    5. if (interceptor == null) {
    6. throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
    7. }
    8. // 将 interceptors 初始化成 HandlerInterceptor 类型,添加到 mappedInterceptors 中
    9. // 注意,HandlerInterceptor 无需进行路径匹配,直接拦截全部
    10. this.adaptedInterceptors.add(adaptInterceptor(interceptor));
    11. }
    12. }
    13. }
    14. protected HandlerInterceptor adaptInterceptor(Object interceptor) {
    15. if (interceptor instanceof HandlerInterceptor) {
    16. return (HandlerInterceptor) interceptor;
    17. }
    18. else if (interceptor instanceof WebRequestInterceptor) {
    19. return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
    20. }
    21. else {
    22. throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
    23. }
    24. }

关于拦截器在后文进行分析

getHandler

getHandler(HttpServletRequest request) 方法,获得请求对应的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors),方法如下:

  1. @Override
  2. @Nullable
  3. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  4. // <1> 获得处理器(HandlerMethod 或者 HandlerExecutionChain),该方法是抽象方法,由子类实现
  5. Object handler = getHandlerInternal(request);
  6. // <2> 获得不到,则使用默认处理器
  7. if (handler == null) {
  8. handler = getDefaultHandler();
  9. }
  10. // <3> 还是获得不到,则返回 null
  11. if (handler == null) {
  12. return null;
  13. }
  14. // Bean name or resolved handler?
  15. // <4> 如果找到的处理器是 String 类型,则从 Spring 容器中找到对应的 Bean 作为处理器
  16. if (handler instanceof String) {
  17. String handlerName = (String) handler;
  18. handler = obtainApplicationContext().getBean(handlerName);
  19. }
  20. // <5> 创建 HandlerExecutionChain 对象(包含处理器和拦截器)
  21. HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
  22. if (logger.isTraceEnabled()) {
  23. logger.trace("Mapped to " + handler);
  24. }
  25. else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
  26. logger.debug("Mapped to " + executionChain.getHandler());
  27. }
  28. if (CorsUtils.isCorsRequest(request)) {
  29. CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
  30. CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
  31. CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
  32. executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  33. }
  34. return executionChain;
  35. }
  1. 调用getHandlerInternal(HttpServletRequest request) 抽象方法,获得 handler 处理器

  2. 如果 handler 处理器没有找到,则调用getDefaultHandler() 方法,使用默认处理器,也就是 defaultHandler 属性

  3. 如果 handler 处理器没有找到,且没有默认的处理器,则直接返回 null

  4. 如果找到的处理器是 String 类型,可能是 Bean 的名称,则从 Spring 容器中找到对应的 Bean 作为处理器

  5. 调用 getHandlerExecutionChain(Object handler, HttpServletRequest request) 方法,获得 HandlerExecutionChain 对象,方法如下:

    1. protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    2. // <1> 创建 HandlerExecutionChain 对象
    3. HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler
    4. : new HandlerExecutionChain(handler));
    5. // <2> 获得请求路径
    6. String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    7. // <3> 遍历 adaptedInterceptors 数组,获得请求匹配的拦截器
    8. for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
    9. // 需要匹配,若路径匹配,则添加到 chain 中
    10. if (interceptor instanceof MappedInterceptor) {
    11. MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
    12. if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { // 匹配
    13. chain.addInterceptor(mappedInterceptor.getInterceptor());
    14. }
    15. }
    16. // 无需匹配,直接添加到 chain 中
    17. else {
    18. chain.addInterceptor(interceptor);
    19. }
    20. }
    21. return chain
    22. }
    1. 创建一个 HandlerExecutionChain 对象,如果 handler 处理器就是该类型对象,则直接使用
    2. 获得请求路径
    3. 遍历 adaptedInterceptors 拦截器数组,根据请求路径获得当前请求匹配的拦截器们,添加到 HandlerExecutionChain 对象中
  6. 返回上面创建的 HandlerExecutionChain 对象

MatchableHandlerMapping

org.springframework.web.servlet.handler.MatchableHandlerMapping,定义了“判断请求和指定 pattern 路径是否匹配”的方法。代码如下:

  1. public interface MatchableHandlerMapping extends HandlerMapping {
  2. /**
  3. * 判断请求和指定 pattern 路径是否匹配
  4. */
  5. @Nullable
  6. RequestMatchResult match(HttpServletRequest request, String pattern);
  7. }

RequestMatchResult

org.springframework.web.servlet.handler.RequestMatchResult 类,判断请求和指定 pattern 路径是否匹配时,返回的匹配结果,代码如下:

  1. public class RequestMatchResult {
  2. /**
  3. * 匹配到的路径
  4. */
  5. private final String matchingPattern;
  6. /**
  7. * 被匹配的路径
  8. */
  9. private final String lookupPath;
  10. /**
  11. * 路径匹配器
  12. */
  13. private final PathMatcher pathMatcher;
  14. public RequestMatchResult(String matchingPattern, String lookupPath, PathMatcher pathMatcher) {
  15. Assert.hasText(matchingPattern, "'matchingPattern' is required");
  16. Assert.hasText(lookupPath, "'lookupPath' is required");
  17. Assert.notNull(pathMatcher, "'pathMatcher' is required");
  18. this.matchingPattern = matchingPattern;
  19. this.lookupPath = lookupPath;
  20. this.pathMatcher = pathMatcher;
  21. }
  22. public Map<String, String> extractUriTemplateVariables() {
  23. return this.pathMatcher.extractUriTemplateVariables(this.matchingPattern, this.lookupPath);
  24. }
  25. }

目前实现 MatchableHandlerMapping 接口的类,有 RequestMappingHandlerMapping 类和 AbstractUrlHandlerMapping 抽象类,在后续都会进行分析

总结

本文对 Spring MVC 处理请求的过程中使用到的 HandlerMapping 组件进行了分析,会为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors

HandlerMapping 组件的实现类分为两种:

  • 基于 URL 进行匹配。例如 《基于 XML 配置的 Spring MVC 简单的 HelloWorld 实例应用》 ,当然,目前这种方式已经基本不用了,被 @RequestMapping 等注解的方式所取代。不过,Spring MVC 内置的一些路径匹配,还是使用这种方式
  • 基于 Method 进行匹配。例如,我们所熟知的 @RequestMapping 等注解的方式

AbstractHandlerMapping 抽象类,作为一个基类,实现了“为请求找到合适的 HandlerExecutionChain 处理器执行链”对应的的骨架逻辑,而暴露 getHandlerInternal(HttpServletRequest request) 抽象方法,交由子类实现。

本文对 HandlerMapping 组件做了一个简单的介绍,更多的细节交由其子类去实现,由于涉及到的内容比较多,BeanNameUrlHandlerMappingRequestMappingHandlerMapping 两种实现类则在后续的文档中依次进行分析

参考文章:芋道源码《精尽 Spring MVC 源码分析》

精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping的更多相关文章

  1. 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  2. 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  3. 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  6. 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  7. 精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  8. 精尽Spring MVC源码分析 - HandlerExceptionResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. 精尽Spring MVC源码分析 - MultipartResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. MySQL 5.x乱码问题解决

    MySQL是一款常用的开源数据库软件,但是对于初次使用者好像并不是太友好,MySQL5.x的版本中默认字符集是latin1也就是我们所知道的ISO-8859-1字符集,这个字符集编码并没有包含汉字,所 ...

  2. FL Studio通道乐器设置页详讲

    上一篇文章我们说到FL Studio通道乐器设置页每个标签页面中几乎都是由包络.低频振荡器和滤波器这三个部分组成.我们之前只对包络进行的简单的介绍,相信很多同学对它还有其他两个的功能的了解还是云里雾里 ...

  3. 如何将各种音频视频素材导入Vegas?

    使用vegas制作视频时,我们经常需要将音频和视频素材导入到媒体库中,以此来达到完美的视听结合效果.其实vegas导入素材并不难,因此很多有剪辑经验的朋友完全可以不用看下去了,主要是纯小白自学视频剪辑 ...

  4. Folx使用教程:怎么通过设置标签分类下载内容

    很多Mac OS下载软件从网上下载各种各样的文件,一般默认都会存放在"下载"文件夹中.如果不是经常整理"下载"文件夹,久而久之,该文件夹会变得庞大而杂乱. 如果 ...

  5. css3系列之transform 详解scale

    scale() scaleX() scaleY() scaleZ() scale3d() 改变的不是元素的宽高,而是 X 和 Y 轴的刻度 本章有个很冷门的知识点 → scale 和 rotate 一 ...

  6. 实战教程:如何将自己的Python包发布到PyPI上

    1. PyPi的用途 Python中我们经常会用到第三方的包,默认情况下,用到的第三方工具包基本都是从Pypi.org里面下载. 我们举个栗子: 如果你希望用Python实现一个金融量化分析工具,目前 ...

  7. Codeforces Round #668 (Div. 2) D. Tree Tag 题解(博弈)

    题目链接 题目大意 给你一颗树,Alice在a点,Bob在b点,Alice最多走da步,Bob最多走db步,两人轮流走路.要你判断经过无数次追赶后,Alice是否可以追上Bob,两人进行的都是最优策略 ...

  8. java17(面向对象)

    1.面向过程:所有事情都是按顺序一件件做,未知主体 买菜,做饭,吃饭,洗碗 面向对象:将功能封装到对象之中,让对象去实现功能 去饭馆,告诉服务员要吃啥,然后等着端上来. 面向对象的目的: 复杂的东西简 ...

  9. rest-framework 响应器(渲染器)

    一 作用: 根据 用户请求URL 或 用户可接受的类型,筛选出合适的 渲染组件. 用户请求URL:    http://127.0.0.1:8000/test/?format=json    http ...

  10. Docker实战 | 第三篇:Docker安装Nginx,实现基于vue-element-admin框架构建的项目线上部署

    一. 前言 在上一文中 点击跳转 通过IDEA集成Docker插件实现微服务的一键部署,但 youlai-mall 是前后端分离的项目,除了后端微服务的部署之外,当然还少不了前端工程的部署.所以本篇讲 ...