【Spring MVC】处理器映射器:AbstractHandlerMethodMapping源码分析

news2024/11/15 9:36:48

目录

一、继承体系

二、HandlerMapping

三、AbstractHandlerMapping

四、AbstractHandlerMethodMapping

4.1 成员属性

4.1.1 MappingRegistry内部类

4.2 AbstractHandlerMethodMapping的初始化

4.3 getHandlerInternal()方法:根据当前的请求url,获取对应的处理器HandlerMathod

五、RequestMappingInfoHandlerMapping

5.1 成员属性和构造方法

5.2 该类复写了一些方法

5.2.1 getMappingPathPatterns

5.2.2 getMatchingMapping

5.2.3 getMappingComparator

5.2.4 handleMatch,handleNoMatch

六、RequestMappingHandlerMapping

6.1 成员属性

6.2 主要方法

6.2.1 覆写了afterPropertiesSet()

6.2.2 isHandler()

6.2.3 getMappingForMethod()


我们现在最流行的就是使用注解实现Controller,那这就会涉及到AbstractHandlerMethodMapping,这个类在我们分析处理请求的源码中非常重要,所以这里单独拿出来分析。

一、继承体系

二、HandlerMapping

HandlerMapping是处理器映射器的顶层接口,只声明了1个方法–>getHandler–>调用getHandler实际上返回的是一个HandlerExecutionChain,这是典型的command的模式(命令模式)的使用,这个HandlerExecutionChain不但持有Handler本身,还包括了处理这个HTTP请求相关的拦截器,方法原型如下:

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

三、AbstractHandlerMapping

实现HandlerMapping的抽象实现,模板方法模式,将一些共性的方法抽象成1个类。其实现了getHandler。如下:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   // 首先根据request获取handler
   Object handler = getHandlerInternal(request);
   // 如果没有指定handler,就使用默认的
   if (handler == null) {
      handler = getDefaultHandler();
   }
   if (handler == null) {
      return null;
   }
   // Bean name or resolved handler?
   if (handler instanceof String) {
      String handlerName = (String) handler;
      handler = getApplicationContext().getBean(handlerName);
   }
   // 获取到了handler之后,再去获取拦截器,将两者封装到处理器执行链中返回
   return getHandlerExecutionChain(handler, request);
}

流程:

  1. 根据request获取对应的handler,该方法是1个抽象方法,由子类来实现,如下:

        

protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
  1. 如果没有对应的handler,就是要默认的handler
  2. 如果没有默认的handler,返回null
  3. 如果获取到的handler是一个字符串,说明这个是Bean名,则通过名称取出对应的 handler bean
  4. 把handler 封装到HandlerExecutionChain中并加上拦截器。如下:

        

		protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		    // 1. 获得HandlerExecutionChain
		    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
		            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
		    // 2. 根据请求获得对应的Path
		    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		    // 3. 遍历adaptedInterceptors
		    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		        // 3.1 如果是MappedInterceptor,并且匹配当前的path,则加入到HandlerExecutionChain中
		        if (interceptor instanceof MappedInterceptor) {
		            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
		            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
		                chain.addInterceptor(mappedInterceptor.getInterceptor());
		            }
		        }
		        // 3.2 否则,直接加入到HandlerExecutionChain
		        else {
		            chain.addInterceptor(interceptor);
		        }
		    }
		    return chain;
}
  1. 获得HandlerExecutionChain
  2. 根据请求获得对应的Path
  3. 遍历adaptedInterceptors(拦截器)
    1. 如果是MappedInterceptor,并且匹配当前的path,则加入到HandlerExecutionChain中
    2. 否则,直接加入到HandlerExecutionChain

四、AbstractHandlerMethodMapping

AbstractHandlerMethodMapping是一个泛型类,其泛型参数T–>用来代表匹配handler的条件专门使用的一种类,这里的条件就不只是url了,还可以有很多其他条件,如request的类型,请求的参数,header等都可以作为匹配的HandlerMethod的条件。默认使用的是RequestMappingInfo,这个也是最常见的情况(只要是用注解实现Controller一般都是用RequestMappingInfo作为匹配条件类)。AbstractHandlerMethodMapping实现了InitializingBean接口。

