创建项目
版本说明这里使用的 SpringBoot 2.0.0.Release
SpringBoot对于SpringMVC的支持
在之前的开发中很多场景下使用的是基于xml配置文件或者是Java配置类的方式来进行SpringMVC的配置。一般来讲,初始的步骤如下所示
- 1、初始化SpringMVC的DispatcherServlet
- 2、搭建转码过滤器,保证客户端请求进行正确的转码
- 3、搭建视图解析器(View Resolver),告诉Spring从什么地方查找视图,以及这些视图使用什么语言编写等
- 4、配置静态资源的位置
- 5、配置所支持的地域以及资源bundle
- 6、配置multipart解析器,保证文件上传能够正常工作
- 7、将Tomcat或者Jetty包含能够在Web服务器上运行应用
- 8、建立错误页面
当然不止是上面这些工作。如下图所示,为SpringMVC核心的处理流程。
核心处理流
功能流图
1.1 分发器和multipart配置
首先在默认的配置文件中加入如下的一行代码,表示已debug模式运行SpringBoot的应用。
debug=true
配置完成之后会看到控制台会打印出debug信息。
DispatcherServletAutoConfiguration matched:- @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)- found 'session' scope (OnWebApplicationCondition)DispatcherServletAutoConfiguration.DispatcherServletConfiguration matched:- @ConditionalOnClass found required class 'javax.servlet.ServletRegistration' (OnClassCondition)- Default DispatcherServlet did not find dispatcher servlet beans (DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition)DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration matched:- @ConditionalOnClass found required class 'javax.servlet.ServletRegistration' (OnClassCondition)- DispatcherServlet Registration did not find servlet registration bean (DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition)DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration#dispatcherServletRegistration matched:- @ConditionalOnBean (names: dispatcherServlet; types: org.springframework.web.servlet.DispatcherServlet; SearchStrategy: all) found bean 'dispatcherServlet' (OnBeanCondition)EmbeddedWebServerFactoryCustomizerAutoConfiguration matched:- @ConditionalOnWebApplication (required) found 'session' scope (OnWebApplicationCondition)EmbeddedWebServerFactoryCustomizerAutoConfiguration.TomcatWebServerFactoryCustomizerConfiguration matched:- @ConditionalOnClass found required classes 'org.apache.catalina.startup.Tomcat', 'org.apache.coyote.UpgradeProtocol' (OnClassCondition)ErrorMvcAutoConfiguration matched:- @ConditionalOnClass found required classes 'javax.servlet.Servlet', 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)- found 'session' scope (OnWebApplicationCondition)ErrorMvcAutoConfiguration#basicErrorController matched:- @ConditionalOnMissingBean (types: org.springframework.boot.web.servlet.error.ErrorController; SearchStrategy: current) did not find any beans (OnBeanCondition)ErrorMvcAutoConfiguration#errorAttributes matched:- @ConditionalOnMissingBean (types: org.springframework.boot.web.servlet.error.ErrorAttributes; SearchStrategy: current) did not find any beans (OnBeanCondition)ErrorMvcAutoConfiguration.DefaultErrorViewResolverConfiguration#conventionErrorViewResolver matched:- @ConditionalOnBean (types: org.springframework.web.servlet.DispatcherServlet; SearchStrategy: all) found bean 'dispatcherServlet'; @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver; SearchStrategy: all) did not find any beans (OnBeanCondition)ErrorMvcAutoConfiguration.WhitelabelErrorViewConfiguration matched:- @ConditionalOnProperty (server.error.whitelabel.enabled) matched (OnPropertyCondition)- ErrorTemplate Missing did not find error template view (ErrorMvcAutoConfiguration.ErrorTemplateMissingCondition)ErrorMvcAutoConfiguration.WhitelabelErrorViewConfiguration#beanNameViewResolver matched:- @ConditionalOnMissingBean (types: org.springframework.web.servlet.view.BeanNameViewResolver; SearchStrategy: all) did not find any beans (OnBeanCondition)ErrorMvcAutoConfiguration.WhitelabelErrorViewConfiguration#defaultErrorView matched:- @ConditionalOnMissingBean (names: error; SearchStrategy: all) did not find any beans (OnBeanCondition)GenericCacheConfiguration matched:- Cache org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration automatic cache type (CacheCondition)HttpEncodingAutoConfiguration matched:- @ConditionalOnClass found required class 'org.springframework.web.filter.CharacterEncodingFilter' (OnClassCondition)- found 'session' scope (OnWebApplicationCondition)- @ConditionalOnProperty (spring.http.encoding.enabled) matched (OnPropertyCondition)HttpEncodingAutoConfiguration#characterEncodingFilter matched:- @ConditionalOnMissingBean (types: org.springframework.web.filter.CharacterEncodingFilter; SearchStrategy: all) did not find any beans (OnBeanCondition)HttpMessageConvertersAutoConfiguration matched:- @ConditionalOnClass found required class 'org.springframework.http.converter.HttpMessageConverter' (OnClassCondition)HttpMessageConvertersAutoConfiguration#messageConverters matched:- @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.http.HttpMessageConverters; SearchStrategy: all) did not find any beans (OnBeanCondition)HttpMessageConvertersAutoConfiguration.StringHttpMessageConverterConfiguration matched:- @ConditionalOnClass found required class 'org.springframework.http.converter.StringHttpMessageConverter' (OnClassCondition)MultipartAutoConfiguration matched:- @ConditionalOnClass found required classes 'javax.servlet.Servlet', 'org.springframework.web.multipart.support.StandardServletMultipartResolver', 'javax.servlet.MultipartConfigElement' (OnClassCondition)- found 'session' scope (OnWebApplicationCondition)- @ConditionalOnProperty (spring.servlet.multipart.enabled) matched (OnPropertyCondition)MultipartAutoConfiguration#multipartConfigElement matched:- @ConditionalOnMissingBean (types: javax.servlet.MultipartConfigElement,org.springframework.web.multipart.commons.CommonsMultipartResolver; SearchStrategy: all) did not find any beans (OnBeanCondition)MultipartAutoConfiguration#multipartResolver matched:- @ConditionalOnMissingBean (types: org.springframework.web.multipart.MultipartResolver; SearchStrategy: all) did not find any beans (OnBeanCondition)
下面就来分析一下DispatcherServletAutoConfiguration配置类
DispatcherServletAutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {
首先会发现它是一个 @Configuration 注解标注的类,说明这是一个典型的Spring配置类。在这个配置类上标注了一个注解 @EnableConfigurationProperties(ServerProperties.class),这个注解表示使用了SpringBoot自动配置原理,对于配置文件与配置类进行了映射,在这里看到向这个配置中注入的自动配置类的 ServerProperties 这里就是在配置文件中以server开头的所有配置。例如在配置文件可以进行端口的设置,访问路径配置
server.port=8080
server.servlet.context-path=/hello
在它的内部有一个静态内部类DispatcherServletConfiguration,这个内部类也是作为一个配置类存在,这里需要注意一个注解 @Conditional 这个注解表示满足条件之后才会向容器中注入对应的组件。这里会看到如果容器中有 DefaultDispatcherServletCondition 这个类才会进行处理。也就是说在这里需要一个默认的DispatcherServlet才会生效
@Configuration
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {private final WebMvcProperties webMvcProperties;public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {this.webMvcProperties = webMvcProperties;}@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public DispatcherServlet dispatcherServlet() {DispatcherServlet dispatcherServlet = new DispatcherServlet();dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());return dispatcherServlet;}@Bean@ConditionalOnBean(MultipartResolver.class)@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)public MultipartResolver multipartResolver(MultipartResolver resolver) {// Detect if the user has created a MultipartResolver but named it incorrectlyreturn resolver;}}
第一步首先来看看对于 DefaultDispatcherServletCondition 进行分析
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DefaultDispatcherServletCondition extends SpringBootCondition {//继承父类实现获取匹配结果方法/*** 两个参数* 第一个参数ConditionContext 进行操作的上下文* 第二个参数AnnotatedTypeMetadata 注解类型的元数据*/@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context,AnnotatedTypeMetadata metadata) {//定义默认的DispatcherServlet信息 ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");//从配合beanFactory中进行遍历 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();List<String> dispatchServletBeans = Arrays.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));//找到dispatcherServletif (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {return ConditionOutcome.noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));}if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {return ConditionOutcome.noMatch(message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));}if (dispatchServletBeans.isEmpty()) {return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());}return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans").items(Style.QUOTE, dispatchServletBeans).append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));}}
我们知道在使用@Bean注解的时候如果没有指定name属性默认会以方法名为容器中Bean的ID,在上面这类中对容器中的DispatchServlet进行匹配的时候需要满足下面这个Bean的条件。而这个bean的配置正是可以在webMvcProperteis中进行配置的。
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {DispatcherServlet dispatcherServlet = new DispatcherServlet();dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());return dispatcherServlet;
}
在其内部还有另外一个内部类DispatcherServletRegistrationConfiguration
@Configuration
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {private final WebMvcProperties webMvcProperties;private final MultipartConfigElement multipartConfig;public DispatcherServletRegistrationConfiguration(WebMvcProperties webMvcProperties,ObjectProvider<MultipartConfigElement> multipartConfigProvider) {this.webMvcProperties = webMvcProperties;this.multipartConfig = multipartConfigProvider.getIfAvailable();}@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,this.webMvcProperties.getServlet().getPath());registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());if (this.multipartConfig != null) {registration.setMultipartConfig(this.multipartConfig);}return registration;}}
@Order(Ordered.LOWEST_PRECEDENCE - 10)private static class DefaultDispatcherServletCondition extends SpringBootCondition {@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();List<String> dispatchServletBeans = Arrays.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {return ConditionOutcome.noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));}if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {return ConditionOutcome.noMatch(message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));}if (dispatchServletBeans.isEmpty()) {return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());}return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans").items(Style.QUOTE, dispatchServletBeans).append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));}}@Order(Ordered.LOWEST_PRECEDENCE - 10)private static class DispatcherServletRegistrationCondition extends SpringBootCondition {@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory);if (!outcome.isMatch()) {return outcome;}return checkServletRegistration(beanFactory);}private ConditionOutcome checkDefaultDispatcherName(ConfigurableListableBeanFactory beanFactory) {List<String> servlets = Arrays.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));boolean containsDispatcherBean = beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);if (containsDispatcherBean && !servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {return ConditionOutcome.noMatch(startMessage().found("non dispatcher servlet").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));}return ConditionOutcome.match();}private ConditionOutcome checkServletRegistration(ConfigurableListableBeanFactory beanFactory) {ConditionMessage.Builder message = startMessage();List<String> registrations = Arrays.asList(beanFactory.getBeanNamesForType(ServletRegistrationBean.class, false, false));boolean containsDispatcherRegistrationBean = beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);if (registrations.isEmpty()) {if (containsDispatcherRegistrationBean) {return ConditionOutcome.noMatch(message.found("non servlet registration bean").items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));}return ConditionOutcome.match(message.didNotFind("servlet registration bean").atAll());}if (registrations.contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) {return ConditionOutcome.noMatch(message.found("servlet registration bean").items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));}if (containsDispatcherRegistrationBean) {return ConditionOutcome.noMatch(message.found("non servlet registration bean").items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));}return ConditionOutcome.match(message.found("servlet registration beans").items(Style.QUOTE, registrations).append("and none is named " + DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));}private ConditionMessage.Builder startMessage() {return ConditionMessage.forCondition("DispatcherServlet Registration");}}
在Spring中往往会通过 @Order 注解来声明优先级,可以看到上面的内容对于两个静态内部类都做了是优先级的配置。在其中还有一个比较重要的注解值得关注 @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) 对于这些注解来说都是为了进一步的优化配置顺序细节处理。