SpringMVC之Controller查找(Spring4.0.3/Spring5.0.4源码进化对比)

简介: 0 摘要本文从源码层面简单讲解SpringMVC的处理器映射环节,也就是查找Controller详细过程1 SpringMVC请求流程Controller查找在上图中对应的步骤1至2的过程SpringMVC详细运行流程图2 SpringMVC初始化过程2.1 先认识两个类Handler通常指用于处理request请求的实际对象,可以类比 XxxController。

0 摘要

  • 本文从源码层面简单讲解SpringMVC的处理器映射环节,也就是查找Controller详细过程

1 SpringMVC请求流程

img_eccb22b350647a755b61150512096ac4.jpe
  • Controller查找在上图中对应的步骤1至2的过程


    img_bd33b79dddb0288de070c99c3b9dc558.png
    SpringMVC详细运行流程图

2 SpringMVC初始化过程

2.1 先认识两个类

  • Handler
    通常指用于处理request请求的实际对象,可以类比 XxxController。在Spring Mvc中并没有具体的类叫 Handler。
  1. RequestMappingInfo
    封装RequestMapping注解
    包含HTTP请求头的相关信息
    一个实例对应一个RequestMapping注解
  2. HandlerMethod
    封装Controller的处理请求方法
    包含该方法所属的bean对象、该方法对应的method对象、该方法的参数等
    img_bd3ab2b693e257b05e72bd3a38860505.png
    RequestMappingHandlerMapping的继承关系

    img_858704d589b505d6aac677eec0fe274f.png
    初始化调用链

2.2 RequestMappingHandlerMapping、AbstractHandlerMethodMapping

RequestMappingHandlerMapping 实现了InitalizingBean
Spring容器在启动的时候会执行InitalizingBean.afterPropertiesSet()方法RequestMappingHandlerMapping实现了这个方法,这里也是Spring MVC初始化的入口。
然后进入AbstractHandlerMethodMappingafterPropertiesSet
这个方法会进入该类的initHandlerMethods
负责从applicationContext中扫描beans,然后从bean中查找并注册处理器方法

2.3 真正的初始化方法initHandlerMethods()

Spring4.0.3版本

//Scan beans in the ApplicationContext, detect and register handler methods.
protected void initHandlerMethods() {
  ...

  //获取applicationContext中所有的bean name
  String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
        BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
        getApplicationContext().getBeanNamesForType(Object.class));
  
  //遍历beanName数组
  for (String beanName : beanNames) {
      //isHandler会根据bean来判断bean定义中是否带有Controller注解或RequestMapping注解
      if (isHandler(getApplicationContext().getType(beanName))){
        detectHandlerMethods(beanName);
      }
  }
  handlerMethodsInitialized(getHandlerMethods());
}

Spring5.0.4版本

   /**
     * Scan beans in the ApplicationContext, detect and register handler methods.
     */
    protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
        //获取所有容器托管的 beanName
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
                obtainApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    //获取 Class 信息
                    beanType = obtainApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                // isHandler()方法判断这个类是否有 @RequestMapping 或 @Controller
                if (beanType != null && isHandler(beanType)) {
                    //发现并注册 Controller @RequestMapping方法
                    detectHandlerMethods(beanName);
                }
            }
        }
        //Spring MVC框架没有实现、可以用于功能扩展
        handlerMethodsInitialized(getHandlerMethods());
    }
img_3a01bd85d2b1bd4201946da9c09ff2c0.png
RequestMappingHandlerMapping#isHandler
  • 上图方法即判断当前bean定义是否带有Controlller注解或RequestMapping注解
    如果只有RequestMapping生效吗?不会的!
    因为这种情况下Spring初始化的时候不会把该类注册为Spring bean,遍历beanNames时不会遍历到这个类,所以这里把Controller换成Compoent也可以,不过一般不这么做

2.3 从handler中获取HandlerMethod

当确定bean为handler后,便会从该bean中查找出具体的handler方法(即Controller类下的具体定义的请求处理方法),查找代码如下

Spring4.0.3

   /**
     * Look for handler methods in a handler
     * @param handler the bean name of a handler or a handler instance
     */