4.1 成员属性

// scpoed 代理 bean的name的前缀。用来去除handler method的判断
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";

// cors请求并且是options类型的请求并且请求头中含有Access-Control-Request-Method时返回的HandlerMethod
private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =
        new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));

private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();

static {
    ALLOW_CORS_CONFIG.addAllowedOrigin("*");
    ALLOW_CORS_CONFIG.addAllowedMethod("*");
    ALLOW_CORS_CONFIG.addAllowedHeader("*");
    ALLOW_CORS_CONFIG.setAllowCredentials(true);
}

// 如果为true,则在当前applicationContext和祖先applicationContext中获取所有的bean,如果为false,则在当前上下文获得所有的bean
private boolean detectHandlerMethodsInAncestorContexts = false;

// 向MappingRegistry中的nameLookup进行注册时用来生成beanName,这里默认使用的是RequestMappingInfoHandlerMethodMappingNamingStrategy
// 其规则为:类名里的大写字母组合+"#"+方法名.
private HandlerMethodMappingNamingStrategy<T> namingStrategy;

// 用来存储各种映射关系
private final MappingRegistry mappingRegistry = new MappingRegistry();

4.1.1 MappingRegistry内部类

这里有必要说明一下MappingRegistry类,它是AbstractHandlerMethodMapping的内部类,其成员属性如下:

class MappingRegistry {
    private final Map<T, MappingRegistration<T>> registry = new HashMap<T, MappingRegistration<T>>();

    // 保存着匹配条件(也就是RequestMappingInfo)和HandlerMethod的对应关系
    private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();

    // 保存着url与匹配条件(也就是RequestMappingInfo)的对应关系,当然这里的url是pattren式的,可以使用通配符。
    // 由于RequestMappingInfo可以同时使用多种不同的匹配方式而不只是url一种,所以反过来说同一个url就可能有多个RequestMappingInfo与之对应
    // 这里的RequestMappingInfo其实就是在@RequestMapping中注释的内容
    private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();

    // 这个Map是spring mvc 4 新增的,保存着name(Controller中处理请求的方法名)与HandlerMethod的对应关系,这个name是从HandlerMethodMappingNamingStrategy的实现类从
    // HandlerMethod中解析处理的,默认使用的是RequestMappingInfoHandlerMethodMappingNamingStrategy,解析规则是:
    // 类名里的大写字母组合+"#"+方法名。这个在正常的匹配过程不需要使用,它主要用在MvcUriComponentsBuilder里,可以根据name获取相应的url
    private final Map<String, List<HandlerMethod>> nameLookup =
            new ConcurrentHashMap<String, List<HandlerMethod>>();

    private final Map<HandlerMethod, CorsConfiguration> corsLookup =
            new ConcurrentHashMap<HandlerMethod, CorsConfiguration>();

    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    ...
}

注意:带有通配符的路径匹配不在urlLookup属性参数中,只存在了registry。直接匹配所有的参数路径中,才会两者都存。

4.2 AbstractHandlerMethodMapping的初始化

由于AbstractHandlerMethodMapping实现了InitializingBean,因此在其初始化过程中,会调用afterPropertiesSet方法,如下:

public void afterPropertiesSet() {
    initHandlerMethods();
}

这里我们就可以看出,在初始化AbstractHandlerMethodMapping类的时候,就自动调用了initHandlerMethods方法。这个方法其实就帮我们提前建立起了urlmethod之间的映射关系。在后面处理请求的时候可以根据url直接找到要处理该请求的method。

