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