protected void detectHandlerMethods(final Object handler) {
  //获取当前Controller bean的class对象
  Class<?> handlerType = (handler instanceof String) ?
        getApplicationContext().getType((String) handler) : handler.getClass();

  //避免重复调用 getMappingForMethod 来重建 RequestMappingInfo 实例
  final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
  //同上,也是该Controller bean的class对象
  final Class<?> userType = ClassUtils.getUserClass(handlerType);
  
  //获取当前bean的所有handler method
  //根据 method 定义是否带有 RequestMapping 
  //若有则创建RequestMappingInfo实例
  Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
            @Override
            public boolean matches(Method method) {
                T mapping = getMappingForMethod(method, userType);
                if (mapping != null) {
                    mappings.put(method, mapping);
                    return true;
                }
                else {
                    return false;
                }
            }
  });

   //遍历并注册当前bean的所有handler method
   for (Method method : methods) {
            //注册handler method,进入以下方法
            registerHandlerMethod(handler, method, mappings.get(method));
   }
 

Spring5.0.4

    /**
     * Look for handler methods in a handler.
     * @param handler the bean name of a handler or a handler instance
     */
    protected void detectHandlerMethods(final Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            final Class<?> userType = ClassUtils.getUserClass(handlerType);
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            //获取 RequestMappingInfo
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            if (logger.isDebugEnabled()) {
                logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
            }
            for (Map.Entry<Method, T> entry : methods.entrySet()) {
                Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
                T mapping = entry.getValue();
                //注册RequestMappingInfo
                registerHandlerMethod(handler, invocableMethod, mapping);
            }
        }
    }

以上代码有两个地方有调用了getMappingForMethod

2.4 创建RequestMappingInfo

这一步会获取方法和类上的@RequestMapping并通过 @RequestMaping配置的参数生成相应的RequestMappingInfo。RequestMappingInfo中保存了很多Request需要匹配的参数。
1、匹配请求Url PatternsRequestCondition patternsCondition;
2、匹配请求方法 GET等 RequestMethodsRequestCondition methodsCondition;
3、匹配参数例如@Requestmaping(Params="action=DoXxx") ParamsRequestCondition paramsCondition;
4、匹配请求头信息 HeadersRequestCondition headersCondition;
5、指定处理请求的提交内容类型(Content-Type),例如application/json, text/html; ConsumesRequestCondition consumesCondition;
6、指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回ProducesRequestCondition producesCondition;
7、用于用户定制请求条件 RequestConditionHolder customConditionHolder; 举个例子,当我们有一个需求只要请求中包含某一个参数时都可以掉这个方法处理,就可以定制这个匹配条件。

    //使用方法和类型级别RequestMapping注解来创建RequestMappingInfo
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = null;
        //获取method的@RequestMapping
        RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
        if (methodAnnotation != null) {
            RequestCondition<?> methodCondition = getCustomMethodCondition(method);
            info = createRequestMappingInfo(methodAnnotation, methodCondition);
            //获取method所属bean的@RequtestMapping注解
            RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
            if (typeAnnotation != null) {
                RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
                //合并两个@RequestMapping注解
                info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
            }
        }
        return info;
    }

Spring5.0.4

   /**
     * Uses method and type-level @{@link RequestMapping} annotations to create
     * the RequestMappingInfo.
     * @return the created RequestMappingInfo, or {@code null} if the method
     * does not have a {@code @RequestMapping} annotation.
     * @see #getCustomMethodCondition(Method)
     * @see #getCustomTypeCondition(Class)
     */
    @Override
    @Nullable
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = createRequestMappingInfo(method);
        if (info != null) {
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                info = typeInfo.combine(info);
            }
        }
        return info;
    }


   /**
     * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
     * supplying the appropriate custom {@link RequestCondition} depending on whether
     * the supplied {@code annotatedElement} is a class or method.
     * @see #getCustomTypeCondition(Class)
     * @see #getCustomMethodCondition(Method)
     */
    @Nullable
    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        RequestCondition<?> condition = (element instanceof Class ?
                getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }

根据handler method创建RequestMappingInfo实例

  • 首先判断该 mehtod 是否含有 RequestMpping
    若有则直接根据该注解的内容创建RequestMappingInfo对象
  • 创建后判断当前method所属的bean是否也含有RequestMapping
    若含有则会根据该类上的注解创建一个RequestMappingInfo实例,然后再合并method上的RequestMappingInfo对象,最后返回合并后的对象。