protected void initHandlerMethods() {
    if (logger.isDebugEnabled()) {
        logger.debug("Looking for request mappings in application context: " + getApplicationContext());
    }

    // 获取ApplicationContext中的所有bean的name
    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
            getApplicationContext().getBeanNamesForType(Object.class));

    // 遍历所有的bean name
    for (String beanName : beanNames) {
        // 如果bean nanme 不是 scopedTarget开头的,则获得其类型
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            Class<?> beanType = null;
            try {
                // 获取bean的类型
                beanType = getApplicationContext().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);
                }
            }
            // 如果beanType不为空,并且是一个处理器,则进行处理
            if (beanType != null && isHandler(beanType)) {
                detectHandlerMethods(beanName);
            }
        }
    }
    // 所有的处理器方法都初始化完成后,调用子类的方法
    handlerMethodsInitialized(getHandlerMethods());
}

流程:

  1. 获得ApplicationContext中的所有bean的name
  2. 遍历
    1. 如果bean name 不是 scopedTarget开头的,则获得其类型
    2. 如果该bean是一个handler(处理器),则调用detectHandlerMethods()对其进行注册。detectHandlerMethods()方法源码如下:

        

		protected void detectHandlerMethods(final Object handler) {
		    // 获得handler的类型
		    Class<?> handlerType = (handler instanceof String ?
		            getApplicationContext().getType((String) handler) : handler.getClass());
		
		    // 如果是cglib代理的子对象类型,则返回父类型,否则直接返回传入的类型
		    final Class<?> userType = ClassUtils.getUserClass(handlerType);
		
		    // 获取当前bean里所有符合Handler要求的Method(也就是获取当前Controller中所有的用来处理请求的方法)
		    Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
		            new MethodIntrospector.MetadataLookup<T>() {
		                @Override
		                public T inspect(Method method) {
		                    try {
		                        return getMappingForMethod(method, userType);
		                    }
		                    catch (Throwable ex) {
		                        throw new IllegalStateException("Invalid mapping on handler class [" +
		                                userType.getName() + "]: " + method, ex);
		                    }
		                }
		            });
		
		    // 将符合要求的methods注册到mappingRegistry中,也就是保存到mappingRegistry的3个map中
		    for (Map.Entry<Method, T> entry : methods.entrySet()) {
		        Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
		        T mapping = entry.getValue();
		        registerHandlerMethod(handler, invocableMethod, mapping);
		    }
		}
  1. 获得handler的类型,如果是cglib代理的子对象类型,则返回父类型,否则直接返回传入的类型。
  2. 获取当前bean里所有符合Handler要求的Method,其中会回调getMappingForMethod方法,该方法是个抽象方法,由子类实现。
  3. 将符合要求的methods注册,代码如下:
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
	// 将method注册到mappingRegistry中
	this.mappingRegistry.register(mapping, handler, method);
}

MappingRegistry#register()将url和handler之间的映射关系进行了注册,实现如下:

public void register(T mapping, Object handler, Method method) {
    this.readWriteLock.writeLock().lock();
    try {
        // 根据传入的handler和method创建HandlerMethod处理器类型的对象
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);

        // 检查是否在mappingLookup已经存在,如果存在而且和现在传入的不同则抛出异常
        assertUniqueMethodMapping(handlerMethod, mapping);

        if (logger.isInfoEnabled()) {
            logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
        }
        
        // 添加到mappingLookup中
        this.mappingLookup.put(mapping, handlerMethod);

        // 添加到urlLookup
        List<String> directUrls = getDirectUrls(mapping);
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }
        // 添加到nameLookup
        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }
        // 实例化CorsConfiguration
        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }
        this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}

步骤:

  1. 创建HandlerMethod,代码如下:

        

protected HandlerMethod createHandlerMethod(Object handler, Method method) {
    HandlerMethod handlerMethod;
    // 如果handler是String类型,那么就是beanName
    if (handler instanceof String) {
        String beanName = (String) handler;
        // 根据beanName获取对应的bean
        handlerMethod = new HandlerMethod(beanName,
                getApplicationContext().getAutowireCapableBeanFactory(), method);
    }
    else {
        handlerMethod = new HandlerMethod(handler, method);
    }
    return handlerMethod;
}
  1. 检查是否在mappingLookup已经存在,如果存在而且和现在传入的不同则抛出异常。代码如下:
