摘要
Spring Web MVC是基于Servlet API构建的原始Web框架,基于servlet3.0的规范实现的web框架。spring mvc主要实现请求的接受,请求解析,请求响应等步骤。本文将详细的介绍spring MVC的源码和spring MVC的启动流程与相关原理。
一、传统spring MVC执行流程
- 用户发送请求至前端控制器DispatcherServlet;
- DispatcherServlet收到请求后,调用处理器映射器HandlerMapping,请求获取Handle;
- 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给前端控制器DispatcherServlet;
- DispatcherServlet通过处理器适配器HandlerAdapte调用处理器Handle;
- 执行处理器(Handler,也叫后端控制器,需要程序员做处理);
- 处理器Handler执行完成返回ModelAndView;
- 处理器适配器HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
- DispatcherServlet将ModelAndView传给视图解析器ViewReslover进行解析;
- 视图解析器ViewReslover解析后返回具体View;
- DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
上面就是一个传统的完整的 Spring MVC
流程,为什么要说这是传统的流程呢?因为这个流程是用于前后端没有分离的时候,后台直接返回页面给浏览器进行渲染,而现在大部分应用都是前后端分离,后台直接生成一个 Json
字符串就直接返回前端,不需要经过视图解析器进行处理。
二、前后端分离的spring MVC执行流程
Spring MVC主要可以分为两大过程,一是初始化,二就是处理请求。初始化的过程主要就是将我们定义好的 RequestMapping
映射路径和 Controller
中的方法进行一一映射存储,这样当收到请求之后就可以处理请求调用对应的方法,从而响应请求。
2.1 spring mvc的初始化
初始化:初始化过程的入口方法是 DispatchServlet
的 init()
方法,而实际上 DispatchServlet
中并没有这个方法,所以我们就继续寻找父类,会发现 init
方法在其父类(FrameworkServlet)的父类 HttpServletBean
中。
HttpServletBean:init()方法
在这个方法中,首先会去家在一些 Servlet 相关配置(web.xml),然后会调用 initServletBean()
方法,这个方法是一个空的模板方法,业务逻辑由子类 FrameworkServlet
来实现。
public final void init() throws ServletException {
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 定义资源
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 加载servlet相关配置
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (BeansException var4) {
if (this.logger.isErrorEnabled()) {
this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
}
throw var4;
}
}
// 核心方法 由子类FrameworkServlet实现
this.initServletBean();
}
FrameworkServlet-initServletBean()
这个方法本身没有什么业务逻辑,主要是初始化 WebApplicationContext
对象,WebApplicationContext
继承自 ApplicationContext
,主要是用来处理 web
应用的上下文。
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
* {@link HttpServletBean} 的重写方法,在设置任何 bean 属性后调用。创建此 servlet 的 WebApplicationContext。
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
FrameworkServlet-initWebApplicationContext()
initWebApplicationContext()
方法主要就是为了找到一个上下文,找不到就会创建一个上下文,创建之后,最终会调用方法configureAndRefreshWebApplicationContext(cwac)
方法,而这个方法最终在设置一些基本容器标识信息之后会去调用 refresh()
方法。这个就是初始化IOC容器。
protected WebApplicationContext initWebApplicationContext() {
// 从serlvetContent 中获取父类容器 webApplicationContext()
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
// 声明一个子容器
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
// 调用ioc容器的refresh()方法来初始化ioc信息
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
当调用 refresh()
方法初始化 ioc
容器之后,最终会调用方法 onRefresh()
,这个方法也是一个模板钩子方法,由子类实现,也就是回到了我们 Spring MVC
的入口类 DispatcherServlet
。
DispatchServlet-onRefresh()
onRefresh()
方法就是 Spring MVC
初始化的最后一个步骤,在这个步骤当中会初始化 Spring MVC
流程中可能需要使用到的九大组件。
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* 初始化的九大组件
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);//初始化多文件上传组件
initLocaleResolver(context);// 初始化本地语言环境
initThemeResolver(context);// 初始化模板处理器
initHandlerMappings(context);// 初始化 HandlerMappings
initHandlerAdapters(context);// 初始化HandlerAdapters
initHandlerExceptionResolvers(context);// 初始化异常拦截器
initRequestToViewNameTranslator(context);// 初始化视图预处理器
initViewResolvers(context);// 初始化视图转化器
initFlashMapManager(context);// 解决用户重的定向是参数丢失问题
}
2.1.1 spring MVC的九大组件
MultipartResolver:这个组件比较熟悉,主要就是用来处理文件上传请求,通过将普通的 Request
对象包装成 MultipartHttpServletRequest
对象来进行处理。
LocaleResolver:LocaleResolver
用于初始化本地语言环境,其从 Request
对象中解析出当前所处的语言环境,如中国大陆则会解析出 zh-CN
等等,模板解析以及国际化的时候都会用到本地语言环境。
ThemeResolver:这个主要是用户主题解析,在 Spring MVC
中,一套主题对应一个 .properties
文件,可以存放和当前主题相关的所有资源,如图片,css样式等。
HandlerMapping:用于查找处理器(Handler
),比如我们 Controller
中的方法,这个其实最主要就是用来存储 url
和 调用方法的映射关系,存储好映射关系之后,后续有请求进来,就可以知道调用哪个 Controller
中的哪个方法,以及方法的参数是哪些。
HandlerAdapter:这是一个适配器,因为 Spring MVC
中支持很多种 Handler
,但是最终将请求交给 Servlet
时,只能是 doService(req,resp)
形式,所以 HandlerAdapter
就是用来适配转换格式的。
HandlerExceptionResolver:这个组件主要是用来处理异常,不过看名字也很明显,这个只会对处理 Handler
时产生的异常进行处理,然后会根据异常设置对应的 ModelAndView
,然后交给 Render
渲染成页面。
RequestToViewNameTranslator:这个主键主要是从 Request
中获取到视图名称。
ViewResolver:这个组件会依赖于 RequestToViewNameTranslator
组件获取到的视图名称,因为视图名称是字符串格式,所以这里会将字符串格式的视图名称转换成为 View
类型视图,最终经过一系列解析和变量替换等操作返回一个页面到前端。
FlashMapManager:这个主键主要是用来管理 FlashMap
,那么 FlashMap
又有什么用呢?要明白这个那就不得不提到重定向了,有时候我们提交一个请求的时候会需要重定向,那么假如参数过多或者说我们不想把参数拼接到 url
上(比如敏感数据之类的),这时候怎么办呢?因为参数不拼接在 url
上重定向是无法携带参数的。FlashMap
就是为了解决这个问题,我们可以在请求发生重定向之前,将参数写入 request
的属性 OUTPUT_FLASH_MAP_ATTRIBUTE
中,这样在重定向之后的 handler
中,Spring
会自动将其设置到 Model
中,这样就可以从 Model
中取到我们传递的参数了。
2.2 spring mvc的请求处理
在九大组件初始化完成之后,Spring MVC
的初始化就完成了,接下来就是接收并处理请求了,那么处理请求的入口在哪里呢?处理请求的入口方法就是 DispatcherServlet
中的 doService
方法,而 doService
方法又会调用 doDispatch
方法。
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
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));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
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);
}
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()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 检查是否有文件上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//获取处理器,也就是controller中的方法
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 获取当前请求的处理程序适配器。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 实际上调用处理程序。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
这个方法最关键的就是调用了 getHandler
方法,这个方法就是会获取到前面九大组件中的 HandlerMapping
,然后进行反射调用对应的方法完成请求,完成请求之后后续还会经过视图转换之类的一些操作,最终返回 ModelAndView
,不过现在都是前后端分离,基本也不需要用到视图模型,在这里我们就不分析后续过程,主要就是分析 HandlerMapping
的初始化和查询过程。
DispatcherServlet-getHandler()
这个方法里面会遍历 handllerMappings
,这个 handllerMappings
是一个 List
集合,因为 HandlerMapping
有多重实现,也就是 HandlerMapping
不止一个实现,其最常用的两个实现为 RequestMappingHandlerMapping
和 BeanNameUrlHandlerMapping
。
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍历的map 中的Handler处理 HandlerMapping 中有多重实现 多重类型
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
AbstractHandlerMapping-getHandler()
AbstractHandlerMapping
是一个抽象类,其 getHandlerInternal
这个方法也是一个模板方法:
@Nullable
public Object getDefaultHandler() {
return this.defaultHandler;
}
getHandlerInternal
方法最终其会调用子类实现,而这里的子类实现会有多个,其中最主要的就是 AbstractHandlerMethodMapping
和 AbstractUrlHandlerMapping
两个抽象类,那么最终到底会调用哪个实现类呢?这时候如果拿捏不准我们就可以看一下类图,上面我们提到,HandlerMapper
有两个非常主要的实现类:RequestMappingHandlerMapping
和 BeanNameUrlHandlerMapping
。那么我们就分别来看一下这两个类的类图关系:
可以看到,这两个实现类的抽象父类正好对应了 AbstractHandlerMapping
的两个子类,所以这时候具体看哪个方法,那就看我们想看哪种类型了。
- RequestMappingHandlerMapping:主要用来存储
RequestMapping
注解相关的控制器和url
的映射关系。 - BeanNameUrlHandlerMapping:主要用来处理
Bean name
直接以/
开头的控制器和url
的映射关系。
其实除了这两种 HandlerMapping
之外,Spring
中还有其他一些 HandllerMapping
,如 SimpleUrlHandlerMapping
等。提到的这几种 HandlerMapping
,对我们来说最常用,最熟悉的那肯定就是 RequestMappingHandlerMapping
,在这里我们就以这个为例来进行分析。
AbstractHandlerMethodMapping-getHandlerInternal()
这个方法本身也没有什么逻辑,其主要的核心查找 Handler
逻辑在 lookupHandlerMethod
方法中,这个方法主要是为了获取一个 HandlerMethod
对象,前面的方法都是 Object
,而到这里变成了 HandlerMethod
类型,这是因为 Handler
有各种类型,目前我们已经基本跟到了具体类型之下,所以类型就变成了具体类型,而如果我们看的的另一条分支线,那么返回的就会是其他对象,正是因为支持多种不同类型的 HandlerMapping
对象,所以最终为了统一执行,才会需要在获得 Hanlder
之后,DispatcherServlet
中会再次通过调用 getHandlerAdapter
方法来进一步封装成 HandlerAdapter
对象,才能进行方法的调用。
/**
* Look up a handler method for the given request.
* 查找给定请求的处理程序方法。
*/
@Override
@Nullable
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);// 查找请求的路径
this.mappingRegistry.acquireReadLock();
try {
// 寻找handler
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
AbstractHandlerMethodMapping-lookupHandlerMethod()
这个方法主要会从 mappingRegistry
中获取命中的方法,获取之后还会经过一系列的判断比较判断比较,因为有些 url
会对应多个方法,而方法的请求类型不同,比如一个 GET
方法,一个 POST
方法,或者其他一些属性不相同等等,都会导致最终命中到不同的方法,这些逻辑主要都是在addMatchingMappings
方法去进一步实现,并最终将命中的结果加入到 matches
集合内。
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
// 找到合适的Mapping 这一步进一步根据属性查找准确的Hadnler
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 如果没有找打 那就遍历mapping中的HandlerMapping
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
//将HandlerMethod进一步进行包装
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
for (Match match : matches) {
if (match.hasCorsConfig()) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
}
}
else {
// 获取第二个 Handler 进行进一步的比较。
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.getHandlerMethod().getMethod();
Method m2 = secondBestMatch.getHandlerMethod().getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.getHandlerMethod();
}
else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
在这个方法中,有一个对象非常关键,那就是 mappingRegistry
,因为最终我们根据url
到这里获取到对应的 HandlerMtthod
,所以这个对象很关键:
class MappingRegistry {
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* Return all registrations.
* @since 5.3
*/
public Map<T, MappingRegistration<T>> getRegistrations() {
return this.registry;
}
/**
* Return matches for the given URL path. Not thread-safe.
* @see #acquireReadLock()
*/
@Nullable
public List<T> getMappingsByDirectPath(String urlPath) {
return this.pathLookup.get(urlPath);
}
/**
* Return handler methods by mapping name. Thread-safe for concurrent use.
*/
public List<HandlerMethod> getHandlerMethodsByMappingName(String mappingName) {
return this.nameLookup.get(mappingName);
}
/**
* Return CORS configuration. Thread-safe for concurrent use.
*/
@Nullable
public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
return this.corsLookup.get(original != null ? original : handlerMethod);
}
看这个对象其实很明显可以看出来,这个对象其实只是维护了一些 Map
对象,所以我们可以很容易猜测到,一定在某一个地方,将 url
和 HandlerMapping
或者 HandlerMethod
的映射关系存进来了,这时候其实我们可以根据 getMappingsByUrl
方法来进行反推,看看 urlLookup
这个 Map
是什么时候被存入的,结合上面的类图关系,一路反推,很容易就可以找到这个 Map
中的映射关系是 AbstractHandlerMethodMapping
对象的 afterPropertiesSet
方法实现的。(AbstractHandlerMethodMapping
实现了 InitializingBean
接口),也就是当这个对象初始化完成之后,我们的 url
和 Handler
映射关系已经存入了 MappingRegistry
对象中的集合 Map
中。
AbstractHandlerMethodMapping 的初始化
afterPropertiesSet
方法中并没有任何逻辑,而是直接调用了 initHandlerMethods
。
AbstractHandlerMethodMapping-initHandlerMethods()
initHandlerMethods
方法中,首先还是会从 Spring
的上下文中获取所有的 Bean
,然后会进一步从带有 RequestMapping
注解和 Controller
注解中的 Bean
去解析并获得 HandlerMethod
。
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* 扫描 ApplicationContext 中的 bean,检测并注册 handler方法。
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
AbstractHandlerMethodMapping-detectHandlerMethods()
这个方法中,其实就是通过反射获取到 Controller
中的所有方法,然后调用 registerHandlerMethod
方法将相关信息注册到 MappingRegistry
对象中的各种 Map
集合之内:
/**
* 在指定的处理程序 bean 中查找处理程序方法
* @param handler either a bean name or an actual handler instance
* @see #getMappingForMethod
*/
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
else if (mappingsLogger.isDebugEnabled()) {
mappingsLogger.debug(formatMappings(userType, methods));
}
// 遍历Controller中所有的方法
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
// 注册HandlerMethod
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
AbstractHandlerMethodMapping-register()
registerHandlerMethod
方法中会直接调用 AbstractHandlerMethodMapping
对象持有的 mappingRegistry
对象中的 regidter
方法,这里会对 Controller
中方法上的一些元信息进行各种解析,比如参数,路径,请求方式等等,然后会将各种信息注册到对应的 Map
集合中,最终完成了整个初始化。
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
三、spring mvc中适配器设计模式
3.1 适配器模式定义
适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
适配器模式包含了几个角色,它们是:
Target(目标角色):该角色定义其他类转化成何种接口,可以是一个抽象类或接口,也可以是具体类。
Adaptee(源角色):你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象。
Adapter(适配器角色):适配器模式的核心角色,职责就是通过继承或是类关联的方式把源角色转换为目标角色。
3.2 类适配器
首先有一个已存在的将被适配的类
public class Adaptee {
public void adapteeRequest() {
System.out.println("被适配者的方法");
}
}
定义一个目标接口
public interface Target {
void request();
}
怎么才可以在目标接口中的 request()
调用 Adaptee
的 adapteeRequest()
方法呢?
public class ConcreteTarget implements Target {
@Override
public void request() {
System.out.println("concreteTarget目标方法");
}
}
如果直接实现 Target
是不行的?
如果通过一个适配器类,实现 Target
接口,同时继承了 Adaptee
类,然后在实现的 request()
方法中调用父类的 adapteeRequest()
即可实现
public class Adapter extends Adaptee implements Target{
@Override
public void request() {
//...一些操作...
super.adapteeRequest();
//...一些操作...
}
}
我们来测试一下
public class Test {
public static void main(String[] args) {
Target target = new ConcreteTarget();
target.request();
Target adapterTarget = new Adapter();
adapterTarget.request();
}
}
代码输出
concreteTarget目标方法
被适配者的方法
这样我们即可在新接口 Target
中适配旧的接口或类。
3.3 对象适配器
对象适配器与类适配器不同之处在于,类适配器通过继承来完成适配,对象适配器则是通过关联来完成,这里稍微修改一下 Adapter
类即可将转变为对象适配器。
public class Adapter implements Target{
// 适配者是对象适配器的一个属性
private Adaptee adaptee = new Adaptee();
@Override
public void request() {
//...
adaptee.adapteeRequest();
//...
}
}
注意这里的 Adapter
是将 Adaptee
作为一个成员属性,而不是继承它。
电压适配器:我们国家的民用电都是 220V,日本是 110V,而我们的手机充电一般需要 5V,这时候要充电,就需要一个电压适配器,将 220V 或者 100V 的输入电压变换为 5V 输出
定义输出交流电接口,输出220V交流电类和输出110V交流电类。
public interface AC {
int outputAC();
}
public class AC110 implements AC {
public final int output = 110;
@Override
public int outputAC() {
return output;
}
}
public class AC220 implements AC {
public final int output = 220;
@Override
public int outputAC() {
return output;
}
}
适配器接口,其中 support()
方法用于检查输入的电压是否与适配器匹配,outputDC5V()
方法则用于将输入的电压变换为 5V 后输出。
public interface DC5Adapter {
boolean support(AC ac);
int outputDC5V(AC ac);
}
public class ChinaPowerAdapter implements DC5Adapter {
public static final int voltage = 220;
@Override
public boolean support(AC ac) {
return (voltage == ac.outputAC());
}
@Override
public int outputDC5V(AC ac) {
int adapterInput = ac.outputAC();
//变压器...
int adapterOutput = adapterInput / 44;
System.out.println("使用ChinaPowerAdapter变压适配器,输入AC:" + adapterInput + "V" + ",输出DC:" + adapterOutput + "V");
return adapterOutput;
}
}
public class JapanPowerAdapter implements DC5Adapter {
public static final int voltage = 110;
@Override
public boolean support(AC ac) {
return (voltage == ac.outputAC());
}
@Override
public int outputDC5V(AC ac) {
int adapterInput = ac.outputAC();
//变压器...
int adapterOutput = adapterInput / 22;
System.out.println("使用JapanPowerAdapter变压适配器,输入AC:" + adapterInput + "V" + ",输出DC:" + adapterOutput + "V");
return adapterOutput;
}
}
public class Test {
private List<DC5Adapter> adapters = new LinkedList<DC5Adapter>();
public Test() {
this.adapters.add(new ChinaPowerAdapter());
this.adapters.add(new JapanPowerAdapter());
}
// 根据电压找合适的变压器
public DC5Adapter getPowerAdapter(AC ac) {
DC5Adapter adapter = null;
for (DC5Adapter ad : this.adapters) {
if (ad.support(ac)) {
adapter = ad;
break;
}
}
if (adapter == null){
throw new IllegalArgumentException("没有找到合适的变压适配器");
}
return adapter;
}
public static void main(String[] args) {
Test test = new Test();
AC chinaAC = new AC220();
DC5Adapter adapter = test.getPowerAdapter(chinaAC);
adapter.outputDC5V(chinaAC);
// 去日本旅游,电压是 110V
AC japanAC = new AC110();
adapter = test.getPowerAdapter(japanAC);
adapter.outputDC5V(japanAC);
}
}
输出
使用ChinaPowerAdapter变压适配器,输入AC:220V,输出DC:5V
使用JapanPowerAdapter变压适配器,输入AC:110V,输出DC:5V
3.2 spring mvc中有几种适配器?
使用getHandlerAdapter 获取对应的handler的具体的handlerAdapter。
HandlerAdapter
的实现类有
- HttpRequestHandlerAdapter (org.springframework.web.servlet.mvc):Http请求处理的适配器
- SimpleServletHandlerAdapter (org.springframework.web.servlet.handler):实现了Httpservlet类采用的适配器。
- HandlerFunctionAdapter (org.springframework.web.servlet.function.support)
- CompositeHandlerAdapter (org.springframework.boot.actuate.autoconfigure.web.servlet)
- AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
- SimpleControllerHandlerAdapter (org.springframework.web.servlet.mvc):继承Controller方式所有使用的适配器
- RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc):注解的方式的(@Controller的方式)采用的适配器
3.3 spring mvc中适配器模式
SpringMVC,这套框架可以帮助我们把前端的请求访问到后台对应的controller的方法上,然后再把处理结果返回给后端,它的底层其实就用到了适配器模式。SpringMVC中的适配器模式主要用于执行目标Controller中的请求处理方法。在它的底层处理中,DispatcherServlet作为用户,HandlerAdapter作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller作为需要适配的类。
为什么要在 Spring MVC 中使用适配器模式?那么SpringMVC是怎么处理的呢?
Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,那样每增加一个类型的Controller就需要使用增加一个if else判断instance of,这违反了设计模式的开闭原则 —— 对扩展开放,对修改关闭。
我们来简单看一下源码,首先是适配器接口HandlerAdapter,
public interface HandlerAdapter {
boolean supports(Object var1);
ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
long getLastModified(HttpServletRequest var1, Object var2);
}
该接口的适配器类对应着 Controller,每自定义一个Controller需要定义一个实现HandlerAdapter的适配器。举个例子,有一个适配器类HttpRequestHandlerAdapter
,该类就是实现了HandlerAdapter接口,这是它的源码:
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}
当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet会通过handler的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的handle
方法来调用Controller中的用于处理请求的方法。
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//.........................
//找到匹配当前请求对应的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
}
// 遍历集合,找到合适的匹配器
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
}
这样一来,所有的controller就都统一交给HandlerAdapter处理,免去了大量的 if-else 语句判断,同时增加controller类型只需增加一个适配器即可,不需要修改到Servlet的逻辑,符合开闭原则。
3.4 适配器模式总结
适配器模式主要优点:
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
- 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
- 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
- 一个对象适配器可以把多个不同的适配者适配到同一个目标;
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。
类适配器模式的缺点如下:
- 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
- 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
- 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
对象适配器模式的缺点如下:
- 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
适用场景:
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
博文参考
SpringMVC源码分析系列(精简)
SpringMVC源码解析
带你一步一步手撕Spring MVC源码加手绘流程图
SpringMVC源码解析