当前位置:   article > 正文

Spring源码学习(六):Spring MVC的初始化过程_spring mvc初始化过程

spring mvc初始化过程

目录

1.ContextLoaderListener

1.1 创建WebApplicationContext

1.2 设置和刷新WebApplicationContext

2.DispatcherServlet

2.1 init方法

2.2 initServletBean方法

2.3 OnRefresh方法

3.九大组件的注册

3.1 文件上传解析器MultipartResolver

3.2 本地化解析器LocaleResolver

3.3 主题解析器ThemeResolver

3.4 处理器映射器HandlerMapping

3.4.1 initHandlerMappings

3.4.2 HandlerMethod初始化:afterPropertiesSet方法

3.4.3 拦截器初始化:initApplicationContext

3.5 处理器适配器HandlerAdapter

3.5.1 @ControllerAdvice 与 initControllerAdviceCache

3.5.2 参数解析器 HandlerMethodArgumentResolver

3.5.3 @InitBinder的初始化

3.5.4 返回值处理器 HandlerMethodReturnValueHandler

3.6 处理器异常解析器HandlerExceptionResolver

3.7 视图名翻译器RequestToViewNameTranslator

3.8 视图解析器ViewResolver

3.9 FlashMap管理器 FlashMapManager

4.CORS的初始化

4.1 CORS的使用

4.2 initCorsConfiguration方法


Spring最常用的场景就是Web后台开发,这就要使用到Spring MVC相关包:spring-web、spring-webmvc等。一个简单的Spring MVC项目如下:

首先是web.xml,它配置了首页、servlet、servlet-mapping、filter、listener等,Spring MVC通过加载该文件,获取配置的Servlet,来拦截URL。下面的配置中,指定了Spring配置文件的位置,设置了DispatcherServlet及启动级别,它将会在启动后尝试从WEB-INF下面加载 servletName-servlet.xml(斜粗体部分为servlet-name配置的内容),listener部分配置了上下文载入器,用来载入其它上下文配置文件,然后配置了servlet映射,“/”表示它拦截所有类型的URL:

  1. <web-app version="2.5"
  2. xmlns="http://java.sun.com/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  5. http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  6. <listener>
  7. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  8. </listener>
  9. <context-param>
  10. <param-name>contextConfigLocation</param-name>
  11. <param-value>/WEB-INF/applicationContext.xml</param-value>
  12. </context-param>
  13. <servlet>
  14. <servlet-name>hello</servlet-name>
  15. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  16. <load-on-startup>2</load-on-startup>
  17. </servlet>
  18. <servlet-mapping>
  19. <servlet-name>hello</servlet-name>
  20. <url-pattern>/</url-pattern>
  21. </servlet-mapping>
  22. </web-app>

Spring也支持编程式配置DispatcherServlet,只要实现WebApplicationInitializer的onStartup方法,在里面创建DispatcherServlet实例并注册到ServletContext即可(例子来源于官方文档):

  1. public class MyWebApplicationInitializer implements WebApplicationInitializer {
  2. @Override
  3. public void onStartup(ServletContext servletCxt) {
  4. // Load Spring web application configuration
  5. AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
  6. ac.register(AppConfig.class);
  7. ac.refresh();
  8. // Create and register the DispatcherServlet
  9. DispatcherServlet servlet = new DispatcherServlet(ac);
  10. ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
  11. registration.setLoadOnStartup(1);
  12. registration.addMapping("/app/*");
  13. }

然后是applicationContext.xml,它就是一个普通的Spring配置文件,一般会在这里配置ViewResolver,下面是一个JSP配置:

  1. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
  2. p:viewClass="org.springframework.web.servlet.view.JstlView"
  3. p:prefix="/WEB-INF/jsp/"
  4. p:suffix=".jsp"/>

hello-context.xml用来配置URL处理器的映射规则,也可以配置如下:

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:p="http://www.springframework.org/schema/p"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context-4.0.xsd">
  9. <context:component-scan base-package="com.test.controller"/>
  10. </beans>

上述配置表示自动扫描com.test.controller包下,由stereotype类型注解标记的类,有四种:@Controller、@Component、@Repository、@Service。

然后我们就可以编写jsp文件和Controller,启动程序后就可以输入URL看到结果。

在上述配置中,有两个关键类:ContextLoaderListener和DispatcherServlet。

1.ContextLoaderListener

它自身的代码很简单,实现了来自ServletContextLoader接口的contextInitialized、contextDestroyed两个方法,不过主要实现在父类ContextLoader中。

  1. public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
  2. ContextLoaderListener() {
  3. }
  4. public ContextLoaderListener(WebApplicationContext context) {
  5. super(context);
  6. }
  7. @Override
  8. public void contextInitialized(ServletContextEvent event) {
  9. initWebApplicationContext(event.getServletContext());
  10. }
  11. @Override
  12. public void contextDestroyed(ServletContextEvent event) {
  13. closeWebApplicationContext(event.getServletContext());
  14. ContextCleanupListener.cleanupAttributes(event.getServletContext());
  15. }
  16. }