回看detectHandlerMethods,有两处调用了getMappingForMethod,个人觉得这里是可以优化的,在第一处判断method是否为handler时,创建的RequestMappingInfo对象可以保存起来,直接拿来后面使用,就少了一次创建RequestMappingInfo实例过程。
然后紧接着进入registerHandlerMehtod

2.5 注册RequestMappingInfo

Spriing4.0.3版本

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
  //创建HandlerMethod
  HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
  HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
  //检查配置是否存在歧义性
  if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
      throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean()
            + "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '"
            + oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
  }
  this.handlerMethods.put(mapping, newHandlerMethod);
  if (logger.isInfoEnabled()) {
      logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
  }
  //获取@RequestMapping注解的value,然后添加value->RequestMappingInfo映射记录至urlMap中
  Set<String> patterns = getMappingPathPatterns(mapping);
  for (String pattern : patterns) {
      if (!getPathMatcher().isPattern(pattern)) {
        this.urlMap.add(pattern, mapping);
      }
  }
}

这里T的类型是RequestMappingInfo
这个对象就是封装的具体Controller下的方法的RequestMapping注解的相关信息
一个 RequestMapping注解对应一个RequestMappingInfo实例
HandlerMethodRequestMappingInfo类似,是对Controlelr下具体处理方法的封装。
第一行,根据handler和mehthod创建HandlerMethod对象
第二行通过handlerMethods map来获取当前mapping对应的HandlerMethod
然后判断是否存在相同的RequestMapping配置。如下这种配置就会导致此处抛
Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping found. Cannot map...
异常

Spring5.0.4版本

这一步 Spring Mvc 会保存
Map<RequestmappingInfo, HandlerMethod>
Map<url, RequestmappingInfo>的映射关系
DispatcherServlet.doDispatch()方法联系起来,doDispatch方法通过 url 在Map<url, RequestmappingInfo>中获取对应的 RequestMappingInfo 再根据request的信息和RequestMappingInfo的各个条件比较是否满足处理条件,如果不满足返回 404 如果满足通过RequestMappingInfo在Map<RequestmappingInfo, HandlerMethod>获取正真处理该请求的方法HandlerMathod.
最后通过反射执行具体的方法(Controller 方法)。


img_b92931494dee831ab796dda6ee0c12ee.png

进入register看看

public void register(T mapping, Object handler, Method method) {
            this.readWriteLock.writeLock().lock();
            try {
                //创建HandlerMethod
                HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                assertUniqueMethodMapping(handlerMethod, mapping);

                if (logger.isInfoEnabled()) {
                    logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
                }
                //保存RequestMappingInfo 和 HandlerMathod的映射关系
                this.mappingLookup.put(mapping, handlerMethod);

                List<String> directUrls = getDirectUrls(mapping);
                for (String url : directUrls) {
                    //保存 Request Url 和 RequestMappingInfo 的对应关系
                    this.urlLookup.add(url, mapping);
                }

                String name = null;
                if (getNamingStrategy() != null) {
                    name = getNamingStrategy().getName(handlerMethod, mapping);
                    addMappingName(name, handlerMethod);
                }

                CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
                if (corsConfig != null) {
                    this.corsLookup.put(handlerMethod, corsConfig);
                }

                this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
            }
            finally {
                this.readWriteLock.writeLock().unlock();
            }
        }
@Controller
@RequestMapping("/AmbiguousTest")
public class AmbiguousTestController {
    @RequestMapping(value = "/test1")
    @ResponseBody
    public String test1(){
        return "method test1";
    }

    @RequestMapping(value = "/test1")
    @ResponseBody
    public String test2(){
        return "method test2";
    }
}

在SpingMVC启动(初始化)阶段检查RequestMapping配置是否有歧义
确认配置正常以后会把该RequestMappingInfo和HandlerMethod对象添加至handlerMethods(LinkedHashMap)
接着把RequestMapping注解的value和ReuqestMappingInfo对象添加至urlMap中

registerHandlerMethod方法总结

  • 检查RequestMapping注解配置是否有歧义
  • 构建RequestMappingInfoHandlerMethod的映射map
    该map便是AbstractHandlerMethodMapping的成员变量handlerMethods。LinkedHashMap。
  • 构建AbstractHandlerMethodMapping的成员变量urlMap,MultiValueMap
    这个数据结构可以把它理解成Map。其中String类型的key存放的是处理方法上RequestMapping注解的value。就是具体的uri

