承接前文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")
@ResponseBody
public 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集合中呢


No.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等请求。

温馨提示:此处只是注册了filter到bean工厂,并没有被注入至tomcat等web容器中,用户如果想支持上述的请求方法,可以考虑通过ServletRegistrationBean/FilterRegistrationBean来进行注入


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();
// 此处的静态资源映射主要针对前端的一些文件,比如jquery/css/html等等
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
**/
@Configuration
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
**/
@Configuration
public 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);
}
}
}

springboot情操陶冶-web配置(三)的更多相关文章

  1. springboot情操陶冶-web配置(四)

    承接前文springboot情操陶冶-web配置(三),本文将在DispatcherServlet应用的基础上谈下websocket的使用 websocket websocket的简单了解可见维基百科 ...

  2. springboot情操陶冶-web配置(九)

    承接前文springboot情操陶冶-web配置(八),本文在前文的基础上深入了解下WebSecurity类的运作逻辑 WebSecurityConfigurerAdapter 在剖析WebSecur ...

  3. springboot情操陶冶-web配置(七)

    参数校验通常是OpenApi必做的操作,其会对不合法的输入做统一的校验以防止恶意的请求.本文则对参数校验这方面作下简单的分析 spring.factories 读者应该对此文件加以深刻的印象,很多sp ...

  4. springboot情操陶冶-web配置(二)

    承接前文springboot情操陶冶-web配置(一),在分析mvc的配置之前先了解下其默认的错误界面是如何显示的 404界面 springboot有个比较有趣的配置server.error.whit ...

  5. springboot情操陶冶-web配置(一)

    承接前文springboot情操陶冶-@SpringBootApplication注解解析,在前文讲解的基础上依次看下web方面的相关配置 环境包依赖 在pom.xml文件中引入web依赖,炒鸡简单, ...

  6. springboot情操陶冶-web配置(八)

    本文关注应用的安全方面,涉及校验以及授权方面,以springboot自带的security板块作为讲解的内容 实例 建议用户可直接路由至博主的先前博客spring security整合cas方案.本文 ...

  7. springboot情操陶冶-web配置(六)

    本文则针对数据库的连接配置作下简单的分析,方便笔者理解以及后续的查阅 栗子当先 以我们经常用的mybatis数据库持久框架来操作mysql服务为例 环境依赖 1.JDK v1.8+ 2.springb ...

  8. springboot情操陶冶-web配置(五)

    本文讲讲mvc的异常处理机制,方便查阅以及编写合理的异常响应方式 入口例子 很简单,根据之前的文章,我们只需要复写WebMvcConfigurer接口的异常添加方法即可,如下 1.创建简单的异常处理类 ...

  9. springboot情操陶冶-@SpringBootApplication注解解析

    承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上对@SpringBootApplication注解作下简单的分析 @SpringBootApplicat ...

随机推荐

  1. acl权限命令

    1.查看acl命令 getfacl 文件名 #查看acl权限 2.设定acl权限命令 setfacl 选项 文件名 选项: -m 设置ACL权限 -x 删除指定的ACL权限 -b 删除所有的ACL设定 ...

  2. python学习:利用循环语句完善输入设置

    利用循环语句完善输入设置 使用for循环: 代码1:_user = "alex"_password = "abc123" for i in range(3): ...

  3. [LeetCode] Binary Gap 二进制间隙

    Given a positive integer N, find and return the longest distance between two consecutive 1's in the ...

  4. js 单行注释

    不可以: var a = 1;//这是注释 应当: var a = 1; //这是注释 1

  5. 2019年我的OKR(objectives and key results)目标与关键成果法

     一.学习目标目标1:每天必背诵英语单词(可可英语App,百词斩App),掌握英语的基本从句语法,听力训练必备(英语四六级听力题,主要是为通过四六级考试)目标2:考研准备,高数(大一上下册),现代(大 ...

  6. history.pushState()和history.replaceState()

    Html5 新增history对象的两个方法:history.pushState()和history.replaceState(),方法执行后,浏览器地址栏会变成你传的url,而页面并不会重新载入或跳 ...

  7. MyEclipse最新版-版本更新说明及下载 - MyEclipse官方中文网

    http://www.myeclipsecn.com/learningcenter/myeclipse-update/ [重要更新]MyEclipse 2015正式版发布 [重要更新]MyEclips ...

  8. Websocket实现即时通讯

    前言 关于我和WebSocket的缘:我从大二在计算机网络课上听老师讲过之后,第一次使用就到了毕业之后的第一份工作.直到最近换了工作,到了一家是含有IM社交聊天功能的app的时候,我觉得我现在可以谈谈 ...

  9. Android端IM应用中的@人功能实现:仿微博、QQ、微信,零入侵、高可扩展

    本文由“猫爸iYao”原创分享,感谢作者. 1.引言 最近有个需求:评论@人(没错,就是IM聊天或者微博APP里的@人功能),就像下图这样:   ▲ 微信群聊界面里的@人功能    ▲ QQ群聊界面里 ...

  10. SUSE12Sp3-kafka安装

    1.安装java jdk sudo mkdir -p /usr/local/java #创建目录 将jdk-8u201-linux-x64.tar.gz上传到该目录 cd /user/local/ja ...