转载

享读SpringMVC源码2-@RequestMapping注解源码

一个人可以走的很快,但一群人可以走的更远 ,新的一年我想遇到优秀的你

1. 回顾

上节,说了Handler的4种定义方式,以及Handler与HandlerMapping的关系。

@RequestMapping方式是我们最常用的定义handler的方式。

RequestMappingHandlerMapping 负责把@RequestMapping方式定义handler登记在册。

这一节从源码角度来看看,@RequestMapping注解如何让一个方法变成一个handler。

2. 精华总结

@RequestMapping注解背后的做的工作其实挺多,但是我们可以抓住几个关键点:

  • 处理时机:Bean初始化
  • 处理方式:遍历所有Bean,判断handler类,找到类中handler
  • 处理结果:关系映射缓存

3. 注册Handler

@RequestMapping注解的解析逻辑,是伴随着RequestMappingHandlerMapping 初始化过程完成的

一切从RequestMappingHandlerMapping.afterPropertiesSet方法开始。

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements MatchableHandlerMapping, EmbeddedValueResolverAware {

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();
    }
}
复制代码

主要做了RequestMappingInfo的Builder助手的配置操作,调用父类的afterPropertiesSet()方法

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
   public void afterPropertiesSet() {
           initHandlerMethods();
       }
       //初始化HandlerMethod
   protected void initHandlerMethods() {
           //获取所有bean
           String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                   BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                   getApplicationContext().getBeanNamesForType(Object.class));
        //遍历判断
           for (String beanName : beanNames) {
               if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                   Class<?> beanType = null;
                   try {
                       beanType = getApplicationContext().getType(beanName);
                   }
                   catch (Throwable ex) {
                       if (logger.isDebugEnabled()) {
                           logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                       }
                   }
                   // 如果是handler, 
                   if (beanType != null && isHandler(beanType)) {
                       //识别出Hadnler中的HandlerMethod
                       detectHandlerMethods(beanName);
                   }
               }
           }
           handlerMethodsInitialized(getHandlerMethods());
       }
}
复制代码

这里的逻辑还是比较清晰的。遍历BeanFactory仓库中Bean,挨个检查类是否是Handler,如果是Handler就去Handler查找HandlerMethod

也就是说: 一个Bean在此会经历两个重点方法,isHandler方法与detectHandlerMethods方法

3.1 isHandler-判

判断当前Bean是否是一个handler,方法实现在RequestMappingHandlerMapping 类中。

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements MatchableHandlerMapping, EmbeddedValueResolverAware {
protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }
}
复制代码

类上有@Controller注解或者@RequestMapping 此处就看做是一个Handler

//@RequestMapping("/user")
@Controller
public class UserController {
}
复制代码

虽然@Controller注解的本质也是表示一个@Component

@Component

public @interface Controller {

}

但是,我不用@Component 来表示一个代表Controller类。

其原因就是@Controller 会在RequestMappingHandlerMapping 中按@Controller注解判定为handler。

所以说:@Controller 是表示这是一个跟web有关的Bean

3.2 detectHandlerMethods

判定为Bean是一个handler 之后,下面就是把类中能处理请求的方法找到,登记在册。也就是 @RequestMapping注解的方法的解析过程

两个动作: 找到,登记

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    protected void detectHandlerMethods(final Object handler) {
            Class<?> handlerType = (handler instanceof String ?
                    getApplicationContext().getType((String) handler) : handler.getClass());
            final Class<?> userType = ClassUtils.getUserClass(handlerType);
            //找:方法搜索器,搜索当前bean继承体系上的所有方法中,符合我们需要的method
            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);
                            }
                        }
                    });

            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();
                registerHandlerMethod(handler, invocableMethod, mapping);
            }
}}
复制代码

3.2.1 寻找

MethodIntrospector 能够搜索到当前类 所有关联的方法(包括,接口,父类,同时还处理参数化方法以及接口和基于类代理的常见场景)中,符合我们要求的方法,返回与之关联的元数据。

MethodIntrospector.MetadataLookup 用于定义条件,匹配当前类所有关联的方法中符合我们要求的方法。

getMappingForMethod就是这个条件。getMappingForMethod是一个抽象方法,在中定义RequestMappingHandlerMapping 中

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements MatchableHandlerMapping, EmbeddedValueResolverAware {
    //获取RequestMappingInfo
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        //查找method是是否有RequestMappingInfo
        RequestMappingInfo info = createRequestMappingInfo(method);
        //如果方法上有
        if (info != null) {
            //获取类上的RequestMappingInfo
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                //合并方法与类上的注解信息。 这也是为啥。方法的访问路径是类路径+方法路径。
                info = typeInfo.combine(info);
            }
        }
        return info;
    }    
    // 根据方法或者类上的注解信息,创建RequestMappingInfo
    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        //获取当前element上的RequestMapping 注解元信息 (包括直接@RequestMapping注解,或者间接RequestMapping 如@GetMapping)
        // 将查询出的多个annotationType类型注解属性合并到查询的第一个注解中
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);

        //获取自定义的条件。
        RequestCondition<?> condition = (element instanceof Class ?
                getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        //根据注解信息,与自定义条件。创建一个RequestMappingInfo
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }

    //创建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();
        }
}
复制代码

简单来说:找的过程,就是 遍历 当前Bean相关的所有方法。 解析 方法上的@RequestMapping注解与类上的@RequestMapping注解, 合并 两者信息,创建出一个 RequestMappingInfo 实例来表示当前方法的@RequestMapping注解元信息

合并这点其实就是解释了 为啥请求处理方法的 Url路径= 类路径+方法路径

最终找的结果:得到一个 以method为K ,以 RequestMappingInfo 为Value的Map methods。也就是解析到了方法与方法对应 RequestMappingInfo 关系映射。