实际上,Spring正是依靠ServletContextListener,才能被Tomcat容器加载的:

  1. public boolean listenerStart() {
  2. ...
  3. for (int i = 0; i < instances.length; i++) {
  4. if (!(instances[i] instanceof ServletContextListener))
  5. continue;
  6. ServletContextListener listener =
  7. (ServletContextListener) instances[i];
  8. try {
  9. fireContainerEvent("beforeContextInitialized", listener);
  10. if (noPluggabilityListeners.contains(listener)) {
  11. listener.contextInitialized(tldEvent);
  12. } else {
  13. listener.contextInitialized(event);
  14. }
  15. fireContainerEvent("afterContextInitialized", listener);
  16. } catch (Throwable t) {
  17. ...
  18. }
  19. }
  20. return ok;
  21. }

可见Tomcat启动Spring容器就是靠contextInitialized调用initWebApplicationContext方法来实现的,从名字不难看出,WebApplicationContext就是在ApplicationContext的基础上增加了一些Web操作及属性。下面来看看这个方法的源码。

首先判断了一次web.xml中是否重复定义了ContextLoader,从下面的代码可以看出,每当创建WebApplicationContext实例时,就会记录在ServletContext中以便全局调用,key就是ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,所以可以getAttribute来检查是否已经创建过WebApplicationContext:

  1. if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
  2. throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!");
  3. }

1.1 创建WebApplicationContext

接下来,假如当前ContextLoader还没有管理任何WebApplicationContext实例,就创建一个,创建方法为createWebApplicationContext。最后的instantiateClass在阅读Spring源码时已经见过很多次了,作用是将Class对象实例化,因此,该方法的核心是determineContextClass方法:

  1. protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
  2. Class<?> contextClass = determineContextClass(sc);
  3. if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
  4. throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
  5. }
  6. return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  7. }

determineContextClass基本逻辑如下(去除了try-catch),ClassUtil.forName很显然就是反射创建类

  1. protected Class<?> determineContextClass(ServletContext servletContext) {
  2. String contextClassName = servletContext.getInitParameter("contextClass");
  3. if (contextClassName != null) {
  4. return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
  5. }
  6. else {
  7. contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
  8. return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
  9. }
  10. }

如果是配合Tomcat使用,一般传入的是ApplicationContext(这个ApplicationContext是ServletContext的子类,而不是Spring容器),它的getInitParameter实现如下:

  1. public String getInitParameter(final String name) {
  2. if ("org.apache.jasper.XML_VALIDATE_TLD".equals(name) &&
  3. context.getTldValidation()) {
  4. return "true";
  5. }
  6. if ("org.apache.jasper.XML_BLOCK_EXTERNAL".equals(name)) {
  7. if (!context.getXmlBlockExternal()) {
  8. return "false";
  9. }
  10. }
  11. return parameters.get(name);
  12. }

