zoukankan      html  css  js  c++  java
  • Java框架-Spring MVC理解005-DispatcherServlet

    DispatcherServlet

      通过源码我们可以看到,onRefresh方法是DispatcherServlet的入口方法。onRefresh中简单地调用了initStrategies,在initStrategies中调用了9个初始化方法。

     

    // org.springframework.web.servlet.DispatcherServlet
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }
    
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

     

    我们会想:为什么不将initStrategies的具体实现直接写到onRefresh中呢?initStrategies方法不是多余的吗?

      其实这主要是分层的原因,onRefresh是用来刷新容器的,initStrategies用来初始化一些策略组件。如果把initStrategies里面的代码直接写到onRefresh里面,对于程序的运行也没有影响,不过这样一来,如果在onRefresh中想再添加别的功能,就会没有将其单独写一个方法出来逻辑清晰,不过这并不是最重要的,更重要的是,如果在别的地方也需要调用initStrategies方法(如需要修改一些策略后进行热部署),但initStrategies没独立出来,就只能调用onRefresh,那样在onRefresh增加了新功能的时候就麻烦了。另外单独将initStrategies写出来还可以被子类覆盖,使用新的模式进行初始化。
      initStrategies的具体内容非常简单,就是初始化的9个组件,下面以LocaleResolver为例来分析具体的初始化方式://初始化区域解析器

    // org.springframework.web.servlet.DispatcherServlet
    private void initLocaleResolver(ApplicationContext context) {
        try {
            // 在context中获取
            this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
            }
        }catch (NoSuchBeanDefinitionException ex) {
            // 使用默认策略
            this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
                "': using default [" + this.localeResolver + "]");
            }
        }
    }

    初始化方式分两步:首先通过context.getBean在容器里面按注册时的名称或类型(这里指“localeResolver”名称或者LocaleResolver.class类型)进行查找,所以在Spring MVC的配置文件中只需要配置相应类型的组件,容器就可以自动找到。如果找不到就调用getDefaultStrategy按照类型获取默认的组件。需要注意的是,这里的context指的是Frame-workServlet中创建的WebApplicationContext,而不是ServletContext。下面介绍getDefault-Strategy是怎样获取默认组件的。

    // org.springframework.web.servlet.DispatcherServlet
    protected<T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
        List<T> strategies = getDefaultStrategies(context, strategyInterface);
        if (strategies.size() != 1) {
            throw new BeanInitializationException(
                "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
        }
        return strategies.get(0);
    }
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        String key = strategyInterface.getName();
        //从defaultStrategies获取所需策略的类型
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            //如果有多个默认值,以逗号分割为数组
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List<T> strategies = new ArrayList<T>(classNames.length);
            //按获取到的类型初始化策略
            for (String className : classNames) {
                try {
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    Object strategy = createDefaultStrategy(context, clazz);
                    strategies.add((T) strategy);
                }catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException(
                        "Could not find DispatcherServlet's default strategy class [" + className +
                            "] for interface [" + key + "]", ex);
                }catch (LinkageError err) {
                    throw new BeanInitializationException(
                        "Error loading DispatcherServlet's default strategy class [" + className +
                            "] for interface [" + key + "]: problem with class file or dependent class", err);
                }
            }
            return strategies;
        }else {
            return new LinkedList<T>();
        }
    }

      可以看到getDefaultStrategy中调用了getDefaultStrategies,后者返回的是List,这是因为HandlerMapping等组件可以有多个,所以定义了getDefaultStrategies方法,getDefaultStrategy直接调用了getDefaultStrategies方法,并返回返回值的第一个结果。getDefaultStrategies中实际执行创建的方法是ClassUtils.forName,它需要的参数是class-Name,所以最重要的是看className怎么来的,找到了className的来源,也就可以理解默认初始化的方式。className来自classNames,classNames又来自value,而value来自default-Strategies.getProperty(key)。所以关键点就在defaultStrategies中,defaultStrategies是一个静态属性,在static块中进行初始化的。

    // org.springframework.web.servlet.DispatcherServlet
    private static final Properties defaultStrategies;
    
    static {
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }catch (IOException ex) { throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
        }
    }
    # org.springframework.web.DispatcherServlet.properties
    # Default implementation classes for DispatcherServlet's strategy interfaces.
    # Used as fallback when no matching beans are found in the DispatcherServlet context.
    # Not meant to be customized by application developers.//这些配置是固定的,开发者不可以定制
    
    org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
    
    org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
    
    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
    
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
    
    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
        org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
        org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
    
    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,
        org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,
        org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
    
    org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
    
    org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
    
    org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

      我们看到defaultStrategies是DispatcherServlet类所在包下的DEFAULT_STRATEGIES_PATH文件里定义的属性,DEFAULT_STRATEGIES_PATH的值是DispatcherServlet.properties。所以defaultStrategies里面存放的是org.springframework.web.DispatcherServlet.properties里面定义的键值对,代码如上。

      可以看到,这里确实定义了不同组件的类型,一共定义了8个组件,处理上传组件Multi-partResolver是没有默认配置的,这也很容易理解,并不是每个应用都需要上传功能,即使需要上传也不一定就要使用MultipartResolver,所以MultipartResolver不需要默认配置。另外HandlerMapping、HandlerAdapter和HandlerExceptionResolver都配置了多个,其实View-Resolver也可以有多个,只是默认的配置只有一个。

      这里需要注意两个问题:首先默认配置并不是最优配置,也不是spring的推荐配置,只是在没有配置的时候可以有个默认值,不至于空着。里面的有些默认配置甚至已经被标注为@Deprecated,表示已弃用,如DefaultAnnotationHandlerMapping、Annotation-MethodHandlerSolver不需要默认配置。另外HandlerMapping、andlerAdapter和HandlerExceptionResolver都配置了多个,其实View-Resolver也可以有多个,只是默认的配置只有一个。

    DispatcherServlet的创建过程主要是对9大组件进行初始化,具体每个组件的作用后面具体讲解。

     

  • 相关阅读:
    《JAVA设计模式》之责任链模式(Chain of Responsibility)
    《JAVA设计模式》之迭代器模式(Iterator)
    《JAVA设计模式》之观察者模式(Observer)
    【python】effective python读书笔记
    【python】contextlib的使用
    【linux】乱七八糟命令
    【深度学习】使用Ray
    【深度学习】超参调节方式:Bayesian Opt
    【linux】scp不需要输入密码的方法
    【深度学习】论文TCN
  • 原文地址:https://www.cnblogs.com/yulibo/p/9069449.html
Copyright © 2011-2022 走看看