有如下Controller

@Controller
@RequestMapping("/UrlMap")
public class UrlMapController {

    @RequestMapping(value = "/test1", method = RequestMethod.GET)
    @ResponseBody
    public String test1(){
        return "method test1";
    }

    @RequestMapping(value = "/test1")
    @ResponseBody
    public String test2(){
        return "method test2";
    }

    @RequestMapping(value = "/test3")
    @ResponseBody
    public String test3(){
        return "method test3";
    }
}

  • 初始化完成后,对应AbstractHandlerMethodMapping的urlMap的结构如下
    img_5571923b434a60342ee848f570562046.jpe
  • 以上便是SpringMVC初始化的主要过程

查找过程

  • 为了理解查找流程,带着一个问题来看,现有如下Controller
@Controller
@RequestMapping("/LookupTest")
public class LookupTestController {

    @RequestMapping(value = "/test1", method = RequestMethod.GET)
    @ResponseBody
    public String test1(){
        return "method test1";
    }

    @RequestMapping(value = "/test1", headers = "Referer=https://www.baidu.com")
    @ResponseBody
    public String test2(){
        return "method test2";
    }

    @RequestMapping(value = "/test1", params = "id=1")
    @ResponseBody
    public String test3(){
        return "method test3";
    }

    @RequestMapping(value = "/*")
    @ResponseBody
    public String test4(){
        return "method test4";
    }
}

  • 有如下请求

<figure style="box-sizing: inherit; margin: 24px 0px;">
img_cb2a7db4b0a6bd1fb03ab1ecfb62c812.jpe
image

</figure>

  • 这个请求会进入哪一个方法?
  • web容器(Tomcat、jetty)接收请求后,交给DispatcherServlet处理。FrameworkServlet调用对应请求方法(eg:get调用doGet),然后调用processRequest方法。进入processRequest方法后,一系列处理后,在line:936进入doService方法。然后在Line856进入doDispatch方法。在line:896获取当前请求的处理器handler。然后进入AbstractHandlerMethodMapping的lookupHandlerMethod方法。代码如下
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  List<Match> matches = new ArrayList<Match>();
   //根据uri获取直接匹配的RequestMappingInfos
  List<T> directPathMatches = this.urlMap.get(lookupPath);
  if (directPathMatches != null) {
      addMatchingMappings(directPathMatches, matches, request);
  }
  //不存在直接匹配的RequetMappingInfo,遍历所有RequestMappingInfo
  if (matches.isEmpty()) {
      // No choice but to go through all mappings
      addMatchingMappings(this.handlerMethods.keySet(), matches, request);
  }
   //获取最佳匹配的RequestMappingInfo对应的HandlerMethod
  if (!matches.isEmpty()) {
      Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
      Collections.sort(matches, comparator);

      if (logger.isTraceEnabled()) {
        logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
      }
      //再一次检查配置的歧义性
      Match bestMatch = matches.get(0);
      if (matches.size() > 1) {
        Match secondBestMatch = matches.get(1);
        if (comparator.compare(bestMatch, secondBestMatch) == 0) {
            Method m1 = bestMatch.handlerMethod.getMethod();
            Method m2 = secondBestMatch.handlerMethod.getMethod();
            throw new IllegalStateException(
                  "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
                  m1 + ", " + m2 + "}");
        }
      }

      handleMatch(bestMatch.mapping, lookupPath, request);
      return bestMatch.handlerMethod;
  }
  else {
      return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
  }
}

  • 进入lookupHandlerMethod方法,其中lookupPath="/LookupTest/test1",根据lookupPath,也就是请求的uri。直接查找urlMap,获取直接匹配的RequestMappingInfo list。这里会匹配到3个RequestMappingInfo。如下

<figure style="box-sizing: inherit; margin: 24px 0px;">
img_9affa9ac962f530d23790149a29cce9c.jpe
image