private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {
    // 检查是否有重复的映射
    HandlerMethod handlerMethod = this.mappingLookup.get(mapping);
    if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {
        throw new IllegalStateException(
                "Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +
                newHandlerMethod + "\nto " + mapping + ": There is already '" +
                handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");
    }
}
  1. 添加到mappingLookup中。
  2. 添加到urlLookup,其中getDirectUrls–>获得mapping的Path,如果不含有*或者含有?的话,则添加到结果集中。代码如下:
private List<String> getDirectUrls(T mapping) {
    List<String> urls = new ArrayList<String>(1);
    for (String path : getMappingPathPatterns(mapping)) {
        if (!getPathMatcher().isPattern(path)) {
            urls.add(path);
        }
    }
    return urls;
}

AntPathMatcher#isPattern,如下:

public boolean isPattern(String path) {
	return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
}
  1. 添加到nameLookup。
  2. 实例化CorsConfiguration,如果不为null,则添加到corsLookup。此处默认返回null,由子类复写。
  3. 添加到registry中。

handlerMethodsInitialized()是模板方法,空实现。

4.3 getHandlerInternal()方法:根据当前的请求url,获取对应的处理器HandlerMathod

getHandlerInternal()是一个很重要的方法,它的实现如下(删去多余代码):

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	// 1.利用request截取用于匹配的url有效路径(获取当前的请求路径)
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	// 2. 使用lookupHandlerMethod方法通过lookupPath和request找对应的HandlerMethod
	HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
	// 3. 如果可以找到handlerMethod则调用createWithResolvedBean方法创建新的HandlerMethod
	return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); 
}
  1. 利用request对象截取用于匹配的url有效路径。
  2. 使用lookupHandlerMethod方法通过lookupPath和request找HandlerMethod。代码如下:

        

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    // match是内部类,用于保存匹配条件和HandlerMethod
    List<Match> matches = new ArrayList<Match>();
    // 根据请求路径lookupPath获取到匹配条件
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        // 将匹配到的条件添加到matches
        addMatchingMappings(directPathMatches, matches, request);
    }
    // 如果不能直接使用lookupPath得到匹配条件,则将所有匹配条件加入到matches
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }
    // 对matches进行排序,并取第一个作为bestMatch。如果前面两个排序相同则抛出异常
    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) {
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            // 如果matches有多个匹配的,则将第2个和第一个进行比较,看顺序是否一样,如果是一样的话,则抛出异常
            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方法的默认实现是将lookupPath设置到request的属性,将更多的参数设置到了request,主要是为了以后使用时方便
        handleMatch(bestMatch.mapping, lookupPath, request);
        // 返回匹配的HandlerMethod
        return bestMatch.handlerMethod;
    }
    else {
        // 如果没有匹配的,则调用handleNoMatch方法,子类RequestMappingInfoHandlerMapping进行了重写
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

Match内部类讲解:

  1. 根据lookupPath获取到匹配条件,将匹配到的条件添加到matches
  2. 如果不能直接使用lookupPath得到匹配条件,则将所有匹配条件加入到matches
  3. 如果matches非空
    1. 对matches进行排序,并取第一个作为bestMatch,如果前面两个排序相同则抛出异常
    2. 在返回前做一些处理。默认实现是将lookupPath设置到request的属性,子类RequestMappingInfoHandlerMapping进行了重写,将更多的参数设置到了request。主要是为了以后使用时方便
  4. 否则,调用handleNoMatch,默认返回null。

  1. 如果可以找到handlerMethod则调用createWithResolvedBean方法创建新的HandlerMethod。代码如下:
// 该方法用于创建一个HandlerMethod对象
// 此时handlerMethod中只有匹配条件,还没有handler处理器,这个方法就是要将处理器和匹配条件绑定在一起,创建HandlerMethod对象
public HandlerMethod createWithResolvedBean() {
    Object handler = this.bean;
    if (this.bean instanceof String) {
        String beanName = (String) this.bean;
        handler = this.beanFactory.getBean(beanName);
    }
    return new HandlerMethod(this, handler);
}

五、RequestMappingInfoHandlerMapping

RequestMappingInfoHandlerMapping–> 继承自AbstractHandlerMethodMapping。

5.1 成员属性和构造方法

// 对OPTIONS请求的处理时用到
private static final Method HTTP_OPTIONS_HANDLE_METHOD;

static {
    try {
        HTTP_OPTIONS_HANDLE_METHOD = HttpOptionsHandler.class.getMethod("handle");
    }
    catch (NoSuchMethodException ex) {
        // Should never happen
        throw new IllegalStateException("Failed to retrieve internal handler method for HTTP OPTIONS", ex);
    }
}

// 构造方法
protected RequestMappingInfoHandlerMapping() {
    setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
}

5.2 该类复写了一些方法

5.2.1 getMappingPathPatterns

protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
    return info.getPatternsCondition().getPatterns();
}