3.2.2 登记(缓存):

登记动作发生在registerHandlerMethod 方法中。此时就是把各种关系缓存起来。

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    protected void detectHandlerMethods(final Object handler) {
            Class<?> handlerType = (handler instanceof String ?
                    getApplicationContext().getType((String) handler) : handler.getClass());
            final Class<?> userType = ClassUtils.getUserClass(handlerType);
            //找:方法搜索器,搜索当前bean继承体系上的所有方法中,符合我们需要的method
            //....
            //注册,
            for (Map.Entry<Method, T> entry : methods.entrySet()) {
                //找到可调用的方法
                Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
                T mapping = entry.getValue();
                //注册
                registerHandlerMethod(handler, invocableMethod, mapping);
            }}
    protected void registerHandlerMethod(Object handler, Method method, T mapping) {
            this.mappingRegistry.register(mapping, handler, method);
        }

    class MappingRegistry {
        //三个参数直接信息, handler类,hangler方法
        public void register(T mapping, Object handler, Method method) {
                    this.readWriteLock.writeLock().lock();//线程安全
                    try {
                        //创建一个HandlerMethod
                        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                        //检查路径是否被定义过,确保一个路径只能找到一个method(路径定义重复的错误就是在这里抛出的)
                        assertUniqueMethodMapping(handlerMethod, mapping);

                        //日志,启动时可以看到
                        if (logger.isInfoEnabled()) {
                            logger.info("Mapped /"" + mapping + "/" onto " + handlerMethod);
                        }

                        // mappingLookup: 缓存Mapping和handlerMethod的关系  
                        this.mappingLookup.put(mapping, handlerMethod);

                        //获取注解里的路径信息。
                        List<String> directUrls = getDirectUrls(mapping);
                        //缓存路径与mapping的关系
                        for (String url : directUrls) {
                            this.urlLookup.add(url, mapping);
                        }

                        // 保存name和handlerMethod的关系  同样也是一对多
                        String name = null;
                        if (getNamingStrategy() != null) {
                            name = getNamingStrategy().getName(handlerMethod, mapping);
                            addMappingName(name, handlerMethod);
                        }

                        //初始化跨域配置,并缓存handlerMethod与跨域配置的
                        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
                        if (corsConfig != null) {
                            this.corsLookup.put(handlerMethod, corsConfig);
                        }

                        // 注册mapping和MappingRegistration的关系                
                        this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
                    }
                    finally {
                        this.readWriteLock.writeLock().unlock();
                    }
                }
    }
}
复制代码

总结注册过程做了啥:

  • 根据handler,method创建一个HandlerMethod实例
  • 缓存mapping和handlerMethod的关系( mappingLookup )
  • 缓存路径url与mapping的关系()
  • 缓存name和handlerMethod的关系
  • 根据mapping,handlerMethod,urls,name创建一个MappingRegistration相关实例
  • 缓存mapping和MappingRegistration的关系

Bean初始化完成后,我们定义的接口方法也就以各种形态,各种关系缓存起来了。

4. 获取handler

当请求来到DispatcherServlet#doDispatch 方法时。遍历所有的handlerMapping, 并尝试从其实例中获取Handler, 如果获取到就返回。

mappedHandler = getHandler(processedRequest);

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }
复制代码

对于我们以 @RequestMapping("/xxxx") 注解定义的handler。RequestMappingHandlerMapping的父类AbstractHandlerMapping 提供对getHandler的实现。

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
   public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
           Object handler = getHandlerInternal(request);
           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);
           }

           HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
           if (CorsUtils.isCorsRequest(request)) {
               CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
               CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
               CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
               executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
           }
           return executionChain;
       } 
}
复制代码

上一篇文章说过这里是个模板方法。定义了算法骨架:

  • 找handler
  • 包装

4.1 匹配

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        //获取请求的URI 的path
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);

        this.mappingRegistry.acquireReadLock();
        try {
            //根据url从多个缓存关系中选择HandlerMethod
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }
复制代码

lookupHandlerMethod

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<Match>();
        //根据url从关系缓存:urlLookup获取对应的匹配条件集
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        //如果没有找到匹配条件,从整个关系缓存mappingLookup中寻找
        if (matches.isEmpty()) {
            // No choice but to go through all mappings...
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
        //找到
        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;
                }
                //第二个为最优匹配
                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 + "}");
                }
            }
            //找到匹配后,在Attributes中加入一些相关信息。
            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            handleMatch(bestMatch.mapping, lookupPath, request);
            //返回最优匹配的handlerMehod
            return bestMatch.handlerMethod;
        }
        else {
            //没有找到。交给子类去处理没有找到的情况
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }
复制代码

4.2 包装

找到handler后,并不是直接返回handler . 为了 体现框架设计的开闭原则,SpringMVC把拦截与Handler 包装成一个HandlerExecutionChain .

HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
复制代码

5. 总结

因为篇幅的原因,很多东西看源码理解的更快,本文提供观看思路,加入个人的理解。

框架千千万万,我们不需要把每一个细节看到非常透,抓住主干,理解原理。我们已经成功了一步。

如果觉得文章有用, 求点赞:+1: 求关注:heart: 求分享:busts_in_silhouette:

如果本文任何错误,请批评指教,不胜感激 !

微信公众号: [ 源码行动 ] 加我认识认识呗

我把自己写文章的定位是个人理解日记。因为每当我写文章时,我会集中注意力在知识点的理解与阐述上,这样会加深,加强对知识点的理解。

新的一年,我享读读源码,不求技术飞速提高,但求心里舒服。

享读SpringMVC源码2-@RequestMapping注解源码
原文  https://juejin.im/post/5e23b8daf265da3e021f9361
正文到此结束
Loading...