美文网首页
Spring MVC 源码分析之 加载及查找 Controlle

Spring MVC 源码分析之 加载及查找 Controlle

作者: 瞎胡扯1 | 来源:发表于2020-11-30 07:07 被阅读0次

一、前言

上一篇文章介绍了SpringMVC的请求过程,其中在DispatcherServlet中的 doDispatch方法中,说到了根据 request 查找具体Handler的,这篇文章主要介绍 Handler的查找,即为怎么根据Request 请求URL查找到 Controller 。

二、查找Handler

2.1、回顾 doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

        try {   
            try {               
                // Determine handler for the current request.
                //根据 request 查找Handler
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

            }catch (Exception ex) {
                dispatchException = ex;
            }                       
        }catch (Exception ex) {

        }catch (Throwable err) {

        }finally {

        }
    }
image.gif

2.2、查看 getHandler方法

@Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping mapping : this.handlerMappings) {
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }
image.gif

根据源码可知,getHandler起始返回的是 HandlerExecutionChain,其实是在 handlerMappings中查找的,那handlerMappings又是从哪来的呢,接下来分析 handlerMappings的出处。

2.3、handlerMappings的前世今生

上篇文章中结束Dispatcher的初始化时介绍到了,在DispatcherServletonRefresh 方法中调用了 initHandlerMappings 方法,接下来看看initHandlerMappings方法的实现逻辑。

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;
        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            // 在SpringContext的容器中查找 HandlerMapping 接口的所有实例。如果查询到进行排序
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }catch (NoSuchBeanDefinitionException ex) {             
            }
        }
        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isTraceEnabled()) {
                logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                        "': using default strategies from DispatcherServlet.properties");
            }
        }
    }
image.gif

查看看 handlerMappings中的HandlerMapper,如下图所示,其中主要解析Controller是在 RequestMappingHandlerMapping中。

image image.gif

接下来看看 HandlerMapping 类的实现结构

image image.gif

接下来重点看HandlerMapping的实现类, RequestMappingHandlerMapping 类。

1、先查看下ReqeustMappingHandlerMapping类的继承关系图

image image.gif

根据继承关系图可知,RequestMappingHandlerMapping同时实现了 InitializingBeanApplicationContextAware两个接口,熟悉这两个接口的都知道,其中InitializingBean 的方法 afterPropertiesSet 是在类初始化完成且设置属性完成后调用,那么接下来看看 RequestMappingHandlerMappingafterPropertiesSet的实现。

2、

    public void afterPropertiesSet() {
        this.config = new RequestMappingInfo.BuilderConfiguration();
        this.config.setUrlPathHelper(getUrlPathHelper());
        this.config.setPathMatcher(getPathMatcher());
        this.config.setSuffixPatternMatch(useSuffixPatternMatch());
        this.config.setTrailingSlashMatch(useTrailingSlashMatch());
        this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
        this.config.setContentNegotiationManager(getContentNegotiationManager());
        //用调用了父类的 方法
        super.afterPropertiesSet();
    }
image.gif

此实现中除了设置一下参数配置,又调用了父类的afterPropertiesSet的实现,在看看父类的方法

@Override
    public void afterPropertiesSet() {

        initHandlerMethods();
    }

    /**
     * Scan beans in the ApplicationContext, detect and register handler methods.
     * @see #getCandidateBeanNames()
     * @see #processCandidateBean
     * @see #handlerMethodsInitialized
     */
    protected void initHandlerMethods() {

        //在Spring容器中获取所有的Bean,
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
               //解析Bean
                processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
image.gif

根据源码可知:

  • 直接调用了 initHandlerMethods方法
  • initHandlerMethods方法是获取了Spring容器中的所有beanName。
  • 然后调用了processCandidateBean方法。

3、查看 processCandidateBean 方法

protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            //获取beanNamed对应的类型
            beanType = obtainApplicationContext().getType(beanName);
        }
        catch (Throwable ex) {

        }
        //判断类型是否为空,和 此类型是否为 Handler
        if (beanType != null && isHandler(beanType)) {
            detectHandlerMethods(beanName);
        }
    }