该方法是在hander注册的时候调用,如下:

5.2.2 getMatchingMapping

检查给定的RequestMappingInfo是否匹配当前的请求,返回RequestMappingInfo,代码如下:

protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
    return info.getMatchingCondition(request);
}

5.2.3 getMappingComparator

返回1个比较RequestMappingInfo的Comparator,在有多个Handler匹配当前请求时用到。代码如下:

protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
    return new Comparator<RequestMappingInfo>() {
        @Override
        public int compare(RequestMappingInfo info1, RequestMappingInfo info2) {
            return info1.compareTo(info2, request);
        }
    };
}

5.2.4 handleMatchhandleNoMatch

比较简单,这里就不再贴出

六、RequestMappingHandlerMapping

RequestMappingHandlerMapping–> 继承自RequestMappingInfoHandlerMapping。根据在实现Controller接口或者被@Controller注解的类中的在类和方法上声明的@RequestMapping,创建一个 RequestMappingInfo。

6.1 成员属性

// 是否使用后缀匹配(.*)当对请求进行模式匹配时,如果可用时,则/users 对/users.*也匹配.默认是true.
private boolean useSuffixPatternMatch = true;

// 是否后缀匹配应该只对ContentNegotiationManager中注册的扩展符匹配时生效.这一般建议减少歧义和避免问题比如当.出现在路径的情况下
private boolean useRegisteredSuffixPatternMatch = false;

// 是否有无斜杠都匹配,如果启用的化,则/users 也匹配 /users/.默认是true
private boolean useTrailingSlashMatch = true;

// 内容协商
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

// 这里使用的是EmbeddedValueResolver
private StringValueResolver embeddedValueResolver;

// RequestMappingInfo的Builder类,用来创建RequestMappingInfo的
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

6.2 主要方法

6.2.1 覆写了afterPropertiesSet()

public void afterPropertiesSet() {
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setUrlPathHelper(getUrlPathHelper());
    this.config.setPathMatcher(getPathMatcher());
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    this.config.setContentNegotiationManager(getContentNegotiationManager());
    super.afterPropertiesSet();
}

6.2.2 isHandler()

protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

调用链如下:

6.2.3 getMappingForMethod()

使用在类和方法上声明的@RequestMapping来创建RequestMappingInfo。代码如下:

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    // 1. 根据Method上的@RequestMapping创建RequestMappingInfo
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        // 2. 根据类上的@RequestMapping创建RequestMappingInfo
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {
            // 3. 合并
            info = typeInfo.combine(info);
        }
    }
    return info;
}

