admin管理员组

文章数量:1794759

Spring Mvc迁移到Spring Boot

Spring Mvc迁移到Spring Boot

Spring Mvc迁移到Spring Boot 题记

随着Spring Boot的蓬勃发展,在业界产生的影响已经十分深远,大多数分布式项目开始往微服务方向改造。由于多数公司依赖于强大的Spring Framework,所以Spring Boot的发展趋势就不言而喻了。当然伴随着多数追随者的主要原因还是自身功能的强大:

Spring 官方说明 Create stand-alone Spring applications -- 创建一个独立的Spring应用 Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files) -- 嵌入式的web服务器Tomcat、Jetty、Undertow(无需部署 war 文件) Provide opinionated 'starter' dependencies to simplify your build configuration -- 提供建议的 "启动" 依赖关系, 以简化生成配置 Automatically configure Spring and 3rd party libraries whenever possible -- 尽可能自动配置 spring 和第三方库 Provide production-ready features such as metrics, health checks and externalized configuration -- 具备为生产准备的特性,如指标、运行状况检查和外部化配置 Absolutely no code generation and no requirement for XML configuration -- 尽可能的无需代码生成并且无XML配置

由于大部分项目属于web项目,前端展示使用jsp,在迁移的过程中使用的首先面对的就是Sring Mvc的迁移

Spring MVC架构图

Spring Web MVC各大组件 Bean typeExplanation
HandlerMapping将请求映射到处理程序以及用于预处理和后处理的拦截器列表。映射基于一些条件, 这些条件的详细信因 HandlerMapping 实现的不同而不同。两个主要的 HandlerMapping 实现是 requestmappinghandl善于映射, 它支持 @RequestMapping 带注释的方法, 以及简单 urlhandl十分映射, 它维护 uri 路径模式对处理程序的显式注册。
HandlerAdapter帮助调度服务器调用映射到请求的处理程序, 而不考虑处理程序的实际调用方式。例如, 调用带注释的控制器需要解析批注。handleradapter 的主要目的是保护调度服务不受这些细节的影响。
HandlerExceptionResolver解决异常的策略可能会将它们映射到处理程序, 或 html 错误视图或其他。请参阅异常。
ViewResolver解析从处理程序返回到要呈现给响应的实际视图的基于逻辑 String-based 的视图名称。
LocaleResolver, LocaleContextResolver解析客户端正在使用的区域设置, 并可能解析其时区, 以便能够提供国际化视图。
ThemeResolver解决 web 应用程序可以使用的主题, 例如, 提供个性化布局。
MultipartResolver用于分析多部分请求的抽象 (例如,浏览器表单文件上传) 的帮助下, 一些多部分的分析库。即多媒体支持
FlashMapManager存储和检索 “输入” 和 “输出” flashmap, 这些信可用于将属性从一个请求传递到另一个请求, 通常是通过重定向。
回顾Spring MVC

在Spring Mvc项目中,随着web服务器,比如Tomcat的启动,会去加载webapp\\WEB-INF\\web.xml,在该文件中驱动Spring Context和Spring MVC Context的加载,完成容器的启动以及Spring的初始化。

下面从标准的官方web.xml 和 app-context.xml进行分析 <web-app> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/app-context.xml</param-value> </context-param> <servlet> <servlet-name>app</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>app</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="www.springframework/schema/beans" xmlns:xsi="www.w3/2001/XMLSchema-instance" xmlns:p="www.springframework/schema/p" xmlns:context="www.springframework/schema/context" xsi:schemaLocation="www.springframework/schema/beans www.springframework/schema/beans/spring-beans-3.0.xsd www.springframework/schema/aop www.springframework/schema/aop/spring-aop-3.0.xsd www.springframework/schema/tx www.springframework/schema/tx/spring-tx-3.0.xsd www.springframework/schema/context www.springframework/schema/context/spring-context-3.0.xsd"> <context:annotation-config /> <!-- component-scan --> <context:component-scan base-package="com.sxs.web" /> <!-- 视图 beans --> <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix"> <value>/WEB-INF/jsp/</value> </property> <property name="suffix"> <value>.jsp</value> </property> <property name="order" value="1" /> </bean> <!-- json消转换 --> <bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> </list> </property> </bean> <!-- 文件上传 --> <bean id="multipartResolver" class="org.springframework.web.multipartmons.CommonsMultipartResolver"> <!-- set the max upload size100MB --> <property name="maxUploadSize"> <value>104857600</value> </property> <property name="maxInMemorySize"> <value>1024000</value> </property> </bean> </beans>
  • org.springframework.web.context.ContextLoaderListener该类是Spring启动类

    web容器启动后spring加载顺序ContextLoaderListener -> ContextLoader#initWebApplicationContext -> ContextLoader#configureAndRefreshWebApplicationContext -> wac.refresh() ->AbstractApplicationContext#refresh

  • org.springframework.web.servlet.DispatcherServlet该类是Spring MVC启动类

    web容器启动后Spring MVC加载顺序HttpServletBean#initServletBean ->FrameworkServlet#initServletBean ->FrameworkServlet#initWebApplicationContext -> org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.web.context.WebApplicationContext) -> FrameworkServlet.ContextRefreshListener#onApplicationEvent ->DispatcherServlet -

    DispatcherServlet#onRefresh -> DispatcherServlet#initStrategies ->DispatcherServlet#doService -> DispatcherServlet#doDispatch ->DispatcherServlet#getHandler -> DispatcherServlet#getHandlerAdapter -> HandlerAdapter#handle-> DispatcherServlet#processDispatchResult ->DispatcherServlet#render -> DispatcherServlet#resolveViewName -> ModelAndView#getView