</figure>

  • 然后进入addMatchingMappings方法
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
  for (T mapping : mappings) {
      T match = getMatchingMapping(mapping, request);
      if (match != null) {
        matches.add(new Match(match, handlerMethods.get(mapping)));
      }
  }
}

  • 这个方法的职责是遍历当前请求的uri和mappings中的RequestMappingInfo能否匹配上,如果能匹配上,创建一个相同的RequestMappingInfo对象。再获取RequestMappingInfo对应的handlerMethod。然后创建一个Match对象添加至matches list中。执行完addMatchingMappings方法,回到lookupHandlerMethod。这时候matches还有3个能匹配上的RequestMappingInfo对象。接下来的处理便是对matchers列表进行排序,然后获取列表的第一个元素作为最佳匹配。返回Match的HandlerMethod。这里进入RequestMappingInfo的compareTo方法,看一下具体的排序逻辑。代码如下
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
  int result = patternsCondition.compareTo(other.getPatternsCondition(), request);
  if (result != 0) {
      return result;
  }
  result = paramsCondition.compareTo(other.getParamsCondition(), request);
  if (result != 0) {
      return result;
  }
  result = headersCondition.compareTo(other.getHeadersCondition(), request);
  if (result != 0) {
      return result;
  }
  result = consumesCondition.compareTo(other.getConsumesCondition(), request);
  if (result != 0) {
      return result;
  }
  result = producesCondition.compareTo(other.getProducesCondition(), request);
  if (result != 0) {
      return result;
  }
  result = methodsCondition.compareTo(other.getMethodsCondition(), request);
  if (result != 0) {
      return result;
  }
  result = customConditionHolder.compareTo(other.customConditionHolder, request);
  if (result != 0) {
      return result;
  }
  return 0;
}

  • 代码里可以看出,匹配的先后顺序是value>params>headers>consumes>produces>methods>custom,看到这里,前面的问题就能轻易得出答案了。在value相同的情况,params更能先匹配。所以那个请求会进入test3()方法。再回到lookupHandlerMethod,在找到HandlerMethod。SpringMVC还会这里再一次检查配置的歧义性,这里检查的原理是通过比较匹配度最高的两个RequestMappingInfo进行比较。此处可能会有疑问在初始化SpringMVC有检查配置的歧义性,这里为什么还会检查一次。假如现在Controller中有如下两个方法,以下配置是能通过初始化歧义性检查的。
@RequestMapping(value = "/test5", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String test5(){
    return "method test5";
}
@RequestMapping(value = "/test5", method = {RequestMethod.GET, RequestMethod.DELETE})
@ResponseBody
public String test6(){
    return "method test6";
}

  • 现在执行 http://localhost:8080/SpringMVC-Demo/LookupTest/test5 请求,便会在lookupHandlerMethod方法中抛
    java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/SpringMVC-Demo/LookupTest/test5'异常。这里抛该异常是因为RequestMethodsRequestCondition的compareTo方法是比较的method数。代码如下
public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) {
  return other.methods.size() - this.methods.size();
}

  • 什么时候匹配通配符?当通过urlMap获取不到直接匹配value的RequestMappingInfo时才会走通配符匹配进入addMatchingMappings方法。

总结

Spring Mvc的初始化过程比较清晰,整个过程Spring 提供的方法很多都是 protected修饰的这样我们可以通过继承灵活的定制我们的需求。
回顾一下整个初始化过程:

通过Spring容器对 InitalizingBean.afterPropertiesSet()方法的支持开始初始化流程。

获取容器中的所有 bean 通过 isHandler()方法区分是否需要处理。

通过方法上的@RequestMapping注解创建 RequestMappingInfo

注册、保存保存 Map<RequestmappingInfo, HandlerMethod>、Map<url, RequestmappingInfo>的映射关系方便后续调用和Request匹配。

目录
相关文章
|
15天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
45 2
|
1月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
20天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
44 9
|
2月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
120 5
|
2月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
2月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
58 2
|
2月前
|
设计模式 JavaScript Java
Spring 事件监听机制源码
Spring 提供了事件发布订阅机制,广泛应用于项目中。本文介绍了如何通过自定义事件类、订阅类和发布类实现这一机制,并展示了如何监听 SpringBoot 启动过程中的多个事件(如 `ApplicationStartingEvent`、`ApplicationEnvironmentPreparedEvent` 等)。通过掌握这些事件,可以更好地理解 SpringBoot 的启动流程。示例代码展示了从事件发布到接收的完整过程。
|
2月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
49 1
|
2月前
|
存储 开发框架 Java
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
文章详细介绍了Spring、IOC、DI的概念和关系,解释了控制反转(IOC)和依赖注入(DI)的原理,并提供了IOC的代码示例,阐述了Spring框架作为IOC容器的应用。
32 0
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
|
2月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
47 0