承接前文springboot情操陶冶-web配置(一),本文将在前文的基础上分析下mvc的相关应用
MVC简单例子
直接编写一个Controller层的代码,返回格式为json
package com.example.demo.web.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;import java.util.Map;/**
* @author nanco
* -------------
* -------------
* @create 2018/9/4
**/@Controller@RequestMapping("/boot")@ResponseBodypublic class DemoController { @RequestMapping(value = "/hello", method = RequestMethod.GET) public Map<String, String> helloWorld() {
Map<String, String> result = new HashMap<>();
result.put("springboot", "hello world"); return result;
}
}运行之后,客户端工具HTTP访问链接http://127.0.0.1:9001/demoWeb/boot/hello便可得到以下的简单结果
{"springboot":"hello world"}源码剖析
我们都知道springmvc最核心的组件便是DispatcherServlet,其本质是个Servlet组件,也包含了处理前端请求的逻辑,具体的可参照SpringMVC源码情操陶冶-DispatcherServlet。本文则讲解Springboot创建DispatcherServlet以及MVC配置的过程
DispatcherServletAutoConfiguration
首先需要配置DispatcherServlet组件,分为几个步骤来看
No.1 脑头注解了解下
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)@Configuration@ConditionalOnWebApplication(type = Type.SERVLET)@ConditionalOnClass(DispatcherServlet.class)@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)@EnableConfigurationProperties(ServerProperties.class)public class DispatcherServletAutoConfiguration {
}由以上的注解可得知,其需要在ServletWebServerFactoryAutoConfiguration类注入至bean工厂后方可继续,这就和前文关联起来了。
No.2 DispatcherServletConfiguration内部类
@Configuration
@Conditional(DefaultDispatcherServletCondition.class) @ConditionalOnClass(ServletRegistration.class) @EnableConfigurationProperties(WebMvcProperties.class) protected static class DispatcherServletConfiguration { // 引入了spring.mvc为开头的配置
private final WebMvcProperties webMvcProperties; private final ServerProperties serverProperties; public DispatcherServletConfiguration(WebMvcProperties webMvcProperties,
ServerProperties serverProperties) { this.webMvcProperties = webMvcProperties; this.serverProperties = serverProperties;
} // 直接创建DispatcherServlet并注入至bean工厂
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet(); // 对应spring.mvc.dispatch-options-request
dispatcherServlet.setDispatchOptionsRequest( this.webMvcProperties.isDispatchOptionsRequest()); // 对应spring.mvc.dispatch-trace-request
dispatcherServlet.setDispatchTraceRequest( this.webMvcProperties.isDispatchTraceRequest()); // 对应spring.mvc.throw-exception-if-no-handler-found
dispatcherServlet.setThrowExceptionIfNoHandlerFound( this.webMvcProperties.isThrowExceptionIfNoHandlerFound()); return dispatcherServlet;
} // 创建名为multipartResolver的用于文件请求
@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;
} // 获取server.servlet.path表明DispatcherServlet的拦截路径
@Bean
public DispatcherServletPathProvider mainDispatcherServletPathProvider() { return () -> DispatcherServletConfiguration.this.serverProperties.getServlet()
.getPath();
}
}很简单,就是创建了DispatcherServlet,那么如何被注入至tomcat的servlet集合中呢
N0.3 DispatcherServletRegistrationConfiguration内部类
@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();
} // 对DispatcherServlet注入至tomcat等容器中
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) { // 同server.servlet.path,默认为/
ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
dispatcherServlet, this.serverProperties.getServlet().getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); // 读取spring.mvc.servlet.load-on-startup,默认为-1
registration.setLoadOnStartup( this.webMvcProperties.getServlet().getLoadOnStartup()); if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
} return registration;
}
}由上述代码得知,将servlet注入至tomcat容器是通过ServletContextInitializer接口的实现类ServletRegistrationBean来实现的,具体的本文不展开,不过如果用户想把Servlet或者Filter注入至tomcat,则常用此Bean来操作即可
WebMvcAutoConfiguration
DispatcherServlet组件创建并注入至web容器后,接下来便是对mvc的相关配置,笔者也按几个步骤来分析
No.1 脑壳注解看一下
@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 {
}此配置也是根据上文中的DispatcherServletAutoConfiguration注入至bean工厂后再生效。
No.2 Filter集合
1.HiddenHttpMethodFilter-隐性传播PUT/DELETE/PATCH请求
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { // 默认对post请求的包读取_method参数指定的方法,然后再作转换
return new OrderedHiddenHttpMethodFilter();
}隐性的通过methodParam参数来传播PUT/DELETE/PATCH请求,默认参数名为*_method*,也可用户自行配置
2.HttpPutFormContentFilter-显性响应PUT/DELETE/PATCH请求
// spring.mvc.formcontent.putfilter.enabled不指定或者值不为false则生效
@Bean
@ConditionalOnMissingBean(HttpPutFormContentFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true) public OrderedHttpPutFormContentFilter httpPutFormContentFilter() { // 直接对PUT/DELETE/PATCH请求进行响应,其order值大于OrderedHiddenHttpMethodFilter
return new OrderedHttpPutFormContentFilter();
}其一般与上述的OrderedHiddenHttpMethodFilter搭配使用,其order值大于前者所以排在后面响应PUT等请求
No.3 EnableWebMvcConfiguration内部类,其类同@EnableWebMvc注解,类同我们常用spring配置的mvc:annotation-driven。由于代码过多,就挑选几个来看
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { // 注册RequestMappingHandlerAdapter组件
@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null
|| this.mvcProperties.isIgnoreDefaultModelOnRedirect()); return adapter;
} // 注册RequestMappingHanlderMapping组件
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() { // Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping();
}
// 校验器组件
@Bean
@Override
public Validator mvcValidator() { if (!ClassUtils.isPresent("javax.validation.Validator",
getClass().getClassLoader())) { return super.mvcValidator();
} return ValidatorAdapter.get(getApplicationContext(), getValidator());
}
// 异常处理组件
@Override
protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() { if (this.mvcRegistrations != null && this.mvcRegistrations
.getExceptionHandlerExceptionResolver() != null) { return this.mvcRegistrations.getExceptionHandlerExceptionResolver();
} return super.createExceptionHandlerExceptionResolver();
}
}主要是用来注册响应前端请求的插件集合,具体的怎么整合可见笔者置顶的spring文章,里面有提,就不在此处展开了
温馨提示:笔者此处提醒下此类是DelegatingWebMvcConfiguration的实现类,其本身也被注解@Configuration修饰,其内部的setConfigurers()方法有助于集结所有实现了WebMvcConfigurer接口的集合,所以用户可通过实现此接口来扩展mvc的相关配置
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers);
}
}No.4 WebMvcAutoConfigurationAdapter内部类(WebMvcConfigurer接口实现类)-在上述的MVC组件的基础上新增其他的组件,包含视图组件、消息处理器组件等。
限于代码过长,笔者此处也挑选几个来看
// 消息处理器集合配置
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.addAll(this.messageConverters.getConverters());
}
// 对路径请求的配置
@Override
public void configurePathMatch(PathMatchConfigurer configurer) { // 对应spring.mvc.pathmatch.use-suffix-pattern,默认为false
configurer.setUseSuffixPatternMatch( this.mvcProperties.getPathmatch().isUseSuffixPattern()); // 对应spring.mvc.patchmatch.use-registered-suffix-pattern,默认为false
configurer.setUseRegisteredSuffixPatternMatch( this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
}
// 创建jsp视图解析器
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver(); // 对应spring.mvc.view.prefix,默认为空 resolver.setPrefix(this.mvcProperties.getView().getPrefix());
// 对应spring.mvc.view.suffix,默认为空
resolver.setSuffix(this.mvcProperties.getView().getSuffix()); return resolver;
}
// 静态文件访问配置
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { // 对应spring.resource.add-mappings,默认为true
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));
} // 对应spring.mvc.static-path-pattern,默认为/**
String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern) // 对应spring.resources.static-locations
.addResourceLocations(getResourceLocations( this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod))
.setCacheControl(cacheControl));
}
} // 欢迎界面配置,一般可在static或者项目根目录下配置index.html界面即可
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ApplicationContext applicationContext) { return new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext),
applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern());
}小结
本文主要讲解了mvc的springboot自动配置过程,读者主要关注DispatcherServlet组件和消息处理等组件的bean工厂配置即可。如果用户也想自定义去扩展mvc的相关配置,可自行去实现WebMvcConfigurer接口即可,样例如下
package com.example.demo.web.config;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.web.servlet.HandlerExceptionResolver;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;/**
* @author nanco
* -------------
* -------------
* @create 2018/9/5
**/public class BootWebMvcConfigurer implements WebMvcConfigurer { @Override
public void addInterceptors(InterceptorRegistry registry) {
} @Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
} @Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
}本文也讲述了如果用户想扩展相应的Filter或者Servlet,可使用FilterRegistrationBean/ServletRegistrationBean,样例如下
package com.example.demo.web.config;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.Servlet;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/**
* @author nanco
* -------------
* -------------
* @create 2018/9/5
**/@Configurationpublic class ServletFilterBeans { // only intercept /simple/
@Bean("simpleServlet") public ServletRegistrationBean<Servlet> simpleServlet() { return new ServletRegistrationBean<>(new SimpleServlet(), "/simple/");
} // intercept /simple、/simple/、/simple/ha etc.
@Bean("simpleFilter") public FilterRegistrationBean<Filter> simpleFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean<>();
bean.setFilter(new SimpleFilter());
bean.addUrlPatterns("/simple/*"); return bean;
} private static class SimpleServlet extends HttpServlet { @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doService path: " + req.getRequestURI()); super.doGet(req, resp);
}
} private static class SimpleFilter extends OncePerRequestFilter { @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("filter path: " + request.getRequestURI());
filterChain.doFilter(request, response);
}
}
}作者:南柯问天 出处:http://www.cnblogs.com/question-sky/ 本文版权归本人和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
随时随地看视频