以上为web服务器启动Spring 和 Spring MVC加载过程 准备迁移

迁移思路

在Spring Boot中是没有web.xml的,而且app-context.xml的各大组件配置完全可以依赖于WebMvcAutoConfiguration进行自动装配,此处需要说明的是必须要明确WebMvcAutoConfiguration自动装配的条件(自动装配详解下述分析),另外Spring Boot自身包含嵌入式的web服务器(如:Tomcat),所以当WebMvcAutoConfiguration自动装配完成后即可重启项目(在IDEA中可直接运行@SpringBootApplication标注的类,在环境中可以直接java -jar xxx.jar) WebMvcAutoConfiguration自动装配 @Configuration @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { public static final String DEFAULT_PREFIX = ""; public static final String DEFAULT_SUFFIX = ""; private static final String[] SERVLET_LOCATIONS = { "/" }; @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); } @Bean @ConditionalOnMissingBean(HttpPutFormContentFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true) public OrderedHttpPutFormContentFilter httpPutFormContentFilter() { return new OrderedHttpPutFormContentFilter(); } // Defined as a nested config to ensure WebMvcConfigurer is not read when not // on the classpath @Configuration @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ResourceLoaderAware { private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class); private final ResourceProperties resourceProperties; private final WebMvcProperties mvcProperties; private final ListableBeanFactory beanFactory; private final HttpMessageConverters messageConverters; final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; private ResourceLoader resourceLoader; public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, @Lazy HttpMessageConverters messageConverters, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) { this.resourceProperties = resourceProperties; this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConverters = messageConverters; this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider .getIfAvailable(); } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.addAll(this.messageConverters.getConverters()); } @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { Duration timeout = this.mvcProperties.getAsync().getRequestTimeout(); if (timeout != null) { configurer.setDefaultTimeout(timeout.toMillis()); } } @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch( this.mvcProperties.getPathmatch().isUseSuffixPattern()); configurer.setUseRegisteredSuffixPatternMatch( this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern()); } @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties .getContentnegotiation(); configurer.favorPathExtension(contentnegotiation.isFavorPathExtension()); configurer.favorParameter(contentnegotiation.isFavorParameter()); if (contentnegotiation.getParameterName() != null) { configurer.parameterName(contentnegotiation.getParameterName()); } Map<String, MediaType> mediaTypes = this.mvcProperties.getContentnegotiation() .getMediaTypes(); mediaTypes.forEach(configurer::mediaType); } @Bean @ConditionalOnMissingBean public InternalResourceViewResolver defaultViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(this.mvcProperties.getView().getPrefix()); resolver.setSuffix(this.mvcProperties.getView().getSuffix()); return resolver; } @Bean @ConditionalOnBean(View.class) @ConditionalOnMissingBean public BeanNameViewResolver beanNameViewResolver() { BeanNameViewResolver resolver = new BeanNameViewResolver(); resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10); return resolver; } @Bean @ConditionalOnBean(ViewResolver.class) @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class) public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); resolver.setContentNegotiationManager( beanFactory.getBean(ContentNegotiationManager.class)); // ContentNegotiatingViewResolver uses all the other view resolvers to locate // a view so it should have a high precedence resolver.setOrder(Ordered.HIGHEST_PRECEDENCE); return resolver; } @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver() { if (this.mvcProperties .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } @Override public MessageCodesResolver getMessageCodesResolver() { if (this.mvcProperties.getMessageCodesResolverFormat() != null) { DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver(); resolver.setMessageCodeFormatter( this.mvcProperties.getMessageCodesResolverFormat()); return resolver; } return null; } @Override public void addFormatters(FormatterRegistry registry) { for (Converter<?, ?> converter : getBeansOfType(Converter.class)) { registry.addConverter(converter); } for (GenericConverter converter : getBeansOfType(GenericConverter.class)) { registry.addConverter(converter); } for (Formatter<?> formatter : getBeansOfType(Formatter.class)) { registry.addFormatter(formatter); } } private <T> Collection<T> getBeansOfType(Class<T> type) { return this.beanFactory.getBeansOfType(type).values(); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache() .getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry .addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)) .setCacheControl(cacheControl)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration( registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations( this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)) .setCacheControl(cacheControl)); } } private Integer getSeconds(Duration cachePeriod) { return (cachePeriod != null ? (int) cachePeriod.getSeconds() : null); } @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping( ApplicationContext applicationContext) { return new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern()); } static String[] getResourceLocations(String[] staticLocations) { String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length]; System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length); System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length); return locations; } private Optional<Resource> getWelcomePage() { String[] locations = getResourceLocations( this.resourceProperties.getStaticLocations()); return Arrays.stream(locations).map(this::getIndexHtml) .filter(this::isReadable).findFirst(); } private Resource getIndexHtml(String location) { return this.resourceLoader.getResource(location + "index.html"); } private boolean isReadable(Resource resource) { try { return resource.exists() && (resource.getURL() != null); } catch (Exception ex) { return false; } } private void customizeResourceHandlerRegistration( ResourceHandlerRegistration registration) { if (this.resourceHandlerRegistrationCustomizer != null) { this.resourceHandlerRegistrationCustomizer.customize(registration); } } @Bean @ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class }) public static RequestContextFilter requestContextFilter() { return new OrderedRequestContextFilter(); } @Configuration @ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true) public static class FaviconConfiguration implements ResourceLoaderAware { private final ResourceProperties resourceProperties; private ResourceLoader resourceLoader; public FaviconConfiguration(ResourceProperties resourceProperties) { this.resourceProperties = resourceProperties; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Bean public SimpleUrlHandlerMapping faviconHandlerMapping() { SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler())); return mapping; } @Bean public ResourceHttpRequestHandler faviconRequestHandler() { ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); requestHandler.setLocations(resolveFaviconLocations()); return requestHandler; } private List<Resource> resolveFaviconLocations() { String[] staticLocations = getResourceLocations( this.resourceProperties.getStaticLocations()); List<Resource> locations = new ArrayList<>(staticLocations.length + 1); Arrays.stream(staticLocations).map(this.resourceLoader::getResource) .forEach(locations::add); locations.add(new ClassPathResource("/")); return Collections.unmodifiableList(locations); } } } /** * Configuration equivalent to {@code @EnableWebMvc}. */ @Configuration public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { private final WebMvcProperties mvcProperties; private final ListableBeanFactory beanFactory; private final WebMvcRegistrations mvcRegistrations; public EnableWebMvcConfiguration( ObjectProvider<WebMvcProperties> mvcPropertiesProvider, ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) { this.mvcProperties = mvcPropertiesProvider.getIfAvailable(); this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique(); this.beanFactory = beanFactory; } @Bean @Override public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(); adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect()); return adapter; } @Override protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() { if (this.mvcRegistrations != null && this.mvcRegistrations.getRequestMappingHandlerAdapter() != null) { return this.mvcRegistrations.getRequestMappingHandlerAdapter(); } return super.createRequestMappingHandlerAdapter(); } @Bean @Primary @Override public RequestMappingHandlerMapping requestMappingHandlerMapping() { // Must be @Primary for MvcUriComponentsBuilder to work return super.requestMappingHandlerMapping(); } @Bean @Override public FormattingConversionService mvcConversionService() { WebConversionService conversionService = new WebConversionService( this.mvcProperties.getDateFormat()); addFormatters(conversionService); return conversionService; } @Bean @Override public Validator mvcValidator() { if (!ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { return super.mvcValidator(); } return ValidatorAdapter.get(getApplicationContext(), getValidator()); } @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { if (this.mvcRegistrations != null && this.mvcRegistrations.getRequestMappingHandlerMapping() != null) { return this.mvcRegistrations.getRequestMappingHandlerMapping(); } return super.createRequestMappingHandlerMapping(); } @Override protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() { try { return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class); } catch (NoSuchBeanDefinitionException ex) { return super.getConfigurableWebBindingInitializer(); } } @Override protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() { if (this.mvcRegistrations != null && this.mvcRegistrations .getExceptionHandlerExceptionResolver() != null) { return this.mvcRegistrations.getExceptionHandlerExceptionResolver(); } return super.createExceptionHandlerExceptionResolver(); } @Override protected void configureHandlerExceptionResolvers( List<HandlerExceptionResolver> exceptionResolvers) { super.configureHandlerExceptionResolvers(exceptionResolvers); if (exceptionResolvers.isEmpty()) { addDefaultHandlerExceptionResolvers(exceptionResolvers); } if (this.mvcProperties.isLogResolvedException()) { for (HandlerExceptionResolver resolver : exceptionResolvers) { if (resolver instanceof AbstractHandlerExceptionResolver) { ((AbstractHandlerExceptionResolver) resolver) .setWarnLogCategory(resolver.getClass().getName()); } } } } @Bean @Override public ContentNegotiationManager mvcContentNegotiationManager() { ContentNegotiationManager manager = super.mvcContentNegotiationManager(); List<ContentNegotiationStrategy> strategies = manager.getStrategies(); ListIterator<ContentNegotiationStrategy> iterator = strategies.listIterator(); while (iterator.hasNext()) { ContentNegotiationStrategy strategy = iterator.next(); if (strategy instanceof PathExtensionContentNegotiationStrategy) { iterator.set(new OptionalPathExtensionContentNegotiationStrategy( strategy)); } } return manager; } } @Configuration @ConditionalOnEnabledResourceChain static class ResourceChainCustomizerConfiguration { @Bean public ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() { return new ResourceChainResourceHandlerRegistrationCustomizer(); } } interface ResourceHandlerRegistrationCustomizer { void customize(ResourceHandlerRegistration registration); } private static class ResourceChainResourceHandlerRegistrationCustomizer implements ResourceHandlerRegistrationCustomizer { @Autowired private ResourceProperties resourceProperties = new ResourceProperties(); @Override public void customize(ResourceHandlerRegistration registration) { ResourceProperties.Chain properties = this.resourceProperties.getChain(); configureResourceChain(properties, registration.resourceChain(properties.isCache())); } private void configureResourceChain(ResourceProperties.Chain properties, ResourceChainRegistration chain) { Strategy strategy = properties.getStrategy(); if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) { chain.addResolver(getVersionResourceResolver(strategy)); } if (properties.isGzipped()) { chain.addResolver(new GzipResourceResolver()); } if (properties.isHtmlApplicationCache()) { chain.addTransformer(new AppCacheManifestTransformer()); } } private ResourceResolver getVersionResourceResolver( ResourceProperties.Strategy properties) { VersionResourceResolver resolver = new VersionResourceResolver(); if (properties.getFixed().isEnabled()) { String version = properties.getFixed().getVersion(); String[] paths = properties.getFixed().getPaths(); resolver.addFixedVersionStrategy(version, paths); } if (properties.getContent().isEnabled()) { String[] paths = properties.getContent().getPaths(); resolver.addContentVersionStrategy(paths); } return resolver; } } /** * Decorator to make {@link PathExtensionContentNegotiationStrategy} optional * depending on a request attribute. */ static class OptionalPathExtensionContentNegotiationStrategy implements ContentNegotiationStrategy { private static final String SKIP_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class .getName() + ".SKIP"; private final ContentNegotiationStrategy delegate; OptionalPathExtensionContentNegotiationStrategy( ContentNegotiationStrategy delegate) { this.delegate = delegate; } @Override public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { Object skip = webRequest.getAttribute(SKIP_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); if (skip != null && Boolean.parseBoolean(skip.toString())) { return MEDIA_TYPE_ALL_LIST; } return this.delegate.resolveMediaTypes(webRequest); } } }
  • 在WebMvcAutoConfiguration中自动装配是有条件的,该处用到了条件装配和顺序装配:
  • 即该装配必须在@ConditionalOnMissingBean条件满足WebMvcConfigurationSupport类或者子类不存在时,一般情况下是指在Spring Web MVC阶段没有@EnableWebMvc进行手动装配的条件下。
  • 相对顺序装配@AutoConfigureAfter,指在DispatcherServletAutoConfiguration.class,和ValidationAutoConfiguration.class装配完成后执行。
  • 绝对顺序装配@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10),在第十一位进行装配。
  • 在WebMvcAutoConfiguration中主要是初始化各个上述组件,还有资源的加载setResourceLoader,消转换处理configureMessageConverters,内容调停configureContentNegotiation,此外在Servlet3.0之后提供了对异步的支持configureAsyncSupport等等。
  • 对于DispatcherServletAutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass(DispatcherServlet.class) @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) @EnableConfigurationProperties(ServerProperties.class) public class DispatcherServletAutoConfiguration { /* * The bean name for a DispatcherServlet that will be mapped to the root URL "/" */ public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet"; /* * The bean name for a ServletRegistrationBean for the DispatcherServlet "/" */ public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration"; @Configuration @Conditional(DefaultDispatcherServletCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) protected static class DispatcherServletConfiguration { private final WebMvcProperties webMvcProperties; private final ServerProperties serverProperties; public DispatcherServletConfiguration(WebMvcProperties webMvcProperties, ServerProperties serverProperties) { this.webMvcProperties = webMvcProperties; this.serverProperties = serverProperties; } @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 incorrectly return resolver; } @Bean public DispatcherServletPathProvider mainDispatcherServletPathProvider() { return () -> DispatcherServletConfiguration.this.serverProperties.getServlet() .getPath(); } } @Configuration @Conditional(DispatcherServletRegistrationCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) @Import(DispatcherServletConfiguration.class) protected static class DispatcherServletRegistrationConfiguration { private final ServerProperties serverProperties; private final WebMvcProperties webMvcProperties; private final MultipartConfigElement multipartConfig; public DispatcherServletRegistrationConfiguration( ServerProperties serverProperties, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfigProvider) { this.serverProperties = serverProperties; 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 ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration( DispatcherServlet dispatcherServlet) { ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>( dispatcherServlet, this.serverProperties.getServlet().getServletMapping()); 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 { @Override public 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 { @Override public 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"); } } }
  • 条件装配@ConditionalOnClass和@ConditionalOnWebApplication(type = Type.SERVLET)装载顺序

    @AutoConfigureOrder以及@AutoConfigureAfter表示在Servlet服务器装载完成后进行装配。

  • 对于ServletWebServerFactoryAutoConfiguration

@Configuration @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(ServerProperties.class) @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) public class ServletWebServerFactoryAutoConfiguration { @Bean public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer( ServerProperties serverProperties) { return new ServletWebServerFactoryCustomizer(serverProperties); } @Bean @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer( ServerProperties serverProperties) { return new TomcatServletWebServerFactoryCustomizer(serverProperties); } /** * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via * {@link ImportBeanDefinitionRegistrar} for early registration. */ public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (beanFactory instanceof ConfigurableListableBeanFactory) { this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } } @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory == null) { return; } registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class); registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class); } private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) { if (ObjectUtils.isEmpty( this.beanFactory.getBeanNamesForType(beanClass, true, false))) { RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); beanDefinition.setSynthetic(true); registry.registerBeanDefinition(name, beanDefinition); } } } }
  • 条件装配ConditionalOnClass和ConditionalOnWebApplication只有是Servlet时进行装配,@Import这个是该类的重点,即web服务器的选择
上述简单介绍了在Spring Boot中对于web mvc的实现,具体细则本次不做过多累述 具体实现

创建Spring Boot的Web项目,修改启动类

@SpringBootApplication(scanBasePackages = "com.sxs.web") public class SpringBootWebMvcBootstrap { public static void main(String[] args) { SpringApplication.run(SpringBootWebMvcBootstrap.class, args); } }

修改aplication.properties配置文件

#服务器端口号 server.port = 8088 #修改视图映射前、后缀 spring.mvc.view.prefix = /WEB-INF/jsp/ spring.mvc.view.suffix = .jsp

此外,所有的.jsp文件参考原有路径进行移植

完整代码及详情

具体依赖及相关源码参照

github/dwyanewede/spring-boot/tree/master/springboot-webmvc/src/main/java/com/sxs/web

相关详情参照公众号

我叫小朋友

本文标签: springMVCboot