这里将常量替换为对应的字面值,可以看到,最终是从一个Map中获取值。如果我们配置了自定义的WebApplicationContext实现,则加载自定义的,否则通过WebApplicationContext的全限定名查找需要加载的类名,并进行加载。在ContextLoader的静态块中,可以看到如下语句:

  1. ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
  2. defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);

也就是说,SpringMVC默认从classpath:org.springframework/web/context/ContextLoader.properties文件加载容器的类名,查询一下,果然如此:

  1. # Default WebApplicationContext implementation class for ContextLoader.
  2. # Used as fallback when no explicit context implementation has been specified as context-param.
  3. # Not meant to be customized by application developers.
  4. org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

可见Spring Web容器的实现类为XmlWebApplicationContext。

1.2 设置和刷新WebApplicationContext

容器创建完毕后,根据经验来看,还需要一些设置和刷新,源码中通过configureAndRefreshWebApplicationContext方法实现。

该源码可分为设置和刷新两部分。首先看设置,代码检查了是否配置了contextId和contextConfigLocation,是则赋给新创建的容器,并且通过setServletContext将Web容器和Servlet上下文关联起来。然后获取Environment进行PropertySource的初始化,这一步中如果没有设置环境,会创建一个StandardServletEnvironment实例,获取servletContextInitParams和servletConfigInitParams,然后进行属性替换。

  1. if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
  2. String idParam = sc.getInitParameter("contextId");
  3. if (idParam != null) {
  4. wac.setId(idParam);
  5. }
  6. else {
  7. wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
  8. ObjectUtils.getDisplayString(sc.getContextPath()));
  9. }
  10. }
  11. wac.setServletContext(sc);
  12. String configLocationParam = sc.getInitParameter("contextConfigLocation");
  13. if (configLocationParam != null) {
  14. wac.setConfigLocation(configLocationParam);
  15. }
  16. ConfigurableEnvironment env = wac.getEnvironment();
  17. if (env instanceof ConfigurableWebEnvironment) {
  18. ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
  19. }

接下来调用customizeContext方法对Web容器进行初始化,它会寻找配置的contextInitializerClasses或globalInitializerClasses,使用它们对Web容器进行初始化。

  1. protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
  2. List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc);
  3. for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
  4. Class<?> initializerContextClass =
  5. GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
  6. if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
  7. throw new ApplicationContextException(String.format(
  8. "Could not apply context initializer [%s] since its generic parameter [%s] " + "is not assignable from the type of application context used by this " + "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),wac.getClass().getName()));
  9. }
  10. this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
  11. }
  12. AnnotationAwareOrderComparator.sort(this.contextInitializers);
  13. for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
  14. initializer.initialize(wac);
  15. }
  16. }

接着调用refresh方法对容器进行刷新。使用过Spring一定不会对它陌生,该方法位于AbstractApplicationContext,绝大部分基本逻辑和Spring是一致的,但是在XmlWebApplicationContext中,对loadBeanDefinitions和postProcessBeanFactory进行了实现,因此又有些区别,首先是loadBeanDefinitions:

  1. protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
  2. String[] configLocations = getConfigLocations();
  3. if (configLocations != null) {
  4. for (String configLocation : configLocations) {
  5. reader.loadBeanDefinitions(configLocation);
  6. }
  7. }
  8. }
  9. protected String[] getDefaultConfigLocations() {
  10. return this.getNamespace() != null ? new String[]{"/WEB-INF/" + this.getNamespace() + ".xml"} : new String[]{"/WEB-INF/applicationContext.xml"};
  11. }

这里读取了WEB-INF下的配置文件,要么由Namespace决定,要么默认读取applicationContext.xml。提到Namespace就不难想到Spring Schema,即通过META-INF下的spring.handlers文件配置命名空间解析器。

Spring MVC的默认命名空间解析器为MvcNamespaceHandler,它注册了一系列解析器,这些解析器方法又在parse方法中注册了一系列组件,例如常用的<mvc:annotation-driven/>配置,就会注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter等:

  1. context.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
  2. context.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
  3. context.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));
  4. context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
  5. context.registerComponent(new
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/1015713
推荐阅读
相关标签
  

闽ICP备14008679号