spring mvc(一):springmvc demo 与 @EnableWebMvc 注解
注:本系列源码分析基于spring 5.2.2.RELEASE,本文的分析基于 annotation 注解方式,gitee仓库链接:gitee.com/funcy/sprin….
1. demo 准备
为了更好地分析springmvc相关源码,我们需要先准备一个springmvc的demo,这里的demo还是放在spring-learn模块。
1. 引入 tomcat 包
在 tomcat 8 之后,tomcat提供了独立的运行包,需要时直接引入相关依赖就可以了,对应的gradle依赖如下:
optional("org.apache.tomcat.embed:tomcat-embed-core")
在spring项目的build.gradle中,已经引入了tomcat-embed-core-9.0.29.jar,因此在spring-learn模块中引入时不用再指定版本。
2. 准备配置类
package org.springframework.learn.mvc.demo01;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Component
@ComponentScan("org.springframework.learn.mvc.demo01")
@EnableWebMvc
public class MvcConfig {
}
配置类为MvcConfig,该类指定了项目的包扫描路径,以及通过@EnableWebMvc开启mvc功能。
3. 实现WebApplicationInitializer
package org.springframework.learn.mvc.demo01;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
public class MyWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(MvcConfig.class);
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/*");
    }
}
spring提供了一个接口——WebApplicationInitializer,实现该接口时,我们在onStartup(...)方法中创建spring的applicationContext,然后往servelet中注册DispatcherServlet。
4. 准备controller
package org.springframework.learn.mvc.demo01;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/hello")
    public String hello() {
         System.out.println("hello!!!");
         return "hello world!";
    }
}
这里准备了一个简单的controller,返回一个字符串"hello world".
5. 主类
接下来就是主类了:
package org.springframework.learn.mvc.demo01;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
public class MvcDemo01Main {
    public static void main(String[] args) throws Exception {
        Tomcat tomcat = new Tomcat();
        Connector connector = new Connector();
        connector.setPort(8080);
        connector.setURIEncoding("UTF-8");
        tomcat.getService().addConnector(connector);
        Context context = tomcat.addContext("", System.getProperty("java.io.tmpdir"));
        LifecycleListener lifecycleListener = (LifecycleListener) 
                Class.forName(tomcat.getHost().getConfigClass())
                .getDeclaredConstructor().newInstance();
        context.addLifecycleListener(lifecycleListener);
        tomcat.start();
        tomcat.getServer().await();
    }
}
在main方法中,主要处理tomcat启动逻辑。
运行,结果如下:
控制台:

页面返回:

可以看到,一个简单的springmvc项目就搭建完成了。
2. servlet 3.0 规范介绍
回忆下古老的springmvc项目,一般有这几个xml配置文件:
- web.xml;servlet 配置文件,配置web启动时的操作,以及- servlet/- listener/- filter;
- spring.xml:spring容器的配置文件,主要用来配置spring bean.
- spring-mvc.xml:springmvc配置文件,用来配置mvc相关的 bean,如文件上传相关的bean,视图解析bean,controller包路径等。
项目在启动时,会先加载web.xml,在web.xml中加载spring相关配置,启动spring容器。
在上面的demo中,我们发现并 没有这些配置,甚至连web.xml文件都没有!那么,上面的web项目是怎么启动的呢?
在servlet在3.0之后,提供了一个spi规范,spring对其实现如下:
- 在spring-web模块的/src/main/resources/META-INF/services/文件夹下,创建文件javax.servlet.ServletContainerInitializer,内容如下
org.springframework.web.SpringServletContainerInitializer

- org.springframework.web.SpringServletContainerInitializer实现了servlet规范:
// @HandlesTypes 注解来自于servlet规范,表示 webAppInitializerClass 为 WebApplicationInitializer.class
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    /*
     * 重写 ServletContainerInitializer 的 onStartup 方法
     * 在这个方法里,主要是实例化 spring 提供的 WebApplicationInitializer.class,然后执行其 onStartup 方法
     *
     * Set<Class<?>> webAppInitializerClasses 中的类型为 WebApplicationInitializer.class,
     * 这个类型由 @HandlesTypes 注解指定
     */
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList<>();
        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                       // 使用反射实例化 WebApplicationInitializer 的实现类,添加到 initializers 中
                        initializers.add((WebApplicationInitializer)
                                ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    catch (Throwable ex) {
                        ...
                    }
                }
            }
        }
        servletContext.log(initializers.size() + " ...");
        // 排序,实现了Orderd接口,标注 @Order 注解,或实现了 PriorityOrderd 接口
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
           // 调用 WebApplicationInitializer 实现类的onStartup方法
           initializer.onStartup(servletContext);
        }
    }
}
- WebApplicationInitializer的实现 我们来看看demo中对- WebApplicationInitializer的实现:
package org.springframework.learn.mvc.demo01;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
public class MyWebApplicationInitializer implements WebApplicationInitializer {
    /*
     * 在这里启动 spring 项目
     */
    @Override
    public void onStartup(ServletContext servletContext) {
        // 创建 spring 的 ApplicationContext
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(MvcConfig.class);
        // 添加 DispatcherServlet 到 servlet 中
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/*");
    }
}
最终执行过程如下:

由此,spring 容器就启动了。
3. @EnableWebMvc 作用
在demo中,我们通过@EnableWebMvc来启动mvc功能,那么这个注解做了什么呢?我们进入EnableWebMvc类:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
可以看到,这个注解通过@Import注解引入了DelegatingWebMvcConfiguration.class,我们再来看看DelegatingWebMvcConfiguration:
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    ...
}
这个类标有@Configuration注解,表明这是个配置类,这个类继承的是WebMvcConfigurationSupport,从名字来看,WebMvcConfigurationSupport 为 "mvc 配置支持",这表明这个类是用来置处理mvc相关的配置的。
为了更好地分析,这里先介绍几个类:
- 
DelegatingWebMvcConfiguration:由@EnableWebMvc引入的类,是WebMvcConfigurationSupport的子类,重写了WebMvcConfigurationSupport提供的配置方法:/* * @Configuration:表明这是个配置类 * extends WebMvcConfigurationSupport:继承了WebMvcConfigurationSupport类 */ @Configuration(proxyBeanMethods = false) public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { // WebMvcConfigurerComposite 是 WebMvcConfigurer 的组合,下面会提到 private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); /** * 设置configurers * 添加了@Autowired注解,表示将spring容器中的所有WebMvcConfigurer bean 作为 * 参数configurers的值 ,然后调用该方法 */ @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } } /** * 配置PathMatch */ @Override protected void configurePathMatch(PathMatchConfigurer configurer) { // 调用 WebMvcConfigurerComposite 的方法进行配置 this.configurers.configurePathMatch(configurer); } // 其他配置方法也是调用 WebMvcConfigurerComposite 对应的方法进行配置的 ... }
- 
WebMvcConfigurerComposite:WebMvcConfigurer的组合:/** * 实现了 WebMvcConfigurer */ class WebMvcConfigurerComposite implements WebMvcConfigurer { // delegates属性为 WebMvcConfigurer 的集合 private final List<WebMvcConfigurer> delegates = new ArrayList<>(); /* * 被DelegatingWebMvcConfiguration#setConfigurers调用 * 最终是把传入的configurers添加到delegates(也就是WebMvcConfigurer集合)中 */ public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.delegates.addAll(configurers); } } /** * 配置时,会遍历delegates(也就是WebMvcConfigurer集合),将传入的配置设置到 * 集合中的每一个WebMvcConfigurer */ @Override public void configurePathMatch(PathMatchConfigurer configurer) { for (WebMvcConfigurer delegate : this.delegates) { delegate.configurePathMatch(configurer); } } // 其他配置类似,省略 ... }
- 
WebMvcConfigurer:springmvc的配置接口,提供了非常多的配置public interface WebMvcConfigurer { default void configurePathMatch(PathMatchConfigurer configurer) { } default void configureContentNegotiation(ContentNegotiationConfigurer configurer) { } default void configureAsyncSupport(AsyncSupportConfigurer configurer) { } ... }
- 
WebMvcConfigurationSupport:springmvc的配置支持类/** * 实现了两个aware接口 */ public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware { //================= 来自 XxxAware 接口的方法 ================= @Override public void setApplicationContext(@Nullable ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Override public void setServletContext(@Nullable ServletContext servletContext) { this.servletContext = servletContext; } //================= @Bean 方法,向spring中添加 bean ================= @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping(...) { RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping(); mapping.setOrder(0); // getInterceptors(...) 获取 interceptors,往下看 mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider)); mapping.setContentNegotiationManager(contentNegotiationManager); // getCorsConfigurations(...) 获取Cors配置,往下看 mapping.setCorsConfigurations(getCorsConfigurations()); // getPathMatchConfigurer(...) 获取PathMatch配置,往下看 PathMatchConfigurer configurer = getPathMatchConfigurer(); ... return mapping; } ... //================= get xxx 配置方法,添加spring提供的默认配置,添加自定义配置 ======= // 获取 interceptors protected final Object[] getInterceptors( FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { if (this.interceptors == null) { InterceptorRegistry registry = new InterceptorRegistry(); // 调用配置方法,添加 interceptor,往下看 addInterceptors(registry); registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService)); registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider)); this.interceptors = registry.getInterceptors(); } return this.interceptors.toArray(); } // 获取Cors配置 protected final Map<String, CorsConfiguration> getCorsConfigurations() { if (this.corsConfigurations == null) { CorsRegistry registry = new CorsRegistry(); // 调用配置方法,添加 CorsMapping,往下看 addCorsMappings(registry); this.corsConfigurations = registry.getCorsConfigurations(); } return this.corsConfigurations; } // 获取PathMatch配置 protected PathMatchConfigurer getPathMatchConfigurer() { if (this.pathMatchConfigurer == null) { this.pathMatchConfigurer = new PathMatchConfigurer(); configurePathMatch(this.pathMatchConfigurer); } return this.pathMatchConfigurer; } ... //================= 配置方法,留待子类实现 ================= // 添加自定义 Interceptor,待子类实现 protected void addInterceptors(InterceptorRegistry registry) { } // 添加自定义 CorsMapping,待子类实现 protected void addCorsMappings(CorsRegistry registry) { } // 配置自定义 PathMatch protected void configurePathMatch(PathMatchConfigurer configurer) { } ... }可以看到,这个类的方法分为四类: - 来自XxxAware的方法:XxxAware接口由spring提供,bean初始化完成时处理回调;
- 有@Bean注解的方法:往spring中添加bean,生成bean时会调用getXxx方法;
- getXxx方法:获取配置方法,在该方法中,会添加spring提供的默认配置,以及调用- addXxx/configureXxx方法添加自定义配置;
- addXxx/configureXxx方法:由子类实现,可以向springmvc中添加自定义配置。
 
- 来自
这里总结下这4个类的关系:

理清了这四个类的关系,@EnableWebMvc 的执行流程就一目了然了,这里总结如下:
- 
@EnableWebMvc向spring容器中引入了DelegatingWebMvcConfiguration;
- 
DelegatingWebMvcConfiguration中有包含@Autowired注解的方法setConfigurers(List<WebMvcConfigurer>),在spring bean的周期中会对其执行,作用为获取容器中所有WebMvcConfigurer的bean将其设置到DelegatingWebMvcConfiguration的属性中;
- 
DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport,在spring bean的周期中会处理WebMvcConfigurationSupport中有@Bean注解的方法,这种方法比较多,如requestMappingHandlerMapping()、mvcPathMatcher等,这些都是 smvc 的功能组件;
- 
在处理 WebMvcConfigurationSupport中有@Bean注解的方法时,会调用getXxx()获取相关配置,该配置包括spring提供的默认配置及自定义配置,getXxx()由WebMvcConfigurationSupport提供;
- 
在调用 WebMvcConfigurationSupport#getXxx()获取自定义配置时,会调用addXxx()/configureXxx(),该方法在WebMvcConfigurationSupport中是空方法,具体休逻辑由子类(也就是DelegatingWebMvcConfiguration)提供,最终调用方式是遍历执行第2步获取的WebMvcConfigurer的addXxx()/configureXxx();
整个流程如下图所示:

在启用springmvc功能,并添加自定义配置时,我们可以这么做:
- 
方式1:使用@EnableWebMvc注解启用mvc功能,实现 WebMvcConfigurer,添加自定义配置 // 使用@EnableWebMvc注解启用mvc功能 @Component @EnableWebMvc public class MvcConfig { ... } // 实现 WebMvcConfigurer,添加自定义配置 @Component public class MyWebMvcConfigurer implements WebMvcConfigurer { // 重写WebMvcConfigurer方法,处理自定义配置 }
- 
方式2:实现 WebMvcConfigurationSupport类,重写其中的配置方法@Component public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport { // 重写配置方法,处理自定义配置 }不过采用这种方式后,再实现 WebMvcConfigurer添加自定义配置就不生效了,自定义配置只能在WebMvcConfigurationSupport进行配置。
springmvc提供了哪些配置项呢?我们来看看WebMvcConfigurer提供的方法:
- configurePathMatch:配置路由请求规则
- configureContentNegotiation:内容协商配置
- configureAsyncSupport:
- configureDefaultServletHandling:默认静态资源处理器
- addFormatters:注册自定义转化器
- addInterceptors:拦截器配置
- addResourceHandlers:资源处理
- addCorsMappings:CORS配置
- addViewControllers:视图跳转控制器
- configureViewResolvers:配置视图解析
- addArgumentResolvers:添加自定义方法参数处理器
- addReturnValueHandlers:添加自定义返回结果处理器
- configureMessageConverters:配置消息转换器。重载会覆盖默认注册的- HttpMessageConverter
- extendMessageConverters:配置消息转换器。仅添加一个自定义的- HttpMessageConverter.
- configureHandlerExceptionResolvers:配置异常转换器
- extendHandlerExceptionResolvers:添加异常转化器
- getValidator:
- getMessageCodesResolver:
如果需要配置相关项,只需要重写相关方法 即可。
最后我们再来看看WebMvcConfigurationSupport中引入了哪些Bean,有@Bean注解的方法如下:
- public RequestMappingHandlerMapping requestMappingHandlerMapping(...)
- public PathMatcher mvcPathMatcher()
- public UrlPathHelper mvcUrlPathHelper()
- public ContentNegotiationManager mvcContentNegotiationManager()
- public HandlerMapping viewControllerHandlerMapping(...)
- public BeanNameUrlHandlerMapping beanNameHandlerMapping(...)
- public RouterFunctionMapping routerFunctionMapping(...)
- public HandlerMapping resourceHandlerMapping(...)
- ResourceUrlProvider mvcResourceUrlProvider()
- public HandlerMapping defaultServletHandlerMapping()
- public RequestMappingHandlerAdapter requestMappingHandlerAdapter(...)
- public HandlerFunctionAdapter handlerFunctionAdapter()
- public FormattingConversionService mvcConversionService()
- public Validator mvcValidator()
- public CompositeUriComponentsContributor mvcUriComponentsContributor(...)
- public HttpRequestHandlerAdapter httpRequestHandlerAdapter()
- public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter()
- public HandlerExceptionResolver handlerExceptionResolver(...)
- public ViewResolver mvcViewResolver(...)
- HandlerMappingIntrospector mvcHandlerMappingIntrospector()
这些都是springmvc中用到的一些组件,具体的具体内容就不展开了。
4. 总结
本文内容比较杂,先提供了一个springmvc的demo,然后介绍了demo中0 xml配置原理(也就是servlet 3.0规范),接着介绍了@EnableWebMvc的功能,着重介绍了WebMvcConfigurationSupport的作用。
本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。
本系列的其他文章
转载自:https://juejin.cn/post/7160109694458003470




