springmvc源码学习

SPI机制

传统的springmvc项目,需要我们指定web.xml等配置文件,但是,在spring的官网,官方推荐的并不是xml格式的,而是

public class MyWebApplicationInitializer implements WebApplicationInitializer {
       @Override
       public void onStartup(ServletContext servletContext) throws ServletException {
           // Load Spring web application configuration
           //初始化springweb容器
           AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
           ac.register(SpringbootAppConfig.class);
           ac.refresh();
           // Create and register the DispatcherServlet
           //注册DispatcherServlet
           DispatcherServlet servlet = new DispatcherServlet(ac);
           ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
           registration.setLoadOnStartup(1);
           registration.addMapping("*.do");
       }
   }

这七行代码,和我们传统的springmvc项目中加web.xml是一样的效果,这个方法是完成servlet容器的初始化,那为什么spring自己写的类,Tomcat在启动的时候,会调用呢?

在Servlet3.0新加的SPI机制是这样要求的:

  1. 如果一个项目(Tomcat)在启动的时候,需求调用外部系统(spring)的部分方法,完成初始化,那么,在外部系统中,在项目的resource文件下 META-INF/services/javax.servlet.ServletContainerInitializer 声明这么一个文件(路径是不允许变的);文件中声明的类如果实现了ServletContainerInitializer接口;那么,Tomcat在启动的时候 必须要调用文件中声明的类的onStart方法;前提是:必须是一个web项目,否则Tomcat不会调用
  2. 但是spring官方文档指明开发springmvc,需要实现的是 WebApplicationInitializer 接口,那么WebApplicationInitializer和ServletContainerInitializer之间有什么关系?servlet3.0还有一个规范,如果在 实现了ServletContainerInitializer接口的类 上加上 @HandlesTypes(WebApplicationInitializer.class) 注解,那么onStart方法需要调用注解中接口的所有实现类对应的onStart方法

 

springmvc应用

image.png

 

这张原理图是在网上随便找了一张

运行原理

  1. 前台发送请求,请求会首先通过DispatcherServlet,前端控制器
  2. 前端控制器收到请求,会调用HandlerMapping(处理器映射器)来匹配有没有相对于的handlerMapping,如果有匹配的,会包装成handlerExecutionChain
  3. 接着dispatcherServlet会调用handlerAdapter,通过handlerAdapter来执行真正的业务逻辑代码,也就是所谓的controller中的方法
  4. 调用完成之后,返回modelAndView,前端控制器会调用师徒解析器ViewResolver对modelAndView进行解析
  5. 视图解析器会解析出对应的view,前端控制器根据view并进行视图的渲染(数据填充),然后返回给前端调用者

 

springmvc核心组件

  1. 前端控制器  DispatcherServlet
  2. 处理器映射器 HandlerMapping
  3. 处理器适配器 HandlerAdapter
  4. 视图解析器   viewResolver
  5. ModelAndView

 

controller的三种配置方式

  1. @Controller注解
  2. 实现Controller接口,这种方式,需要在类名增加@Component("/映射地址")
  3. 实现HttpRequestHandler接口,在类上加@Component("/映射地址")

后面两种原理是一样的,下面会说到;spring默认的handlerMapping有两种:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping;对于@Controller注解的controller,都是由前者来处理的,实现controller接口或者httpRequestHandler接口,是由后者来处理的

 

spring自带的handlerMapping

  1. RequestMappingHanderlMapping:@Controller是由该handlerMapping处理的
  2. BeanNameUrlHandlerMapping:后面两种实现方式都是=该handlerMapping处理的

spring自带的handlerAdapter

  1. RequestMappingHandlerAdapter:@Controller是由该handlerAdapter处理的
  2. HttpRequestHandlerAdapter:后面两种实现方式都是该handlerAdapter处理的
  3. SimpleControllerHadnlerAdapter

 

springmvc源码

springmvc的源码,我们暂时分为两部分,一是启动初始化,二是调用过程

简单而言,在请求接口的时候,需要根据与URL地址找到对应的处理方法,这个映射关系是在初始化的时候,存入到了一个map集合中

启动初始化

在spring初始化的时候,我们需要关注两个bean的初始化:

RequestMappingHandlerMapping和BeanNameUrlHandlerMapping

RequestMappingHandlerMapping是在org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration这里,在springboot自动注入的时候,给注入到beanDefinitionMap中了

 

