SpringMVC的朴素解释
Spring MVC与Spring WebFlux都是基于MVC思想设计的web框架。不同的是Spring MVC是在J2EE的Servlet API基础上实现的,它本身依赖Tomcat或者Jetty这样的web容器(在Springboot项目中容器时内嵌到应用的)。而Spring WebFlux与Spring MVC虽然设计上是一样的,但是它并不依赖Servlet API,它是基于Netty实现的(相当于它自己实现了http协议)。本文假设你是个SpringMVC的老玩家。
MVC
MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式(来自百度百科)。应该说带界面的应用都应该走这样一个模式。混沌初开的时候大家写代码都是野路子,所有的逻辑杂糅在一起,后来代码腐化之后才会去思考该如何解决这样的问题。
Spring MVC
在SpringMVC中,view(视图)被抽象成了一个接口:View,它有一个重要的方法(实际上也没有那么重要,比较日常开发基本上犯不着自己自定义view):
public interface View {
/**
* 视图的content-type,估计只要在自定义视图的时候用得上,其他时候基本上都得动态确定。比如静态资源View就包括html、css、js三类
*/
@Nullable
default String getContentType() {
return null;
}
/**
* 渲染视图的方法。这里可以看到model是个map
*/
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
model(模型)也被抽象成了一个接口:Model,这个model与request、session的用法保持高度一致,都是通过addAttribute、getAttribute之类的方法添加和取出数据:
public interface Model {
Model addAttribute(String attributeName, @Nullable Object attributeValue);
Model addAttribute(Object attributeValue);
Model addAllAttributes(Collection<?> attributeValues);
Model addAllAttributes(Map<String, ?> attributes);
Model mergeAttributes(Map<String, ?> attributes);
boolean containsAttribute(String attributeName);
@Nullable
Object getAttribute(String attributeName);
Map<String, Object> asMap();
}
注:model的实现都是基于Map,分为俩类实现,基于LinkedHashMap(他被扩展成了一个ModelMap)和基于ConcurrentHashMap。
Controller(控制器)的表现形式的多种多样的,尽管SpringMVC提供了Controller接口(如下),但是一般情况下我们不会直接扩展这个接口,而是使用@Controller。
//这是一个函数式接口
@FunctionalInterface
public interface Controller {
@Nullable
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
注:Controller开发通常是业务开发的重点,其他的比如Model与View已经定型了。
故事要从DispatcherServletAutoConfiguration说起。是它将DispatcherServlet注入到容器的:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET) //servletweb应用
@ConditionalOnClass(DispatcherServlet.class) //DispatcherServlet存在
//ServletWebServerFactoryAutoConfiguration会做好服务器(tomcat)相关的定制处理(不是注入tomcat,是注入处理tomcat的bean)。
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
/**
* dispatcherServlet作为beanName,默认根路径是/
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class) // 会检查是否已经注入了dispatcherServlet为beanName的bean
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class) // properties配置
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
// 是否在每次请求处理完成后产生ServletRequestHandledEvent
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
// 是否在DEBUG级别记录请求参数log,以及在TRACE级别记录header log
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
}
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
// 前面的DispatcherServlet已经注入容器,DispatcherServletRegistrationBean的作用是将其注入到Tomcat服务器
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
}
注:你可以注入自己的dispatcherServlet以定制DispatcherServlet。这样默认的dispatcherServlet就不会生效了。DispatcherServletRegistrationBean的作用是将DispatcherServlet添加到Web容器(Tomcat)。
上文我们注入了一个DispatcherServlet,但是它目前不具备servlet功能,环境还没有准备好,配置也没有完成。ServletWebServerFactoryAutoConfiguration会导入EmbeddedTomcat(内嵌tomcat)到容器,通过EmbeddedTomcat将TomcatServletWebServerFactory注入到容器。ServletWebServerFactoryAutoConfiguration本身会将ServletWebServerFactoryCustomizer,TomcatServletWebServerFactoryCustomizer注入到容器,它们本质都是将server.XXXX的配置(对应的类是ServerProperties)配置到TomcatServletWebServerFactory,前者配置端口(server.port)、context-path等大的信息,后者配置tomcat相关的信息(server.tomcat.XXX)。上面这些工作是在注入DispatcherServlet之前完成的,这个时候TomcatServletWebServerFactory已经准备好了,但是这并不代表WebServer准备好了。
注:TomcatServletWebServerFactory是Tomcat的ServletWebServerFactory实现,其他容器也有对应的实现。
Tomcat是在ApplicationContext的onRefresh阶段创建的:
// ServletWebServerApplicationContext的方法
protected void onRefresh() {
super.onRefresh();
try {
// 创建tomcat服务器
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
// 创建服务器是具体实现
private void createWebServer() {
// 目前是null
WebServer webServer = this.webServer;
// 目前是null
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 在IOC有讲到的步骤记录器,可查看IOC文章
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
// 上文提到的TomcatServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
// 步骤记录器打标
createWebServer.tag("factory", factory.getClass().toString());
// 通过factory拿到WebServer
this.webServer = factory.getWebServer(getSelfInitializer());
// 解释记录
createWebServer.end();
。。。。。。。。。。。。。。。。后续注入WebServerGracefulShutdownLifecycle与WebServerStartStopLifecycle
}
}
注:getSelfInitializer()会获取到一个默认的ServletContextInitializer,这是个WebApplicationInitializer的springboot替代,springboot中的Servlet容器是交给Spring管理的,所以才会需要这么一个替代。DispatcherServlet就是通过ServletContextInitializer从spring容器添加到web容器的,具体实现是DispatcherServletRegistrationBean。这是内嵌Tomcat的一个重要的扩展点。下面是简单示例:
/**
接口源码
@FunctionalInterface
public interface ServletContextInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
*/
@Component
public class TestServletContextInitializer implements ServletContextInitializer {
//在此处处理上下文。
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("--------------TestServletContextInitializer.onStartup--------------------");
}
}
上文中我们知道DispatcherServlet与DispatcherServletRegistrationBean已经注入到Spring容器,而onRefresh()之后内嵌的Tomcat已经准备好了。现在我们需要知道是什么时候将DispatcherServlet添加到Tomcat的。
//TomcatServletWebServerFactory.getWebServer()
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// 得到一个Tomcat
Tomcat tomcat = new Tomcat();
// 工作目录
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 下面的配置对应tomcat的xml配置文件的各个部分,这个和MyBatis的Configuration类配置有异曲同工之妙
// 对应<connector>配置
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
// 对应<server>下面的<service>
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
// 对应Host
tomcat.getHost().setAutoDeploy(false);
// 对应Engine
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 此步将ServletContextInitializer放入了
prepareContext(tomcat.getHost(), initializers);
// 此步获取WebServer同时会执行ServletContextInitializer.onStartup
return getTomcatWebServer(tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
。。。。。。。。。前面一大堆准备工作
// 配置到WEB容器
configureContext(context, initializersToUse);
。。。。。
}
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
// 委托给这个TomcatStarter(一个也是ServletContextInitializer)
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
// 放入到Web容器(Tomcat)里面
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
。。。。。。。。。。。。。。。。。。。。。。。
}
// 获取WebServer
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
....................前面一堆准备工作
// 这一步会启动tomcat
initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
。。。。。。。。。。。。。。。。略
// 此步start()会将tomcat启动,前面配置进去的TomcatStarter.onStartup将会被执行
this.tomcat.start();
...............................略
}
到这一步,基本上就已经将DispatcherServlet添加到Tomcat的走完了。
注:前文中getSelfInitializer()会返回一个Lambda表达式,实际调用onStartup()调用的是selfInitialize()。Spring通过这种方式将Web容器与Spring容器关联了起来。如下:
// ServletContext是Web容器抽象,而this是Spring容器
private void selfInitialize(ServletContext servletContext) throws ServletException {
。。。。。。。。。。。。。其他步骤,比如设置容器层级关系等等
// 这一步会获取到Spring容器中的ServletContextInitializer,然后依次执行他们的onStartup方法。
// DispatcherServletRegistrationBean和我们自定义的ServletContextInitializer在此处获取并执行。
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
注: Context层级关系请点击这里
到此,DispatcherServlet就已经完成了它的注入过程。SpringMVC所有的请求都经过DispatcherServlet分发处理。上文已经阐述了SpringMVC对MVC设计的基本组件,而为了组合与处理这些组件SpringMVC提供了另外一些组件。以下是DispatcherServlet的结构:
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
/**处理content-type为multipart/form-data的数据 */
private MultipartResolver multipartResolver;
/** 国际化相关 */
private LocaleResolver localeResolver;
/** 主题相关,实际上没什么用 */
private ThemeResolver themeResolver;
/** 分发请求 */
private List<HandlerMapping> handlerMappings;
/** 请求处理器适配器,帮助DispatchServlet调用handler */
private List<HandlerAdapter> handlerAdapters;
/** 异常处理 */
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** 从请求中转换提取处viewName */
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMap管理器,是一种服务的的重定向存储信息的技术,没太大用 */
private FlashMapManager flashMapManager;
/** 视图解析器,通过viewName和locale获取到对应的view实例*/
private List<ViewResolver> viewResolvers;
}
Servlet是懒加载的,在第一次请求到来之后才会初始化,这也就意味着第一次请求到来之前DispatcherServlet的这些属性是还未初始化完成的。
MultipartResolver:formdata解析器Spring提供有两种:CommonsMultipartResolver和StandardServletMultipartResolver前者使用Commons-upload组件处理,后者使用Servlet3.0的Part接口处理,formdata一般使用在文件上传的时候。默认使用StandardServletMultipartResolver。关于formdata可看FormData解析。
public interface MultipartResolver {
boolean isMultipart(HttpServletRequest request);
// 将普通的request包装成MultipartHttpServletRequest,本质是将各个部分拿出来解析后进行再次封装。
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
void cleanupMultipart(MultipartHttpServletRequest request);
}
LocaleResolver/LocaleContextResolver:解析请求获取到Locale信息,后者实现可能会带有时区信息。常用CookieLocaleResolver、AcceptHeaderLocaleResolver、SessionLocaleResolver。默认使用AcceptHeaderLocaleResolver,使用accept-language请求头设置语言,AcceptHeaderLocaleResolver不支持设置时区。
public interface LocaleResolver {
// 解析request,获取Locale信息
Locale resolveLocale(HttpServletRequest request);
// 动态设置Locale信息,绝大多时候不应该在一次请求处理过程中动态变更Locale信息
void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
}
注:配置LocaleChangeInterceptor拦截器会拦截并设置Locale信息。它会拦截某个特定的字段名(你配置的,默认是locale)。
ThemeResolver:在前后端分离的现在,ThemeResolver确实没什么卵用。这里就不提了。
HandlerMapping:HandlerMapping的作用是为一个request找到合适的HandlerExecutionChain,它包括了一个Handler(Controller)和一个HandlerInterceptor的list。默认会注册以下几个mapping:
- RequestMappingHandlerMapping:此HandlerMapping将会从@Controller和@RequestMapping系列注解注册的Handler里面选择适合该request的。
- BeanNameUrlHandlerMapping:会在beanName为“/”开头的bean里面选择Handler。
- RouterFunctionMapping:是对函数式Controller的实现,会从用户注入RouterFunction bean里面寻找。
- SimpleUrlHandlerMapping:内部存有一个urlMap属性,存储url与handler映射,这个映射是由用户配置的。静态资源是由这个它映射的。
WelcomePageHandlerMapping:springboot特地带上的,映射index页,页没什么卵用。
public interface HandlerMapping { /** 根据request找到最合适的HandlerExecutionChain,找不到则为null。可以有多个HandlerMapping实现,全部都找不到则为异常 */ HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; }
注:全部的Handler的匹配规则都是与url路径相关的。匹配器有两种:AntPathMatcher、PathPattern。
HandlerAdapter:各种Handler的执行方式是不一样的,比如Controller接口和HttpRequestHandler是执行handleRequest方法,HandlerFunction(函数式接口)是执行handle方法,Servlet是执行service方法,而基于注解的得使用method去执行。为了适配这些情况,需要为每种类型的Handler写个HandlerAdapter以统一执行逻辑。默认会注册以下几个:- RequestMappingHandlerAdapter:@Controller和@RequestMapping系列注解的执行适配器,它是最复杂的。
- HandlerFunctionAdapter:执行HandlerFunction(从RouterFunction里面获取的)。
- SimpleControllerHandlerAdapter、HttpRequestHandlerAdapter:分别执行Controller接口和HttpRequestHandler接口
public interface HandlerAdapter {
//是否支持该类型的handler
boolean supports(Object handler);
//执行handler
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
// 为浏览器缓存准备的,以便一些静态资源可以快速响应
long getLastModified(HttpServletRequest request, Object handler);
}
注:执行是个复杂的过程,因为参数绑定等等操作是在这个过程中处理的。
HandlerExceptionResolver:处理执行过程中的异常,默认会有两个:DefaultErrorAttributes、HandlerExceptionResolverComposite,前者是默认的自定义错误页(html返回html,json返回json大家都见过),后者是一个HandlerExceptionResolver组合,它会将处理委托给其他的HandlerExceptionResolver。使用了组合模式,与WebMvcConfigurerComposite的做法是一致的。
public interface HandlerExceptionResolver {
//处理异常
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
RequestToViewNameTranslator:spring只提供了一个默认实现DefaultRequestToViewNameTranslator,只是对请求路径做去斜杠掐头去尾。实际上它这样也已经够用了。
public interface RequestToViewNameTranslator {
// 得到viewName
String getViewName(HttpServletRequest request) throws Exception;
}
FlashMapManager:Spring只提供了一个实现SessionFlashMapManager,FlashMap是重定向存储数据的一种处理方式,它会将数据存储在session里面,使用一次之后就会被删除。重定向有两种存储数据的方式,1是存url,2是存session。我个人认为能1就1,比较FlashMap挺局限的。
public interface FlashMapManager {
// 取数据
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
// 存数据,session或者cookie
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}
ViewResolver:上文的LocaleResolver与RequestToViewNameTranslator分别获取到了Locale与viewName信息,通过这些信息可以获取到对应的view。默认会注入以下几个实现:
- BeanNameViewResolver:将viewName作为beanName寻找View对象
- ContentNegotiatingViewResolver:请求文件名(viewName+.+扩展名)或接受头(Accept)来获取View。
- InternalResourceViewResolver:jsp与servlet。
- ViewResolverComposite:一个组合,实际上会将解析工作委托给其他ViewResolver实现。
在前后端分离的今天后端视图技术已经被抛弃的差不多了。
public interface ViewResolver {
// 通过viewName与Locale信息得到一个View
View resolveViewName(String viewName, Locale locale) throws Exception;
}
注:DispatcherServlet默认策略接口实现配置在/org/springframework/web/servlet/DispatcherServlet.properties
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
SpringBoot通过WebMvcAutoConfiguration自动配置组件:Springboot在处理EnableWebMvcConfiguration,WebMvcAutoConfigurationAdapter这两个config类的过程中将SpringMVC必要的组件注入到容器,在第一次请求到来时,Servlet初始化过程中将Spring容器里面的组件放到DispatcherServlet。
public class WebMvcAutoConfiguration {
....................
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class,
org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
@Order(0)
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
//
}
。。。。。。。。。。。。。。。。。。
}
.................
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
public EnableWebMvcConfiguration(
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebMvcProperties mvcProperties, WebProperties webProperties,
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ListableBeanFactory beanFactory) {
//
}
。。。。。。。。。。。。。。。。。。
}
。。。。。。。。。。。。。。。。。。。。
}
首先实例化EnableWebMvcConfiguration,各种Properties参数不再赘述,它们就是一些设置,在些springboot配置文件的时候可以配置进去,WebMvcRegistrations接口如下:
public interface WebMvcRegistrations {
default RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return null;
}
default RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return null;
}
default ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() {
return null;
}
}
它是一个重要的扩展点,可以使用自定义的RequestMappingHandlerMapping、RequestMappingHandlerAdapter和ExceptionHandlerExceptionResolver替代默认,但是也确实没必要这样搞。这三个类的实现都是比较繁琐的,万不得已的时候可以继承该这几个类重写自定义逻辑,然后写个WebMvcRegistrations注入到Spring容器。
ResourceHandlerRegistrationCustomizer接口是包权限的,无法扩展,没什么卵用。beanFactory的注入让这个EnableWebMvcConfiguration可以搜索Spring容器。EnableWebMvcConfiguration是个DelegatingWebMvcConfiguration他有个重要的方法:
//实际的WebMvcConfigurer执行工作交由它处理
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//在实例化完成的后会搜索WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
DelegatingWebMvcConfiguration继承WebMvcConfigurationSupport,WebMvcConfigurationSupport做了大量的准备工作,内部属性保存有HandlerMethodArgumentResolver、HandlerMethodReturnValueHandler等等在Handler执行时需要使用的组件。这些组件要么是来自Spring的默认实现,要么是通过WebMvcConfigurer获取的,上文中的WebMvcAutoConfigurationAdapter也是一个WebMvcConfigurer,构造参数HttpMessageConverters帮助拿到HttpMessageConverter,ServletRegistrationBean和DispatcherServletPath主要用来配置configurePathMatch如下:
public void configurePathMatch(PathMatchConfigurer configurer) {}
上文中已经将各种帮助类及配置的存入到以下几个类的属性之中了:WebMvcAutoConfigurationAdapter、EnableWebMvcConfiguration(也就是DelegatingWebMvcConfiguration),而调用WebMvcConfigurer、WebMvcRegistrations方法等帮助类的各种方法的时机是在@Bean过程中,因为WebMvcAutoConfigurationAdapter与EnableWebMvcConfiguration都是一个@Configuration标识的类。这里有个例子:
//获取requestMappingHandlerAdapter
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
// 使用默认或者WebMvcRegistrations里面自定义的RequestMappingHandlerAdapter
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(contentNegotiationManager);
// 最终会委托给WebMvcConfigurerComposite获取MessageConverter。
// WebMvcConfigurerComposite会前文中注入的WebMvcConfigurer的所有MessageConverter相关的方法都执行一遍
adapter.setMessageConverters(getMessageConverters());
// getMessageConverters一样的操作
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
// getMessageConverters一样的操作
adapter.setCustomArgumentResolvers(getArgumentResolvers());
// getMessageConverters一样的操作
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
// getMessageConverters一样的操作
AsyncSupportConfigurer configurer = getAsyncSupportConfigurer();
// TaskExecutor在异步处理请求的时候会使用到
if (configurer.getTaskExecutor() != null) {
adapter.setTaskExecutor(configurer.getTaskExecutor());
}
if (configurer.getTimeout() != null) {
adapter.setAsyncRequestTimeout(configurer.getTimeout());
}
// 异步处理请求的拦截器是特别的
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
return adapter;
}
执行帮助类的过程都是像这个掺杂在@Bean过程中的。
到此,所有的DispatcherServlet依赖的Bean都已经注入到容器之中,在第一次请求到来的时候,Servlet将会初始化(调用init方法),最终会调用initStrategies方法将必要的Bean注入到容器里面:
// 这个onRefresh是FrameworkServlet定义的,非Spring容器的方法
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// 获取依赖组件
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
注:前文中Tomcat最重要的扩展点是ServletContextInitializer。而SpringMVC最重要的扩展点是WebMvcConfigurer,这是因为SpringMVC的其他部分都比较稳定基本上可以满足我们的所有需求,但是比如消息转换、类型转换等等我们可能会有独特的需求。
public interface WebMvcConfigurer {
// 路径匹配的时候会用上这里的配置
default void configurePathMatch(PathMatchConfigurer configurer) {}
//配置内容协商,本质是配置一个映射,从什么地方确定request能够接受的content-type(请求头、参数还是资源扩展名等等)。
// 它有俩个部分,一部分是像configurer.mediaType("json", MediaType.APPLICATION_JSON);这样配置映射
// 另一部分配置ContentNegotiationStrategy request解析策略。spring提供了多种策略,基本上够用了。
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
//异步请求相关,包括执行器、超时时间、拦截器等等等等,在后文会讲到
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
// 配置另一个Servlet,这个Servlet会在DispatcherServlet无法处理请求的时候被调用(forward形式),没什么用
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
// 注册Formatter、Converter、Parser或者Printer;全是转换器,在类型转换的时候会用到。
default void addFormatters(FormatterRegistry registry) {}
// 注册拦截器
default void addInterceptors(InterceptorRegistry registry) {}
//添加额外的静态资源位置
default void addResourceHandlers(ResourceHandlerRegistry registry) {}
//配置全局跨域
default void addCorsMappings(CorsRegistry registry) {}
//配置无逻辑的Controller,重定向或者跳到某个页面,比如404,500等等等等,只需要结果简单的配置就可完成
default void addViewControllers(ViewControllerRegistry registry) {}
// 配置ViewResolver视图解析器
default void configureViewResolvers(ViewResolverRegistry registry) {}
// 添加参数解析器,在参数绑定的时候会用到
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}
// 添加一个返回值处理器
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}
// 配置HttpMessageConverter
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
// 配置HttpMessageConverter
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
// 异常处理器
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
// 异常处理器
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
// 自定义Validator,后面会提到
default Validator getValidator() {}
// 参数验证错误的解析器
default MessageCodesResolver getMessageCodesResolver() {}
}
一个简单的例子:配置日期自定义格式
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream().filter(i -> i instanceof MappingJackson2HttpMessageConverter).forEach(i -> {
MappingJackson2HttpMessageConverter converter = (MappingJackson2HttpMessageConverter) i;
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().indentOutput(true)
.simpleDateFormat("yyyy-MM-dd HH:mm:ss").modulesToInstall(new ParameterNamesModule());
converter.setObjectMapper(builder.build());
});
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
}
}
到此,从tomcat到dispatcherServlet到SpringMVC的初始化全过程就完了。上文提到了一堆的新接口,比如Converter、HttpMessageConverter、HandlerExceptionResolver、HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler等等等等都是可能用到的,本文也许不会有例子,但是它们确实非常重要,能够帮助我们更好的扩展SpringMVC。
请求处理过程
前文中SpringMVC已经初始化完成。我们现在需要知道请求的处理过程以及各个组件时如何被应用上去的。Servlet通过service方法处理请求:
// FrameworkServlet.service()主要是为了支持PATCH类型的请求,其他类型的请求都被在HttpServlet被处理了。
// FrameworkServlet是DispatcherServlet的父类
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
//实际上所有的请求都被分发到了这个方法
processRequest(request, response);
}
else {
//HttpServlet做的事情实际上就是将GET类型的请求分发到doGet方法,以此类推。
super.service(request, response);
}
}
// 这是一个例子,表示所有的请求最后都是交给processRequest方法处理的
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
请求首先被service方法处理,之后都会交给processRequest方法处理:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//记录请求处理起始时间
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//此步应用到了LocaleContextResolver/LocaleResolver
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// 处理异步请求相关,WebAsyncManager存入request
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 将locale信息与request信息存入LocaleContextHolder与RequestContextHolder
initContextHolders(request, localeContext, requestAttributes);
try {
//处理请求
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
// 清除Holder里面的数据
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
// 标为已完成,清除临时session属性
requestAttributes.requestCompleted();
}
// 记录日志,debug或者trace级别
logResult(request, response, failureCause, asyncManager);
// 发布事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
// DispatcherServlet.buildLocaleContext(),使用到了locale,不过还没有调用resolveLocale()
@Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
LocaleResolver lr = this.localeResolver;
if (lr instanceof LocaleContextResolver) {
return ((LocaleContextResolver) lr).resolveLocaleContext(request);
}
else {
return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
}
}
上下文及环境已经准备好了,doService正式处理请求:
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 如果配置了log且为debug或者trace级别,则记录日志(包括参数,头,路径,请求方式等等详细信息)
logRequest(request);
// 处理include请求(include、forward、redirect)。存一份属性以便恢复。
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// 随时可能被使用的组件,存入到request属性中:ApplicationContext
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
// themeResolver使用到了
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
// flashMapManager/FlashMap使用到了,存入了request
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
// 路径解析,也存入了request。
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
try {
// 处理请求
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// 恢复request Attributes
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
// 缓存RequestPath,存入request
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
注:早期的路径匹配时使用AntPathMatcher现在用的时PathPattern。
本次请求可能用到工具全部存入了request,为下一步doDispatch做好了准备:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
// 取出WebAsyncManager
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 如果时form-data类型则转换成MultipartHttpServletRequest,此处用到了MultipartResolver
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 通过循环所有HandlerMapping,获取合适的HandlerExecutionChain。此处用到了HandlerMapping
mappedHandler = getHandler(processedRequest);
// 没找到则给个异常或者直接返回(响应),取决于配置throwExceptionIfNoHandlerFound
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 找到循环所有的HandlerAdapter找到合适的HandlerAdapter。此处用到了HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 与缓存相关,如果LastModified没变则标识该资源没变
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 应用前置拦截器,此处用到了HandlerInterceptor
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 处理请求,也就是调用我们自己写的逻辑
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果异步请求则直接返回等待后续其他线程处理完成
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 设置viewName到ModelAndView,此处用到了RequestToViewNameTranslator
applyDefaultViewName(processedRequest, mv);
// 应用后置拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 此步包含了多个步骤:首先有异常则处理异常(用到了HandlerExceptionResolver)
// 然后无异常则渲染view,包括以下处理:获取Locale获取View,View渲染;用到了(LocaleResolver、ViewResolver和View)
// 最后触发处理完成拦截器 interceptor.afterCompletion
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 触发处理完成拦截器 interceptor.afterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
//触发处理完成拦截器 interceptor.afterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// 异步请求应用AsyncHandlerInterceptor拦截器
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// 清除Multipart缓存
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
大致流程就是上面这样,就是HandlerAdapter处理请求的过程,最复杂的就是RequestMappingHandlerAdapter的处理请求过程,因为其他类型的请求参数与返回值大多是确定的,请求HandlerAdapter.handle()会流转到RequestMappingHandlerAdapter.handleInternal:
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// 检查方式是否支持、session是否支持,不支持则异常
checkRequest(request);
// 如果需要同步执行则同步执行,保证一个session下的多个request不会混乱
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 无session没同步的必要
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 不需要同步则立即执行
mav = invokeHandlerMethod(request, response, handlerMethod);
}
// 浏览器缓存相关的设置,如果处理器设置了Cache-Control头的话
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
// 执行handler
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 包装请求
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 取initBinder(),先全局(@ControllerAdvice的@InitBinder),再局部(@Controller的@InitBinder)
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
// ModelFactory 先全局(@ControllerAdvice的@ModelAttribute),再局部(@Controller的@ModelAttribute)
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//初始化ServletInvocableHandlerMethod;
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
//放入HandlerMethodArgumentResolver
//是个组合模式,最终会交给放入具体HandlerMethodArgumentResolver处理参数
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
//和上面的HandlerMethodArgumentResolver一个处理方式
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 上面的DataBinderFactory
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 结果容器
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
//FlashMap放入,model
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// 包装一个异步请求
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
// 执行器,拦截器等等等等异步请求需要的东西
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
// 处理上个异步结果
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 执行且处理结果
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// 是并发则直接返回(不需要结果)
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 清除缓存,包装结果
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
// 标为已完成,清除缓存
webRequest.requestCompleted();
}
}
ServletInvocableHandlerMethod.invokeAndHandle()方法内部为目标方法的每个参数都寻找到合适的HandlerMethodArgumentResolver并执行解析(通常与加在参数上的注解有关比如@RequestParam、@RequestBody等等,默认无注解情况下BeanUtils.isSimpleProperty为true走的是@RequestParam,否则走的是@ModelAttribute;HandlerMethodArgumentResolver里面会使用MessageConverter,Formatter,InitBinder等等工具),解析完成后会取出目标方法然后执行方法,执行完成后设置status,然后交给HandlerMethodReturnValueHandler处理返回结果,HandlerMethodReturnValueHandler的选择与HandlerMethodReturnValueHandler的实现有关,这个过程也是可能会使用到各种转换工具。
高级特性
异步处理请求:
异步处理请求有一大操作方式,CompletableFuture
@GetMapping("/hello3")
public Callable<UserInfo> hello3(Date date) {
System.out.println("-------a------");
return ()->{
Thread.currentThread().sleep(5000);
System.out.println("-------b------");
return new UserInfo("eric", date);
};
}
所有的异步本质都是通过request.startAsync()做到的(Servlet3.0特性)。
参数校验:
这个问题再业务开发四大噩梦——无处不在的参数校验有阐述,引入hibernate-validator依赖后可以自动校验参数。
你可以自定义Validator,但是通常会不够通用。
自定义注解:
@RequestMapping是可以自定义的,@RequestMapping的方法是交给RequestMappingHandlerMapping和RequestMappingHandlerAdapter处理的,不用配套的处理器也能够直接使用:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping
public @interface ExampleMapping {
@AliasFor(annotation = RequestMapping.class)
String name() default "";
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
@AliasFor(annotation = RequestMapping.class)
String[] params() default {};
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {};
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {};
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {};
}
@RestController
public class TestExampleController {
//使用自定义注解
@ExampleMapping("/example")
public String hello(String name) {
return "hello "+name;
}
}
同理,当需要自定义处理参数与返回值逻辑的时候你也可以自定义注解:
// 定义注解
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ExampleTarget {}
// 配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 处理参数
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new HandlerMethodArgumentResolver() {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasMethodAnnotation(ExampleTarget.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getParameter("name");
}
});
}
// 处理返回值
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
handlers.add(new HandlerMethodReturnValueHandler() {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return returnType.hasMethodAnnotation(ExampleTarget.class);
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
mavContainer.setRequestHandled(true);
webRequest.getNativeResponse(HttpServletResponse.class).getWriter().write(((UserInfo)returnValue).getName());
}
});
}
}
//应用
@Controller
public class TestExampleController {
@ExampleMapping("/example")
@ExampleTarget
public UserInfo hello(@ExampleTarget String name) {
return new UserInfo("hello "+name, new Date());
}
}
@InitBinder、@ModelAttribute、@、@ControllerAdvice、@Controller:
@ControllerAdvice/@RestControllerAdvice注解的类的生命周期要比@Controller/@RestController的作用域更大一些,@ControllerAdvice/@RestControllerAdvice在不配置的情况下会横跨所有的@Controller/@RestController,相当于为所有的Controller做了切面,当然你也可以设置@ControllerAdvice/@RestControllerAdvice的作用范围,在你需要为所有Controller做前置操作,异常处理的时候可以使用:
@RestControllerAdvice
public class WebConfig {
// 这个绑定会应用到所有controller
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
}
// ModelAttribute 可以在所有Controller参数里被取出
@ModelAttribute("userInfo")
public UserInfo info() {
return new UserInfo("username",new Date());
}
// 处理Controller异常
@ExceptionHandler
public String handleExeception(Exception e) {
return e.getMessage();
}
}
// 应用
@RestController
public class Test2Controller {
//应用
@GetMapping("/date2")
public UserInfo date(Date date,@ModelAttribute("userInfo") UserInfo userInfo) {
int i=1/0;
return userInfo;
}
}
请求事件处理:
前文提到每个请求处理完成之后都会发布一个处理完成的事件,你可以写个监听器处理这个事件:
@EventListener
public void handlerRequestEvent(ServletRequestHandledEvent event) {
System.out.println("--------------------");
}
它异步的,你可以用它记录日志,也可以用它做一个请求之后的后续操作,比如用户登录后为这个用户发送登录礼包。
函数式:
前文提到的RouterFunctionMapping就是处理这种类类型的Controller而设计的,通常我们使用RouterFunctions.route()进行流式处理就行了,如下:
@Bean
public RouterFunction<ServerResponse> routeFunction(){
return route()
.GET("/person/{id}", accept(APPLICATION_JSON), request -> ServerResponse.ok().body(new UserInfo("eric", new Date())))
.GET("/person", accept(APPLICATION_JSON), request -> ServerResponse.ok().body(new UserInfo("eric", new Date())))
.POST("/person", request -> ServerResponse.ok().body("hello world"))
.build();
}
只适合简单情况,复杂的Controller业务处理对于这种方式来说太复杂了,而且会很混乱。
WebSocket:
基于Spring后端websocket
总结
原本设计只有View、Model、Controller;而为了实现这些需求SpringMVC引入了HandlerMapping,HandlerAdapter等等概念,为了适配业务需求添加了Locale,Theme,TimeZone等等功能,为了参数绑定、返回值处理等等又添加了Converter,Formatter,HandlerMethodArgumentResolver,HandlerMethodReturnValueHandler等等一堆东西,为了实现异步又添加了一堆东西……
总的来说SpringMVC全流程是这样的:Springboot在启动的时候通过自动配置将Tomcat(ServletWebServerFactoryAutoConfiguration)与DispatcherServlet(DispatcherServletAutoConfiguration)注入到容器,同时也将SpringMVC的定义或者依赖的组件通过WebMvcAutoConfiguration注入到了Spring容器,Spring容器onRefresh阶段内嵌服务器会创建好,同时将DispatcherServlet配置到WebServer,这样启动就完成了;之后当第一个请求到来的时候DispatcherServlet将会被初始化(Servlet.init()),最终会调用DispatcherServlet.initStrategies()从Spring容器中拿到依赖组件并初始化完成,此时DispatcherServlet已经准备好处理请求了,接受到请求会调用Servlet.service()方法最终会到DispacherServlet.doService()方法,这个方法会做些分发准备工作然后调用DispacherServlet.doDispatch()分发请求,首先会尝试将HttpServletRequest转换成MultipartHttpServletRequest,然后通过HandlerMapping获取能够处理该请求的Handler,再然后就是找到能够执行该Handler的HandlerAdapter,执行前置拦截器,HandlerAdapter执行Handler处理请求,执行后置拦截器,执行已完成拦截器;HandlerAdapter处理Handler是一个复杂的过程,包括一系列的参数绑定、消息转换、本地化、类型/格式转换、返回值处理等操作。
在百分之99的情况下,我们实现WebMvcConfigurer就能很好的扩展Spring MVC以满足我们的业务需求,也就是说上面的绝大多是知识都是一种吃力不讨好的情况。
注:转载请标明作者与出处。如有意见或建议请评论留言。