zoukankan      html  css  js  c++  java
  • 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是如何判断处理的

  • 相关阅读:
    jQuery选择器ID、CLASS、标签获取对象值、属性、设置css样式
    shell脚本,awk取奇数行与偶数行方法。
    shell脚本,awk取中间列的方法。
    shell脚本,每5个字符之间插入"|",行末不插入“|”。
    shell脚本,tee小工具的用法。
    shell脚本,逻辑结构题练习。
    shell脚本,实现奇数行等于偶数行。
    shell脚本,编程题练习。
    shell脚本,用awk实现替换文件里面的内容。
    shell脚本,awk替换{}里面的内容
  • 原文地址:https://www.cnblogs.com/mpyn/p/12044674.html
Copyright © 2011-2022 走看看