//此方法是RequestMappingHandlerMapping类中实现的,判断了此类型是否添加了
// Controller 和 RequestMapping 注解。
@Override
protected boolean isHandler(Class<?> beanType) {
  return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
image.gif

有源码可知

  • 如果判断类型为Handler,即类添加了 Controller 和RequestMapping 注解。
  • 如果是 则调用 detectHandlerMethods 方法,此方法即为生成 RequestMappingInfo 的方法。此方法在父类

接下来看看 detectHandlerMethods 方法的实现

protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            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.isTraceEnabled()) {
                logger.trace(formatMappings(userType, methods));
            }
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }
image.gif

detectHandlerMethods 方法说明:

  1. 通过反射机制获取 handlerType的所有方法,并调用 getMappingForMethod() 方法,把方法解析为 RequestMappingInfo 类型对象。
  2. 解析完所有的方法后,在调用 registerHandlerMethod 方法注册到HandlerMapping中。具体注AbstractHandlerMethodMapping内部类MappingRegistry的 register 方法,再次方法中 把 handler 和 具体的方法再次封装为 HandlerMethod。然后进行注册。

至此所有的查找注册工作完成了。

三、补充说明

查看RequestMappingHandlerMapping类的创建。

1、通过 <annotation-driven/> 方式

此方式是在解析类 AnnotationDrivenBeanDefinitionParser 中的 parse 方法中实现的。如下所示:

image image.gif

2、SpringBoot方式

此种方式是在 类 WebMvcConfigurationSupport 中创建的,如下所示:

image image.gif

四、总结

  1. 在系统启动时,会在Spring容器中查找所有通过 Controller和RequestMapping注解的类,并解析器方法,把所有符合条件的方法都以RequestMappingInfo及HandlerMethod 注册到 RequestMappingHandlerMapping中。
  2. 在Servlet初始化时,会在Spring容器中查找所有的 HandlerMapping对象。
  3. 在请求是主要通过DispatcherServlet中的 doDispatch方法进行转发。
  4. 在doDispatch中调用 getHandler中,在此方法中循环所欲的HandlerMapping,找到符合条件的HandlerMapping进行处理。

相关文章

  • Spring MVC 源码分析之 加载及查找 Controlle

    一、前言 上一篇文章介绍了SpringMVC的请求过程,其中在DispatcherServlet中的 doDisp...

  • 2018-06-02

    spring源码分析(七) 目录五、源码分析--5.8 Spring MVC 框架设计原理----5.8.1 Sp...

  • 2018-08-22

    SpringMvc初始化流程源码解析及请求加载流程解析 及常见Mvc三剑客在spring-boot中的配置和加载原...

  • Spring Mvc源码分析

    Spring Mvc源码分析 mvc源码图解springmvc经过一系列的调用到DispatcherServlet...

  • 2018-05-26

    spring源码分析(六) 目录五、spring源码分析--5.7、Spring JDBC 设计原理及二次开发--...

  • 2018-05-19

    spring源码分析(五) 目录五、源码分析--5.6、Spring AOP 设计原理及具体实践----5.6.1...

  • spring加载配置

    源码分析基于spring 4.3.x 本文通过阅读源码,分析spring加载配置文件的过程 小栗子 bean类 s...

  • spring源码阅读,从springweb开始

    前言 这个系列是java spring mvc 源码阅读与分析的一个系列阅读源码分支为 spring初始化流程 ...

  • Spring 源码分析(二)之 Spring IOC 容器源码分

    Spring 源码分析(二)之 Spring IOC 容器源码分析 在之前介绍了Spring IOC 容器在项目中...

  • Spring MVC拦截器

    Spring MVC的静态概念 DispatcherServlet:浏览器用户请求分发到达合适的Controlle...

网友评论

      本文标题:Spring MVC 源码分析之 加载及查找 Controlle

      本文链接:https://www.haomeiwen.com/subject/tafxwktx.html