createRequestMappingInfo(),如下:

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    // 获取@RequestMapping 注解
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    // 此处返回的都是null
    RequestCondition<?> condition = (element instanceof Class ?
            getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
  1. 获取@RequestMapping 注解
  2. 获得RequestCondition,此处返回的都是null
  1. 如果requestMapping等于null,则返回null,否则根据RequestMapping创建RequestMappingInfo。代码如下:
protected RequestMappingInfo createRequestMappingInfo(
    RequestMapping requestMapping, RequestCondition<?> customCondition) {
return RequestMappingInfo
        .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
        .methods(requestMapping.method())
        .params(requestMapping.params())
        .headers(requestMapping.headers())
        .consumes(requestMapping.consumes())
        .produces(requestMapping.produces())
        .mappingName(requestMapping.name())
        .customCondition(customCondition)
        .options(this.config)
        .build();
}

相关文章:【Spring MVC】Spring MVC框架的介绍及其使用方法

                  【Spring MVC】Spring MVC的执行流程与源码分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1469135.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RocketMQ-架构与设计

RocketMQ架构与设计 一、简介二、框架概述1.设计特点 三、架构图1.Producer2.Consumer3.NameServer4.BrokerServer 四、基本特性1.消息顺序性1.1 全局顺序1.2 分区顺序 2.消息回溯3.消息重投4.消息重试5.延迟队列&#xff08;定时消息&#xff09;6.重试队列7.死信队列8.消息语…

智慧城市与数字孪生:共创未来城市新篇章

一、引言 随着科技的飞速发展&#xff0c;智慧城市与数字孪生已成为现代城市建设的核心议题。智慧城市注重利用先进的信息通信技术&#xff0c;提升城市治理水平&#xff0c;改善市民生活品质。而数字孪生则通过建立物理城市与数字模型之间的连接&#xff0c;为城市管理、规划…

接近于pi的程序

在一个平静的午后&#xff0c;两个神秘的数字悄然相遇了。它们分别是-1031158223和-328227871。这两个数字看起来普普通通&#xff0c;但谁知它们背后隐藏着一段令人惊叹的奇幻之旅。 这两个数字其实是π的两位探险家&#xff0c;它们决定通过一次除法运算来探索π的奥秘。它们…

浅谈密码学

文章目录 每日一句正能量前言什么是密码学对称加密简述加密语法Kerckhoffs原则常用的加密算法现代密码学的原则威胁模型&#xff08;按强度增加的顺序&#xff09; 密码学的应用领域后记 每日一句正能量 人生在世&#xff0c;谁也不能做到让任何人都喜欢&#xff0c;所以没必要…

Vue+SpringBoot打造快递管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 数据中心模块2.2 快递类型模块2.3 快递区域模块2.4 快递货架模块2.5 快递档案模块 三、界面展示3.1 登录注册3.2 快递类型3.3 快递区域3.4 快递货架3.5 快递档案3.6 系统基础模块 四、免责说明 一、摘要 1.1 项目介绍 …

【Java程序设计】【C00294】基于Springboot的车辆充电桩管理系统(有论文)

基于Springboot的车辆充电桩管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的车辆充电桩管理系统 本系统前台功能模块分为&#xff1a;首页功能和用户后台管理 后台功能模块分为&#xff1a;管理员功能和…

博途PLC PID仿真(单容水箱液位高度控制含变积分变增益测试)

单容水箱和双荣水箱的微分方程和数值求解,可以参考下面文章链接: https://rxxw-control.blog.csdn.net/article/details/131139432https://rxxw-control.blog.csdn.net/article/details/131139432这篇博客我们利用欧拉求解器在PLC里完成单容水箱的数学建模。PLC也可以和MATL…

Vue模板引用之ref特殊属性

1. 使用实例 <template><input ref"input" name"我是input的name" /><br /><ul><li v-for"arr in array" :key"arr" id"111" ref"itemRefs">{{arr}}</li></ul> </…

我的NPI项目之设备系统启动(八) -- Android14的GKI2.0开发步骤和注意事项

GKI是什么&#xff1f; Google为什么要推行GKI&#xff1f; GKI全称General Kernel Image。GKI在framework和kernel之间提供了标准接口&#xff0c;使得android OS能够轻松适配/维护/兼容不同的设备和linux kernel。 Google引入GKI的目的是将Framework和Kernel进一步的解耦。因…

汇编反外挂

在软件保护领域&#xff0c;尤其是游戏保护中&#xff0c;反外挂是一个重要的议题。外挂通常指的是一种第三方软件&#xff0c;它可以修改游戏数据、操作游戏内存或提供其他作弊功能&#xff0c;从而给玩家带来不公平的优势。为了打击外挂&#xff0c;游戏开发者会采取一系列措…

【数据结构和算法初阶(C语言)】时间复杂度(衡量算法快慢的高端玩家,搭配例题详细剖析)

目录 1.算法效率 1.1如何衡量一个算法的好坏 1.2 算法的复杂度 2.主菜-时间复杂度 2.1 时间复杂度的概念 2.2 大O的渐进表示法 2.2.1算法的最好&#xff0c;最坏和平均的情况 3.经典时间复杂度计算举例 3.1计算冒泡排序的时间复杂度 3.2计算折半查找的时间复杂度 3.…

SQL 中如何实现多表关联查询?

阅读本文之前请参阅----MySQL 数据库安装教程详解&#xff08;linux系统和windows系统&#xff09; 在SQL中&#xff0c;多表关联查询是通过使用JOIN操作来实现的&#xff0c;它允许你从两个或多个表中根据相关列的值来检索数据。以下是几种常见的JOIN类型&#xff1a; …

C语言:指针的进阶讲解

目录 1. 二级指针 1.1 二级指针是什么&#xff1f; 1.2 二级指针的作用 2. 一维数组和二维数组的本质 3. 指针数组 4. 数组指针 5. 函数指针 6. typedef的使用 7. 函数指针数组 7.1 转移表 1. 二级指针 如果了解了一级指针&#xff0c;那二级指针也是可以很好的理解…

(每日持续更新)jdk api之ObjectStreamException基础、应用、实战

博主18年的互联网软件开发经验&#xff0c;从一名程序员小白逐步成为了一名架构师&#xff0c;我想通过平台将经验分享给大家&#xff0c;因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验&#xff0c;晚上进行用心精简、整理、总结、定稿&…

联想开天昭阳N4620Z笔记本如何恢复出厂麒麟操作系统(图解)

联想开天昭阳N4620Z笔记本简单参数&#xff1a; 中央处理器&#xff1a;KX-6640MA G2 内存&#xff1a;8GB 固态硬盘&#xff1a;512GB SSD 显示器&#xff1a;14.0”FHD 电池&#xff1a;4Cell 操作系统&#xff1a;麒麟KOS中文RTM&#xff08;试用版&#xff09; 此款笔…

[深度学习]yolov9+bytetrack+pyqt5实现目标追踪

【简介】 目标追踪简介 目标追踪是计算机视觉领域中的一个热门研究方向&#xff0c;它涉及到从视频序列中实时地、准确地跟踪目标对象的位置和运动轨迹。随着深度学习技术的快速发展&#xff0c;基于深度学习的目标追踪方法逐渐展现出强大的性能。其中&#xff0c;YOLOv9&…

深入浅出:探究过完备字典矩阵

在数学和信号处理的世界里&#xff0c;我们总是在寻找表达数据的最佳方式。在这篇博文中&#xff0c;我们将探讨一种特殊的矩阵——过完备字典矩阵&#xff0c;这是线性代数和信号处理中一个非常有趣且实用的概念。 什么是过完备字典矩阵&#xff1f; 首先&#xff0c;我们先…

Github 2024-02-24 开源项目日报Top10

根据Github Trendings的统计&#xff0c;今日(2024-02-24统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目5TypeScript项目2C项目1Rust项目1JavaScript项目1HTML项目1Jupyter Notebook项目1 Python - 100天…

【黑马程序员】2、TypeScript介绍_黑马程序员前端TypeScript教程,TypeScript零基础入门到实战全套教程

课程地址&#xff1a;【黑马程序员前端TypeScript教程&#xff0c;TypeScript零基础入门到实战全套教程】 https://www.bilibili.com/video/BV14Z4y1u7pi/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 2、TypeScript初体验 2.1 安装编译TS的工…

C# WPF 桌面应用程序使用 SQlite 数据库

我们在开发 WPF 桌面应用程序时&#xff0c;数据库存的使用是必不可少的&#xff0c;除非你的应用没有数据存储的需求&#xff0c;有了数据存储需求&#xff0c;我们就会面临使用什么样的数据库的选择问题&#xff0c;我的选择方案是&#xff0c;单机版的应用我优先选择 Sqlite…