likes
comments
collection
share

spring mvc(一):springmvc demo 与 @EnableWebMvc 注解

作者站长头像
站长
· 阅读数 56

注:本系列源码分析基于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启动逻辑。

运行,结果如下:

控制台:

spring mvc(一):springmvc demo 与 @EnableWebMvc 注解

页面返回:

spring mvc(一):springmvc demo 与 @EnableWebMvc 注解

可以看到,一个简单的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项目是怎么启动的呢?

servlet3.0之后,提供了一个spi规范,spring对其实现如下:

  1. spring-web模块的/src/main/resources/META-INF/services/文件夹下,创建文件javax.servlet.ServletContainerInitializer,内容如下
org.springframework.web.SpringServletContainerInitializer

spring mvc(一):springmvc demo 与 @EnableWebMvc 注解

  1. 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);
        }
    }

}
  1. 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 mvc(一):springmvc demo 与 @EnableWebMvc 注解

由此,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相关的配置的。

为了更好地分析,这里先介绍几个类:

  1. 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 对应的方法进行配置的
        ...
    
    }
    
  2. WebMvcConfigurerCompositeWebMvcConfigurer的组合:

    /**
     * 实现了 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);
            }
        }
    
        // 其他配置类似,省略
        ...
    }
    
  3. WebMvcConfigurer:springmvc的配置接口,提供了非常多的配置

    public interface WebMvcConfigurer {
    
        default void configurePathMatch(PathMatchConfigurer configurer) {
        }
    
        default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        }
    
        default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        }
    
        ...
    }
    
  4. 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个类的关系:

spring mvc(一):springmvc demo 与 @EnableWebMvc 注解

理清了这四个类的关系,@EnableWebMvc 的执行流程就一目了然了,这里总结如下:

  1. @EnableWebMvc 向spring容器中引入了 DelegatingWebMvcConfiguration

  2. DelegatingWebMvcConfiguration中有包含@Autowired注解的方法setConfigurers(List<WebMvcConfigurer>),在spring bean的周期中会对其执行,作用为获取容器中所有WebMvcConfigurer的bean将其设置到 DelegatingWebMvcConfiguration的属性中;

  3. DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport,在spring bean的周期中会处理WebMvcConfigurationSupport中有@Bean注解的方法,这种方法比较多,如requestMappingHandlerMapping()mvcPathMatcher等,这些都是 smvc 的功能组件;

  4. 在处理WebMvcConfigurationSupport中有@Bean注解的方法时,会调用getXxx()获取相关配置,该配置包括spring提供的默认配置及自定义配置,getXxx()WebMvcConfigurationSupport提供;

  5. 在调用WebMvcConfigurationSupport#getXxx()获取自定义配置时,会调用addXxx()/configureXxx(),该方法在WebMvcConfigurationSupport中是空方法,具体休逻辑由子类(也就是 DelegatingWebMvcConfiguration)提供,最终调用方式是遍历执行第2步获取的WebMvcConfigureraddXxx()/configureXxx()

整个流程如下图所示:

spring mvc(一):springmvc demo 与 @EnableWebMvc 注解

在启用springmvc功能,并添加自定义配置时,我们可以这么做:

  1. 方式1:使用@EnableWebMvc注解启用mvc功能,实现 WebMvcConfigurer,添加自定义配置

    // 使用@EnableWebMvc注解启用mvc功能
    @Component
    @EnableWebMvc
    public class MvcConfig {
        ...
    }
    
    // 实现 WebMvcConfigurer,添加自定义配置
    @Component
    public class MyWebMvcConfigurer implements WebMvcConfigurer {
    
        // 重写WebMvcConfigurer方法,处理自定义配置
    }
    
  2. 方式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/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

本系列的其他文章

【spring源码分析】spring源码分析系列目录

转载自:https://juejin.cn/post/7160109694458003470
评论
请登录