BeanNameHandlerMapping 是在org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport中注入的

 

RequestMappingHandlerMapping

image.png

由于该mapping实现了InitializingBean,所以,在实例化该bean的时候,会调用父类的afterProperties()方法,在父类的方法中,又会调用到子类的org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods这个方法,来初始化URL和method的对应关系;具体的调用逻辑在截图中,这几个方法中,只是有一些简单的校验,所以就跳过

image.png

 

/**
     * @param handler
     * 在这里其实是根据bean,获取到bean中所有添加了@RequestMapping注解的method,
   * 然后把method和url进行映射,并把映射关系存到map中
     */
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            //userType是当前的类名
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            //根据类名获取到所有的方法
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) 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);
            }
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
        //这里是来注册映射关系的
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }

 

在注册映射关系的时候,其实就是将url和对应的方法,存入到了一个map集合中,这里的registerHandlerMethod内部调用到了org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register,这这个方法中,将映射关系存入到了urlLookup这个map中

 

BeanNameUrlHandlerMapping

image.png

 

由于beanNameUrlHandlerMapping间接的实现了applicationContextAware,所以,在调用org.springframework.context.support.ApplicationContextAwareProcessor#postProcessBeforeInitialization的时候,会调用org.springframework.context.support.ApplicationObjectSupport#setApplicationContext;在底层,会调用到org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#detectHandlers

image.png

在该方法中,

protected void detectHandlers() throws BeansException {
        ApplicationContext applicationContext = obtainApplicationContext();
        String[] beanNames = (this.detectHandlersInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
                applicationContext.getBeanNamesForType(Object.class));

        // Take any bean name that we can determine URLs for.遍历beanName
        for (String beanName : beanNames) {
        //判断beanName是否是以 / 开头的
            String[] urls = determineUrlsForHandler(beanName);
            if (!ObjectUtils.isEmpty(urls)) {
                // URL paths found: Let's consider it a handler.
                registerHandler(urls, beanName);
            }
        }

        if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
            logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
        }
    }
  
  org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#determineUrlsForHandler
  
  @Override
    protected String[] determineUrlsForHandler(String beanName) {
        List<String> urls = new ArrayList<>();
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        String[] aliases = obtainApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }

推断出来beanName是以 / 开头的话,就会将当前url和对应的beanName添加到 一个map集合中:org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#handlerMap

 

总结来说

RequestMappingHandlerMapping(@Controller注解)实现了InitializingBean接口
 1.在初始化这个bean的时候,会调用afterPropertiesSet(initialization初始化方法)方法,
 2.在这个方法中,会获取到当前单实例池中所有的Object类型的bean,过滤掉以scopedTarget.开头的bean
 3.获取到bean中定义的方法(得到method名称和映射地址),遍历methods,调用org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register方法,在这里,会把当前

BeanNameUrlHandlerMapping(Controller接口)是实现了ApplicationContextAware接口的类(中间有多重继承实现)
 1.在初始化这个bean的时候,会调用setApplicationContext()方法
 2.最终会调用到org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#detectHandlers方法,
 3.在这个方法中,会获取到单实例池中所有的Object.class类型的beanName,获取到beanName之后,有一个判断,判断beanName是否是以 / 开头的;如果是,就调用registerHandler(urls, beanName);
 4.然后会把当前映射路径和对应的Controller添加到handlerMap中

 

调用

在第一次调用controller的时候,会对dispatcherServlet进行初始化

protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

 

在request请求过来的时候,会进入到org.springframework.web.servlet.DispatcherServlet#doDispatch,这里是springmvc处理请求的核心方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
        }
    }

在getHandler()方法中,会根据当前请求的url地址来进行映射方法的查找,如果找到,返回对应的handlerMapping,然后包装成HandlerExecutionChain返回;

在getHandlerAdapter()方法中,根据当前handlerMapping对应的handlerAdapter,找到对应的handlerAdapter,

在mv = ha.handle(processedRequest, response, mappedHandler.getHandler());中会根据方法的入参和实际请求中的参数名对参数进行赋值,然后调用对应的controller方法

 

 

 

遗留问题

1.url中的参数如何解析? XXX/{id}

2.controller入参,dispatcherServlet是如何判断处理的

posted @ 2019-12-15 16:58  小小少年-  阅读(271)  评论(0编辑  收藏  举报