02-HandlerMapping
SpringMVC九大内置组件
- HandlerMapping
- HandlerAdapter
- ViewResolver
- HandlerExceptionResolver
- RequestToViewNameTranslator
- LocaleResolver
- ThemeResolver
- MultipartResolver
- FlashMapManager
1. service()
1. META-INF
- 自建的module,没有
spring.handlers
、spring.schemas
,项目编译会覆盖这两个文件 resources/META-INF/spring.handlers
、resources/META-INF/spring.schemas
取其余module下这两个文件并集集合
2. doService()
1. HttpServlet
- Tomcat 核心组件
- Servlet 容器
- JSP 引擎
- HTTP 服务器
- WebSocket 支持和集群和负载均衡
Tomcat代码
public abstract class HttpServlet extends GenericServlet {
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
// 1... FrameworkServlet
service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
// 1.1. `lastModified`
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
// 1.2... FrameworkServlet
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
// 2... FrameworkServlet
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
// 3... FrameworkServlet
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
// 4... FrameworkServlet
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
// 5... FrameworkServlet
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
// 6... FrameworkServlet
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
// 7... FrameworkServlet
doTrace(req,resp);
} else {
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
}
1. lastModified
- 处理GET、HEAD请求的Last-Modified。当浏览器第一次向服务器请求资源时,服务器会在返回的请求头里包含一个
last_modified
的属性,代表资源最后是什么时候修改的。之后再发送请求,会携带接收到的Last_modified
。服务器将两个Last_modified
对比。过期了返回新的资源,否则直接返回304表示未过期,直接使用之前缓存的结果即可
2. FrameworkServlet
super.service(request, response);
后面调用Tomcat代码,无法debug。直接do***()
即可
@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
/**
* Override the parent class implementation in order to intercept PATCH requests.
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获得请求方法
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
// 处理patch请求
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
// 1... HttpServlet
super.service(request, response);
}
}
/**
* Delegate GET requests to processRequest/doService.
* <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
* with a {@code NoBodyResponse} that just captures the content length.
* @see #doService
* @see #doHead
*/
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 记录当前时间,用于计算处理请求花费的时间
long startTime = System.currentTimeMillis();
// 记录异常,用于保存处理请求过程中发送的异常
Throwable failureCause = null;
// 1. 获取LocaleContextHolder中原来保存的LocaleContext(保存的本地化信息)
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 2.1... DispatcherServlet 获取当前请求的LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
// 获取RequestContextHolder总原来保存的RequestAttribute(管理request和session的属性)
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 获取当前请求的ServletRequestAttribute
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// 获取异步管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 2.2. 当前请求的LocaleContext、ServletRequestAttribute设置到LocaleContextHolder、RequestContextHolder
initContextHolders(request, localeContext, requestAttributes);
try {
// 3... DispatcherServlet
doService(request, response);
}
catch (ServletException | IOException ex) {
// 记录抛出的异常
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
// 记录抛出的异常
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
// 2.3. 恢复原来的LocaleContext、ServletRequestAttributes到LocaleContextHolder、RequestContextHolder
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
// 日志级别为debug,打印请求日志
logResult(request, response, failureCause, asyncManager);
// 发布ServletRequestHandledEvent请求处理完成事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 如果 dispatcherOptionsRequest 为true,则处理该请求,默认为true
if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
// 处理请求
processRequest(request, response);
// 如果响应Header包含allow,则不需要提交给父方法处理
if (response.containsHeader("Allow")) {
// Proper OPTIONS response coming from a handler - we're done.
return;
}
}
// Use response wrapper in order to always add PATCH to the allowed methods
// 调用父方法,并在响应Header的allow增加patch的值
super.doOptions(request, new HttpServletResponseWrapper(response) {
@Override
public void setHeader(String name, String value) {
if ("Allow".equals(name)) {
value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();
}
super.setHeader(name, value);
}
});
}
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 如果 dispatchTraceRequest 为 true ,则处理该请求,默认为 false
if (this.dispatchTraceRequest) {
// 处理请求
processRequest(request, response);
// 如果响应的内容类型为 "message/http" ,则不需要交给父方法处理
if ("message/http".equals(response.getContentType())) {
// Proper TRACE response coming from a handler - we're done.
return;
}
}
// 调用父方法
super.doTrace(request, response);
}
}
3. DispatcherServlet
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
/**
* 语言处理器,提供国际化的支持
* LocaleResolver used by this servlet.
*/
@Nullable
private LocaleResolver localeResolver;
@Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
// 1. 内置组件
LocaleResolver lr = this.localeResolver;
if (lr instanceof LocaleContextResolver) {
return ((LocaleContextResolver) lr).resolveLocaleContext(request);
}
else {
// 2. AcceptHeaderLocaleResolver
return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
}
}
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
* for the actual dispatching.
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 如果日志级别为DEBUG,则打印请求日志
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.
// 当include请求时,对request的Attribute做快照备份
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) { // skip
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.
// 设置Spring框架中的常用对象到Request属性中,这四个属性会在handler和view中使用
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); // .CONTEXT
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); // .LOCALE_RESOLVER
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); // .THEME_RESOLVER
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // .THEME_SOURCE
// FlashMap的相关配置,主要用于Redirect转发时参数的传递
// post请求redirect本身只能通过url传递参数,而url有长度的限制同时还容易对外暴露,可以使用flashMap来传递参数
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);
}
try {
// 1.. 执行请求的分发
doDispatch(request, response);
}
finally {
// 异步处理相关
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
// 还原request快照的属性
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
}
2. doDispatch()
两个阶段,两层try_catch
- 请求处理
- 页面渲染
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 实际处理时所用的request,如果不是上传请求,则直接使用接收到的request,否则封装成上传类型的request
HttpServletRequest processedRequest = request;
// 处理请求的处理器链(包含处理器和对应的interceptor)
HandlerExecutionChain mappedHandler = null;
// 是不是上传请求的标志
boolean multipartRequestParsed = false;
// 获取异步管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
// 封装model和view的容器
ModelAndView mv = null;
// 处理请求过程中抛出的异常,但是不包含渲染过程中抛出的异常
Exception dispatchException = null;
try {
// 1. 上传请求,则通过multipartResolver将其封装成MultipartHttpServletRequest
processedRequest = checkMultipart(request);
// 设置上传请求标志
multipartRequestParsed = (processedRequest != request);
// 2. Determine handler for the current request.
// 获取请求对应的HandlerExecutionChain对象(HandlerMethod、HandlerInterceptor)
mappedHandler = getHandler(processedRequest);
// 获取不到,则根据配置抛出异常、返回404错误
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 3. Determine handler adapter for the current request.
// 获得当前handler对应的HandlerAdapter对象
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 4. Process last-modified header, if supported by the handler.
// 处理GET、HEAD请求的Last-Modified
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
// 获取请求中Service端最后被修改时间
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 5. 执行响应的Interceptor的preHandler
// 注意:该方法如果有一个拦截器的前置处理返回false,则开始倒序触发所有的拦截器的 已完成处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 6. Actually invoke the handler.
// 真正的调用handler方法,也就是执行对应的方法,并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 7. 需要异步处理,直接返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 8. 当view为空时,根据request设置默认的view
applyDefaultViewName(processedRequest, mv);
// 9. 执行响应的interceptor的postHandler
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);
}
// 10. 处理返回结果,包括处理异常、渲染页面、触发Interceptor的afterCompletion
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));
}
// 11. 释放资源
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);
}
}
}
}
}
1. HandlerInterceptor
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
2. HandlerAdapter
/**
* MVC framework SPI, allowing parameterization of the core MVC workflow.
*
* <p>Interface that must be implemented for each handler type to handle a request.
* This interface is used to allow the {@link DispatcherServlet} to be indefinitely
* extensible. The {@code DispatcherServlet} accesses all installed handlers through
* this interface, meaning that it does not contain code specific to any handler type.
*
* <p>Note that a handler can be of type {@code Object}. This is to enable
* handlers from other frameworks to be integrated with this framework without
* custom coding, as well as to allow for annotation-driven handler objects that
* do not obey any specific Java interface.
*
* <p>This interface is not intended for application developers. It is available
* to handlers who want to develop their own web workflow.
*
* <p>Note: {@code HandlerAdapter} implementors may implement the {@link
* org.springframework.core.Ordered} interface to be able to specify a sorting
* order (and thus a priority) for getting applied by the {@code DispatcherServlet}.
* Non-Ordered instances get treated as lowest priority.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
* @see org.springframework.web.servlet.handler.SimpleServletHandlerAdapter
*/
public interface HandlerAdapter {
/**
* 是否支持该处理器
* <p>
* Given a handler instance, return whether or not this {@code HandlerAdapter}
* can support it. Typical HandlerAdapters will base the decision on the handler
* type. HandlerAdapters will usually only support one handler type each.
* <p>A typical implementation:
* <p>{@code
* return (handler instanceof MyHandler);
* }
*
* @param handler the handler object to check
* @return whether or not this object can use the given handler
*/
boolean supports(Object handler);
/**
* 执行处理器,返回ModelAndView结果
* <p>
* Use the given handler to handle this request.
* The workflow that is required may vary widely.
*
* @param request current HTTP request
* @param response current HTTP response
* @param handler the handler to use. This object must have previously been passed
* to the {@code supports} method of this interface, which must have
* returned {@code true}.
* @return a ModelAndView object with the name of the view and the required
* model data, or {@code null} if the request has been handled directly
* @throws Exception in case of errors
*/
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/**
* 返回请求的最新更新时间,如果不支持该操作,则返回-1即可
* <p>
* Same contract as for HttpServlet's {@code getLastModified} method.
* Can simply return -1 if there's no support in the handler class.
*
* @param request current HTTP request
* @param handler the handler to use
* @return the lastModified value for the given handler
* see javax.servlet.http.HttpServlet#getLastModified
* @see org.springframework.web.servlet.mvc.LastModified#getLastModified
*/
long getLastModified(HttpServletRequest request, Object handler);
}
3. ViewResolver
/**
* 将String类型的视图名和locale解析为view类型的视图
* viewResolver需要找到渲染所用的模板和视图的类型进行渲染,具体的渲染过程则交给不同的视图来完成
* 最常使用的有
* UrlBasedViewResolver:针对单一视图
* InternalResourceViewResolver:针对jsp类型的视图
* FreeMarkerViewResolver:针对freeMaker
* <p>
* Interface to be implemented by objects that can resolve views by name.
*
* <p>View state doesn't change during the running of the application,
* so implementations are free to cache views.
*
* <p>Implementations are encouraged to support internationalization,
* i.e. localized view resolution.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.web.servlet.view.InternalResourceViewResolver
* @see org.springframework.web.servlet.view.ResourceBundleViewResolver
* @see org.springframework.web.servlet.view.XmlViewResolver
*/
public interface ViewResolver {
/**
* 根据视图名和国际化,获得最终的view对象
* <p>
* Resolve the given view by name.
* <p>Note: To allow for ViewResolver chaining, a ViewResolver should
* return {@code null} if a view with the given name is not defined in it.
* However, this is not required: Some ViewResolvers will always attempt
* to build View objects with the given name, unable to return {@code null}
* (rather throwing an exception when View creation failed).
*
* @param viewName name of the view to resolve
* @param locale the Locale in which to resolve the view.
* ViewResolvers that support internationalization should respect this.
* @return the View object, or {@code null} if not found
* (optional, to allow for ViewResolver chaining)
* @throws Exception if the view cannot be resolved
* (typically in case of problems creating an actual View object)
*/
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
3. upload
https://commons.apache.org/proper/commons-fileupload
下载CommonsFileUploadSupport
的jar包
1. checkMultipart()
- 不存在默认的
multipartResolver
,所以需要IOC注入
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="17367648787"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>
this.multipartResolver.isMultipart(request)
this.multipartResolver.resolveMultipart(request)
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
/**
* multipart数据文件处理器
* MultipartResolver used by this servlet.
*/
@Nullable
private MultipartResolver multipartResolver; // CommonsMultipartResolver
/**
* Initialize the MultipartResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* no multipart handling is provided.
*/
private void initMultipartResolver(ApplicationContext context) {
try {
// 1. IOC中获取MultipartResolver的Bean
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.multipartResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isTraceEnabled()) {
logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
}
}
}
/**
* Convert the request into a multipart request, and make multipart resolver available.
* <p>If no multipart resolver is set, simply use the existing request.
*
* @param request current HTTP request
* @return the processed request (multipart wrapper if necessary)
* @see MultipartResolver#resolveMultipart
*/
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
// 1... CommonsMultipartResolver multipart(文件)请求
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
} else if (hasMultipartException(request)) {
logger.debug("Multipart resolution previously failed for current request - " +
"skipping re-resolution for undisturbed error rendering");
} else {
try {
// 2... CommonsMultipartResolver HttpServletRequest封装成MultipartHttpServletRequest,解析请求参数以及文件
return this.multipartResolver.resolveMultipart(request);
} catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
} else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
pageContext.setAttribute("ctx", request.getContextPath());
%>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${ctx}/fileupload" enctype="multipart/form-data" method="post">
描述:<input type="text" name="desc"><br><br>
文件:<input type="file" name="file"><br><br>
<input type="submit" value="上传">
</form>
</body>
</html>
2. CommonsMultipartResolver
/**
* Servlet-based {@link MultipartResolver} implementation for
* <a href="https://commons.apache.org/proper/commons-fileupload">Apache Commons FileUpload</a>
* 1.2 or above.
*
* <p>Provides "maxUploadSize", "maxInMemorySize" and "defaultEncoding" settings as
* bean properties (inherited from {@link CommonsFileUploadSupport}). See corresponding
* ServletFileUpload / DiskFileItemFactory properties ("sizeMax", "sizeThreshold",
* "headerEncoding") for details in terms of defaults and accepted values.
*
* <p>Saves temporary files to the servlet container's temporary directory.
* Needs to be initialized <i>either</i> by an application context <i>or</i>
* via the constructor that takes a ServletContext (for standalone usage).
*
* @author Trevor D. Cook
* @author Juergen Hoeller
* @see #CommonsMultipartResolver(ServletContext)
* @see #setResolveLazily
* @see org.apache.commons.fileupload.servlet.ServletFileUpload
* @see org.apache.commons.fileupload.disk.DiskFileItemFactory
* @since 29.09.2003
*/
public class CommonsMultipartResolver extends CommonsFileUploadSupport
implements MultipartResolver, ServletContextAware {
@Override
protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
return new ServletFileUpload(fileItemFactory);
}
@Override
public boolean isMultipart(HttpServletRequest request) {
// 1... ServletFileUpload
return ServletFileUpload.isMultipartContent(request);
}
@Override
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
Assert.notNull(request, "Request must not be null");
if (this.resolveLazily) {
return new DefaultMultipartHttpServletRequest(request) {
@Override
protected void initializeMultipart() {
MultipartParsingResult parsingResult = parseRequest(request);
setMultipartFiles(parsingResult.getMultipartFiles());
setMultipartParameters(parsingResult.getMultipartParameters());
setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
}
};
} else {
// 1..
MultipartParsingResult parsingResult = parseRequest(request);
// 2. 请求、多文件、多实参,分别封装
return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
}
}
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
// 1.. 读取当前请求编码
String encoding = determineEncoding(request);
// 2... CommonsFileUploadSupport => ServletFileUpload 比较请求的编码、XML中配置编码,不一样,会拒绝处理
FileUpload fileUpload = prepareFileUpload(encoding);
try {
// 3... ServletFileUpload 对请求中的multipart文件进行具体的处理
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
// 4... CommonsFileUploadSupport
return parseFileItems(fileItems, encoding);
} catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
} catch (FileUploadBase.FileSizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
} catch (FileUploadException ex) {
throw new MultipartException("Failed to parse multipart servlet request", ex);
}
}
protected String determineEncoding(HttpServletRequest request) {
String encoding = request.getCharacterEncoding();
if (encoding == null) {
// 1... CommonsFileUploadSupport
encoding = getDefaultEncoding();
}
return encoding;
}
}
1. MultipartResolver
- 常用实现类
CommonsMultipartResolver
package org.springframework.web.multipart;
import javax.servlet.http.HttpServletRequest;
/**
* MultipartResolver用于处理上传请求,处理方式是将普通的request包装成MultipartHttpServletRequest,可以直接调用getFile方法来获取File,
* 如果上传多个文件,可以调用getFileMap来处理。
* <p>
* A strategy interface for multipart file upload resolution in accordance
* with <a href="https://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.
* Implementations are typically usable both within an application context
* and standalone.
*
* <p>There are two concrete implementations included in Spring, as of Spring 3.1:
* <ul>
* <li>{@link org.springframework.web.multipart.commons.CommonsMultipartResolver}
* for Apache Commons FileUpload
* <li>{@link org.springframework.web.multipart.support.StandardServletMultipartResolver}
* for the Servlet 3.0+ Part API
* </ul>
*
* <p>There is no default resolver implementation used for Spring
* {link org.springframework.web.servlet.DispatcherServlet DispatcherServlets},
* as an application might choose to parse its multipart requests itself. To define
* an implementation, create a bean with the id "multipartResolver" in a
* {link org.springframework.web.servlet.DispatcherServlet DispatcherServlet's}
* application context. Such a resolver gets applied to all requests handled
* by that {link org.springframework.web.servlet.DispatcherServlet}.
*
* <p>If a {link org.springframework.web.servlet.DispatcherServlet} detects a
* multipart request, it will resolve it via the configured {@link MultipartResolver}
* and pass on a wrapped {@link javax.servlet.http.HttpServletRequest}. Controllers
* can then cast their given request to the {@link MultipartHttpServletRequest}
* interface, which allows for access to any {@link MultipartFile MultipartFiles}.
* Note that this cast is only supported in case of an actual multipart request.
*
* <pre class="code">
* public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
* MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
* MultipartFile multipartFile = multipartRequest.getFile("image");
* ...
* }</pre>
* <p>
* Instead of direct access, command or form controllers can register a
* {@link org.springframework.web.multipart.support.ByteArrayMultipartFileEditor}
* or {@link org.springframework.web.multipart.support.StringMultipartFileEditor}
* with their data binder, to automatically apply multipart content to form
* bean properties.
*
* <p>As an alternative to using a {@link MultipartResolver} with a
* {link org.springframework.web.servlet.DispatcherServlet},
* a {@link org.springframework.web.multipart.support.MultipartFilter} can be
* registered in {@code web.xml}. It will delegate to a corresponding
* {@link MultipartResolver} bean in the root application context. This is mainly
* intended for applications that do not use Spring's own web MVC framework.
*
* <p>Note: There is hardly ever a need to access the {@link MultipartResolver}
* itself from application code. It will simply do its work behind the scenes,
* making {@link MultipartHttpServletRequest MultipartHttpServletRequests}
* available to controllers.
*
* @author Juergen Hoeller
* @author Trevor D. Cook
* @see MultipartHttpServletRequest
* @see MultipartFile
* @see org.springframework.web.multipart.commons.CommonsMultipartResolver
* @see org.springframework.web.multipart.support.ByteArrayMultipartFileEditor
* @see org.springframework.web.multipart.support.StringMultipartFileEditor
* see org.springframework.web.servlet.DispatcherServlet
* @since 29.09.2003
*/
public interface MultipartResolver {
/**
* 判断是否是上传请求
*/
boolean isMultipart(HttpServletRequest request);
/**
* 将request请求包装成MultipartHttpServletRequest
*/
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
/**
* 处理完成之后清理上传过程中产生的临时资源
*/
void cleanupMultipart(MultipartHttpServletRequest request);
}
2. CommonsFileUploadSupport
public abstract class CommonsFileUploadSupport {
public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
private final DiskFileItemFactory fileItemFactory; // DiskFileItemFactory
private final FileUpload fileUpload; // ServletFileUpload
public CommonsFileUploadSupport() {
// 1.. DiskFileItemFactory
this.fileItemFactory = newFileItemFactory();
// 2... CommonsMultipartResolver => ServletFileUpload
this.fileUpload = newFileUpload(getFileItemFactory());
}
protected DiskFileItemFactory newFileItemFactory() {
return new DiskFileItemFactory();
}
protected String getDefaultEncoding() {
String encoding = getFileUpload().getHeaderEncoding();
if (encoding == null) {
// 1. ISO-8859-1
encoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
}
return encoding;
}
protected FileUpload prepareFileUpload(@Nullable String encoding) {
// 1..
FileUpload fileUpload = getFileUpload(); // ServletFileUpload
FileUpload actualFileUpload = fileUpload;
// Use new temporary FileUpload instance if the request specifies
// its own encoding that does not match the default encoding.
if (encoding != null && !encoding.equals(fileUpload.getHeaderEncoding())) { // skip
actualFileUpload = newFileUpload(getFileItemFactory());
actualFileUpload.setSizeMax(fileUpload.getSizeMax());
actualFileUpload.setFileSizeMax(fileUpload.getFileSizeMax());
actualFileUpload.setHeaderEncoding(encoding);
}
return actualFileUpload;
}
public FileUpload getFileUpload() {
return this.fileUpload;
}
protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) {
// 保存上传的文件
MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<>();
// 保存参数
Map<String, String[]> multipartParameters = new HashMap<>();
// 保存参数的contentType
Map<String, String> multipartParameterContentTypes = new HashMap<>();
// Extract multipart files and multipart parameters.
// 将fileItems分为文件和参数两类,并设置到对应的map
for (FileItem fileItem : fileItems) {
// 1. 参数类型
if (fileItem.isFormField()) {
String value;
String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
try {
value = fileItem.getString(partEncoding);
} catch (UnsupportedEncodingException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Could not decode multipart item '" + fileItem.getFieldName() +
"' with encoding '" + partEncoding + "': using platform default");
}
value = fileItem.getString();
}
String[] curParam = multipartParameters.get(fileItem.getFieldName());
if (curParam == null) {
// simple form field
// 单个参数
multipartParameters.put(fileItem.getFieldName(), new String[]{value});
} else {
// array of simple form fields
// 数组参数
String[] newParam = StringUtils.addStringToArray(curParam, value);
multipartParameters.put(fileItem.getFieldName(), newParam);
}
// 保存参数的contentType
multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());
}
// 2. 文件类型
else {
// multipart file field
CommonsMultipartFile file = createMultipartFile(fileItem);
multipartFiles.add(file.getName(), file);
LogFormatUtils.traceDebug(logger, traceOn ->
"Part '" + file.getName() + "', size " + file.getSize() +
" bytes, filename='" + file.getOriginalFilename() + "'" +
(traceOn ? ", storage=" + file.getStorageDescription() : "")
);
}
}
// 3.
return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);
}
/**
* Create a {@link CommonsMultipartFile} wrapper for the given Commons {@link FileItem}.
*
* @param fileItem the Commons FileItem to wrap
* @return the corresponding CommonsMultipartFile (potentially a custom subclass)
* @see #setPreserveFilename(boolean)
* @see CommonsMultipartFile#setPreserveFilename(boolean)
* @since 4.3.5
*/
protected CommonsMultipartFile createMultipartFile(FileItem fileItem) {
CommonsMultipartFile multipartFile = new CommonsMultipartFile(fileItem);
multipartFile.setPreserveFilename(this.preserveFilename);
return multipartFile;
}
}
1. CommonsMultipartFile
MultipartFile
为 controller 上的形参
public class CommonsMultipartFile implements MultipartFile, Serializable {
protected static final Log logger = LogFactory.getLog(CommonsMultipartFile.class);
private final FileItem fileItem;
private final long size;
private boolean preserveFilename = false;
/**
* Create an instance wrapping the given FileItem.
* @param fileItem the FileItem to wrap
*/
public CommonsMultipartFile(FileItem fileItem) {
this.fileItem = fileItem;
this.size = this.fileItem.getSize();
}
@Override
public String getName() {
return this.fileItem.getFieldName();
}
@Override
public String getOriginalFilename() {
String filename = this.fileItem.getName();
if (filename == null) {
// Should never happen.
return "";
}
if (this.preserveFilename) {
// Do not try to strip off a path...
return filename;
}
// Check for Unix-style path
int unixSep = filename.lastIndexOf('/');
// Check for Windows-style path
int winSep = filename.lastIndexOf('\\');
// Cut off at latest possible point
int pos = Math.max(winSep, unixSep);
if (pos != -1) {
// Any sort of path separator found...
return filename.substring(pos + 1);
}
else {
// A plain name
return filename;
}
}
@Override
public String getContentType() {
return this.fileItem.getContentType();
}
@Override
public byte[] getBytes() {
if (!isAvailable()) {
throw new IllegalStateException("File has been moved - cannot be read again");
}
byte[] bytes = this.fileItem.get();
return (bytes != null ? bytes : new byte[0]);
}
@Override
public InputStream getInputStream() throws IOException {
if (!isAvailable()) {
throw new IllegalStateException("File has been moved - cannot be read again");
}
InputStream inputStream = this.fileItem.getInputStream();
return (inputStream != null ? inputStream : StreamUtils.emptyInput());
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
if (!isAvailable()) {
throw new IllegalStateException("File has already been moved - cannot be transferred again");
}
if (dest.exists() && !dest.delete()) {
throw new IOException(
"Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted");
}
try {
this.fileItem.write(dest);
LogFormatUtils.traceDebug(logger, traceOn -> {
String action = "transferred";
if (!this.fileItem.isInMemory()) {
action = (isAvailable() ? "copied" : "moved");
}
return "Part '" + getName() + "', filename '" + getOriginalFilename() + "'" +
(traceOn ? ", stored " + getStorageDescription() : "") +
": " + action + " to [" + dest.getAbsolutePath() + "]";
});
}
catch (FileUploadException ex) {
throw new IllegalStateException(ex.getMessage(), ex);
}
catch (IllegalStateException | IOException ex) {
// Pass through IllegalStateException when coming from FileItem directly,
// or propagate an exception from I/O operations within FileItem.write
throw ex;
}
catch (Exception ex) {
throw new IOException("File transfer failed", ex);
}
}
@Override
public void transferTo(Path dest) throws IOException, IllegalStateException {
if (!isAvailable()) {
throw new IllegalStateException("File has already been moved - cannot be transferred again");
}
FileCopyUtils.copy(this.fileItem.getInputStream(), Files.newOutputStream(dest));
}
}
3. ServletFileUpload
public class ServletFileUpload extends FileUpload {
/**
* Constant for HTTP POST method.
*/
private static final String POST_METHOD = "POST";
public ServletFileUpload(FileItemFactory fileItemFactory) {
super(fileItemFactory);
}
public static final boolean isMultipartContent(
HttpServletRequest request) {
if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) {
return false;
}
// 1... FileUploadBase
return FileUploadBase.isMultipartContent(new ServletRequestContext(request));
}
@Override
public List<FileItem> parseRequest(HttpServletRequest request) throws FileUploadException {
// 1... FileUploadBase
return parseRequest(new ServletRequestContext(request));
}
}
1. FileUpload
public class FileUpload extends FileUploadBase {
/**
* The factory to use to create new form items.
*/
private FileItemFactory fileItemFactory; // DiskFileItemFactory
public FileUpload(FileItemFactory fileItemFactory) {
super();
this.fileItemFactory = fileItemFactory;
}
}
2. FileUploadBase
public abstract class FileUploadBase {
public static final String CONTENT_TYPE = "Content-type";
public static final String CONTENT_DISPOSITION = "Content-disposition";
public static final String CONTENT_LENGTH = "Content-length";
public static final String FORM_DATA = "form-data";
public static final String ATTACHMENT = "attachment";
public static final String MULTIPART = "multipart/";
public static final String MULTIPART_FORM_DATA = "multipart/form-data";
public static final String MULTIPART_MIXED = "multipart/mixed";
public static final boolean isMultipartContent(RequestContext ctx) {
// 1... ServletRequestContext
String contentType = ctx.getContentType();
if (contentType == null) {
return false;
}
// 2. `multipart/`
if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
return true;
}
return false;
}
public List<FileItem> parseRequest(RequestContext ctx)
throws FileUploadException {
List<FileItem> items = new ArrayList<FileItem>();
boolean successful = false;
try {
// 1..
FileItemIterator iter = getItemIterator(ctx); // FileUploadBase$FileItemIteratorImpl
FileItemFactory fac = getFileItemFactory(); // DiskFileItemFactory
if (fac == null) {
throw new NullPointerException("No FileItemFactory has been set.");
}
while (iter.hasNext()) {
// 2... FileUploadBase$FileItemIteratorImpl => FileItemStreamImpl
final FileItemStream item = iter.next();
// Don't use getName() here to prevent an InvalidFileNameException.
final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
// 3... DiskFileItemFactory => DiskFileItem
FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(), item.isFormField(), fileName);
items.add(fileItem);
try {
// 4.
Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new IOFileUploadException(format("Processing of %s request failed. %s",
MULTIPART_FORM_DATA, e.getMessage()), e);
}
// 5.
final FileItemHeaders fih = item.getHeaders();
fileItem.setHeaders(fih);
}
successful = true;
// 6.
return items;
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new FileUploadException(e.getMessage(), e);
} finally {
if (!successful) {
for (FileItem fileItem : items) {
try {
fileItem.delete();
} catch (Exception ignored) {
// ignored TODO perhaps add to tracker delete failure list somehow?
}
}
}
}
}
public FileItemIterator getItemIterator(RequestContext ctx) throws FileUploadException, IOException {
try {
// 1.
return new FileItemIteratorImpl(ctx);
} catch (FileUploadIOException e) {
// unwrap encapsulated SizeException
throw (FileUploadException) e.getCause();
}
}
// ------------------------------------------------------------------------
/**
* The iterator, which is returned by
* {@link FileUploadBase#getItemIterator(RequestContext)}.
*/
private class FileItemIteratorImpl implements FileItemIterator {
/**
* The multi part stream to process.
*/
private final MultipartStream multi;
/**
* Creates a new instance.
*
* @param ctx The request context.
* @throws FileUploadException An error occurred while
* parsing the request.
* @throws IOException An I/O error occurred.
*/
FileItemIteratorImpl(RequestContext ctx) throws FileUploadException, IOException {
if (ctx == null) {
throw new NullPointerException("ctx parameter");
}
// 1.
String contentType = ctx.getContentType();
if ((null == contentType) || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
throw new InvalidContentTypeException(String.format(
"the request doesn't contain a %s or %s stream, content type header is %s",
MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
}
// 2.
final long requestSize = ((UploadContext) ctx).contentLength();
InputStream input; // N.B. this is eventually closed in MultipartStream processing
if (sizeMax >= 0) {
if (requestSize != -1 && requestSize > sizeMax) {
throw new SizeLimitExceededException(String.format(
"the request was rejected because its size (%s) exceeds the configured maximum (%s)",
Long.valueOf(requestSize), Long.valueOf(sizeMax)),
requestSize, sizeMax);
}
// 3.1. N.B. this is eventually closed in MultipartStream processing
input = new LimitedInputStream(ctx.getInputStream(), sizeMax) {
@Override
protected void raiseError(long pSizeMax, long pCount)
throws IOException {
FileUploadException ex = new SizeLimitExceededException(
String.format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
Long.valueOf(pCount), Long.valueOf(pSizeMax)),
pCount, pSizeMax);
throw new FileUploadIOException(ex);
}
};
} else {
// 3.2.
input = ctx.getInputStream();
}
String charEncoding = headerEncoding;
if (charEncoding == null) {
// 4.
charEncoding = ctx.getCharacterEncoding();
}
boundary = getBoundary(contentType);
if (boundary == null) {
IOUtils.closeQuietly(input); // avoid possible resource leak
throw new FileUploadException("the request was rejected because no multipart boundary was found");
}
notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
try {
// 5.
multi = new MultipartStream(input, boundary, notifier);
} catch (IllegalArgumentException iae) {
IOUtils.closeQuietly(input); // avoid possible resource leak
throw new InvalidContentTypeException(
String.format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
}
// 6.
multi.setHeaderEncoding(charEncoding);
skipPreamble = true;
// 7..
findNextItem();
}
/**
* Returns, whether another instance of {@link FileItemStream}
* is available.
*
* @throws FileUploadException Parsing or processing the
* file item failed.
* @throws IOException Reading the file item failed.
* @return True, if one or more additional file items
* are available, otherwise false.
*/
@Override
public boolean hasNext() throws FileUploadException, IOException {
if (eof) {
return false;
}
if (itemValid) {
return true;
}
try {
// 1..
return findNextItem();
} catch (FileUploadIOException e) {
// unwrap encapsulated SizeException
throw (FileUploadException) e.getCause();
}
}
/**
* Returns the next available {@link FileItemStream}.
*
* @throws java.util.NoSuchElementException No more items are
* available. Use {@link #hasNext()} to prevent this exception.
* @throws FileUploadException Parsing or processing the
* file item failed.
* @throws IOException Reading the file item failed.
* @return FileItemStream instance, which provides
* access to the next file item.
*/
@Override
public FileItemStream next() throws FileUploadException, IOException {
if (eof || (!itemValid && !hasNext())) {
throw new NoSuchElementException();
}
itemValid = false;
return currentItem;
}
/**
* Called for finding the next item, if any.
*
* @return True, if an next item was found, otherwise false.
* @throws IOException An I/O error occurred.
*/
private boolean findNextItem() throws IOException {
if (eof) { // skip
return false;
}
if (currentItem != null) { // skip
currentItem.close();
currentItem = null;
}
for (;;) {
boolean nextPart;
if (skipPreamble) {
nextPart = multi.skipPreamble();
} else {
nextPart = multi.readBoundary();
}
if (!nextPart) { // skip
if (currentFieldName == null) {
// Outer multipart terminated -> No more data
eof = true;
return false;
}
// Inner multipart terminated -> Return to parsing the outer
multi.setBoundary(boundary);
currentFieldName = null;
continue;
}
FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
if (currentFieldName == null) {
// 1. We're parsing the outer multipart
String fieldName = getFieldName(headers);
if (fieldName != null) {
// 2.
String subContentType = headers.getHeader(CONTENT_TYPE);
if (subContentType != null && subContentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART_MIXED)) { // skip
currentFieldName = fieldName;
// Multiple files associated with this field name
byte[] subBoundary = getBoundary(subContentType);
multi.setBoundary(subBoundary);
skipPreamble = true;
continue;
}
// 3.
String fileName = getFileName(headers);
// 4...
currentItem = new FileItemStreamImpl(fileName, fieldName, headers.getHeader(CONTENT_TYPE),
fileName == null, getContentLength(headers));
// 5.
currentItem.setHeaders(headers);
notifier.noteItem();
itemValid = true;
return true;
}
} else {
String fileName = getFileName(headers);
if (fileName != null) {
currentItem = new FileItemStreamImpl(fileName,
currentFieldName,
headers.getHeader(CONTENT_TYPE),
false, getContentLength(headers));
currentItem.setHeaders(headers);
notifier.noteItem();
itemValid = true;
return true;
}
}
multi.discardBodyData();
}
}
/**
* Default implementation of {@link FileItemStream}.
*/
class FileItemStreamImpl implements FileItemStream {
/**
* The file items content type.
*/
private final String contentType;
/**
* The file items field name.
*/
private final String fieldName;
/**
* The file items file name.
*/
private final String name;
/**
* Whether the file item is a form field.
*/
private final boolean formField;
/**
* The file items input stream.
*/
private final InputStream stream;
/**
* The headers, if any.
*/
private FileItemHeaders headers;
/**
* Creates a new instance.
*
* @param pName The items file name, or null.
* @param pFieldName The items field name.
* @param pContentType The items content type, or null.
* @param pFormField Whether the item is a form field.
* @param pContentLength The items content length, if known, or -1
* @throws IOException Creating the file item failed.
*/
FileItemStreamImpl(String pName, String pFieldName,
String pContentType, boolean pFormField,
long pContentLength) throws IOException {
name = pName;
fieldName = pFieldName;
contentType = pContentType;
formField = pFormField;
if (fileSizeMax != -1) { // Check if limit is already exceeded
if (pContentLength != -1
&& pContentLength > fileSizeMax) {
FileSizeLimitExceededException e =
new FileSizeLimitExceededException(
String.format("The field %s exceeds its maximum permitted size of %s bytes.",
fieldName, Long.valueOf(fileSizeMax)),
pContentLength, fileSizeMax);
e.setFileName(pName);
e.setFieldName(pFieldName);
throw new FileUploadIOException(e);
}
}
// 1. OK to construct stream now
final ItemInputStream itemStream = multi.newInputStream();
InputStream istream = itemStream;
if (fileSizeMax != -1) {
// 2.
istream = new LimitedInputStream(istream, fileSizeMax) {
@Override
protected void raiseError(long pSizeMax, long pCount)
throws IOException {
itemStream.close(true);
FileSizeLimitExceededException e =
new FileSizeLimitExceededException(
String.format("The field %s exceeds its maximum permitted size of %s bytes.",
fieldName, Long.valueOf(pSizeMax)),
pCount, pSizeMax);
e.setFieldName(fieldName);
e.setFileName(name);
throw new FileUploadIOException(e);
}
};
}
// 3.
stream = istream;
}
}
}
}
1. ServletRequestContext
package org.apache.tomcat.util.http.fileupload.servlet;
/**
* <p>Provides access to the request information needed for a request made to
* an HTTP servlet.</p>
*
* @since FileUpload 1.1
*/
public class ServletRequestContext implements UploadContext {
// ----------------------------------------------------- Instance Variables
/**
* The request for which the context is being provided.
*/
private final HttpServletRequest request;
// ----------------------------------------------------------- Constructors
/**
* Construct a context for this request.
*
* @param request The request to which this context applies.
*/
public ServletRequestContext(HttpServletRequest request) {
this.request = request;
}
// --------------------------------------------------------- Public Methods
/**
* Retrieve the character encoding for the request.
*
* @return The character encoding for the request.
*/
@Override
public String getCharacterEncoding() {
return request.getCharacterEncoding();
}
/**
* Retrieve the content type of the request.
*
* @return The content type of the request.
*/
@Override
public String getContentType() {
return request.getContentType();
}
/**
* Retrieve the content length of the request.
*
* @return The content length of the request.
* @since 1.3
*/
@Override
public long contentLength() {
long size;
try {
size = Long.parseLong(request.getHeader(FileUploadBase.CONTENT_LENGTH));
} catch (NumberFormatException e) {
size = request.getContentLength();
}
return size;
}
/**
* Retrieve the input stream for the request.
*
* @return The input stream for the request.
*
* @throws IOException if a problem occurs.
*/
@Override
public InputStream getInputStream() throws IOException {
return request.getInputStream();
}
}
2. DiskFileItemFactory
package org.apache.tomcat.util.http.fileupload.disk;
public class DiskFileItemFactory implements FileItemFactory {
/**
* Create a new {@link DiskFileItem}
* instance from the supplied parameters and the local factory
* configuration.
*
* @param fieldName The name of the form field.
* @param contentType The content type of the form field.
* @param isFormField <code>true</code> if this is a plain form field;
* <code>false</code> otherwise.
* @param fileName The name of the uploaded file, if any, as supplied
* by the browser or other client.
*
* @return The newly created file item.
*/
@Override
public FileItem createItem(String fieldName, String contentType, boolean isFormField, String fileName) {
DiskFileItem result = new DiskFileItem(fieldName, contentType, isFormField, fileName, sizeThreshold, repository);
result.setDefaultCharset(defaultCharset);
return result;
}
}
3. StandardSvltMultipartResolver
// Servlet3.0后的上传组件。不常用
public class StandardServletMultipartResolver implements MultipartResolver {
@Override
public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
}
4. 三种Controller
1. implements Controller
public class Ctl1 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("Ctl1 ......");
return null;
}
}
<bean name="/ctl1" class="com.listao.controller.ctl.Ctl1"/>
2. implements HttpReqHandler
public class Ctl2 implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("Ctl2 ......");
}
}
<bean name="/ctl2" class="com.listao.controller.ctl.Ctl2"/>
3. @Controller
@Controller
public class Ctl3 {
@RequestMapping("/ctl3")
public void ctl3() {
System.out.println("Ctl3 ......");
}
}
5. getHandler()
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
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
/**
* 处理器匹配器,返回请求对应的处理器和拦截器
* List of HandlerMappings used by this servlet.
*/
@Nullable
private List<HandlerMapping> handlerMappings;
/**
* Detect all HandlerMappings or just expect "handlerMapping" bean?.
*/
private boolean detectAllHandlerMappings = true;
/**
* Initialize the HandlerMappings used by this class.
* <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
* we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
// 将handlerMappings置空
this.handlerMappings = null;
// 1. 如果开启探测功能,则扫描已注册的HandlerMapping的bean,添加到handlerMappings中
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
// 扫描已注册的handlerMapping的bean
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
// 添加到handlerMappings中,并进行排序
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order. 可以定义多种视图,按照优先级进行排序操作
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
// 2. 如果关闭探测功能,则获取Bean名称为handlerMapping对应的bean,将其添加到handlerMappings
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
} catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
// 3. 如果未获得到,则获得默认配置的handlerMapping类
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
/**
* 返回当前request匹配的interceptor和handler
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
*
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
// 1... AbstractHandlerMapping
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
}
1. HandlerMapping
- 通过接口学习组件
/**
* 根据Request找到响应的处理器handler和interceptors
* <p>
* Interface to be implemented by objects that define a mapping between
* requests and handler objects.
*
* <p>This class can be implemented by application developers, although this is not
* necessary, as {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}
* and {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping}
* are included in the framework. The former is the default if no
* HandlerMapping bean is registered in the application context.
*
* <p>HandlerMapping implementations can support mapped interceptors but do not
* have to. A handler will always be wrapped in a {@link HandlerExecutionChain}
* instance, optionally accompanied by some {@link HandlerInterceptor} instances.
* The DispatcherServlet will first call each HandlerInterceptor's
* {@code preHandle} method in the given order, finally invoking the handler
* itself if all {@code preHandle} methods have returned {@code true}.
*
* <p>The ability to parameterize this mapping is a powerful and unusual
* capability of this MVC framework. For example, it is possible to write
* a custom mapping based on session state, cookie state or many other
* variables. No other MVC framework seems to be equally flexible.
*
* <p>Note: Implementations can implement the {@link org.springframework.core.Ordered}
* interface to be able to specify a sorting order and thus a priority for getting
* applied by DispatcherServlet. Non-Ordered instances get treated as lowest priority.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.core.Ordered
* @see org.springframework.web.servlet.handler.AbstractHandlerMapping
* @see org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
*/
public interface HandlerMapping {
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
2. ApplicationObjectSupport
public abstract class ApplicationObjectSupport implements ApplicationContextAware {
/** Logger that is available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
/** ApplicationContext this object runs in. */
@Nullable
private ApplicationContext applicationContext;
@Override
public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
if (context == null && !isContextRequired()) {
// Reset internal context state.
this.applicationContext = null;
this.messageSourceAccessor = null;
}
else if (this.applicationContext == null) {
// Initialize with passed-in context.
if (!requiredContextClass().isInstance(context)) {
throw new ApplicationContextException(
"Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
}
this.applicationContext = context;
this.messageSourceAccessor = new MessageSourceAccessor(context);
// 1... WebApplicationObjectSupport
initApplicationContext(context);
}
else {
// Ignore reinitialization if same context passed in.
if (this.applicationContext != context) {
throw new ApplicationContextException(
"Cannot reinitialize with different application context: current one is [" +
this.applicationContext + "], passed-in one is [" + context + "]");
}
}
}
protected void initApplicationContext(ApplicationContext context) throws BeansException {
// 1...
// BeanNameUrlHandlerMapping => AbstractDetectingUrlHandlerMapping
// RequestMappingHandlerMapping => AbstractHandlerMapping
initApplicationContext();
}
protected void initApplicationContext() throws BeansException {
}
}
3. WebApplicationObjectSupport
public abstract class WebApplicationObjectSupport extends ApplicationObjectSupport implements ServletContextAware {
@Nullable
private ServletContext servletContext;
/**
* Calls {@link #initServletContext(javax.servlet.ServletContext)} if the
* given ApplicationContext is a {@link WebApplicationContext}.
*/
@Override
protected void initApplicationContext(ApplicationContext context) {
// 1... ApplicationObjectSupport
super.initApplicationContext(context);
if (this.servletContext == null && context instanceof WebApplicationContext) {
this.servletContext = ((WebApplicationContext) context).getServletContext();
if (this.servletContext != null) {
initServletContext(this.servletContext);
}
}
}
}
4. AbstractHandlerMapping
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
implements HandlerMapping, Ordered, BeanNameAware {
// 默认处理器,使用的对象类型是Object,子类实现的时候使用HandlerMethod,HandlerExecutionChain等
@Nullable
private Object defaultHandler;
// url路径管理工具
private UrlPathHelper urlPathHelper = new UrlPathHelper();
// 基于ant进行path匹配,解决/user/{id}的场景
private PathMatcher pathMatcher = new AntPathMatcher();
// 初始化后的拦截器handlerInterceptor数组
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
/**
* 用于配置springmvc的拦截器,有两种设置方式,
* 1. 注册handlerMapping时通过属性设置
* 2. 通过子类的extendInterceptors钩子方法进行设置
* <p>
* 此集合并不会直接使用,而是通过initInterceptors方法按照类型分配到mappedInterceptors和adaptedInterceptors中进行使用
*/
private final List<Object> interceptors = new ArrayList<>();
/**
* Initializes the interceptors.
*
* @see #extendInterceptors(java.util.List)
* @see #initInterceptors()
*/
@Override
protected void initApplicationContext() throws BeansException {
// 1.. 空实现,交给子类实现,用于注册自定义的拦截器到interceptors中,目前暂无子类实现
extendInterceptors(this.interceptors);
// 2.. 扫描已注册的MappedInterceptor的Bean们,添加到adaptedInterceptors中
detectMappedInterceptors(this.adaptedInterceptors);
// 3.. 将interceptors初始化成 HandlerInterceptor类型,添加到adaptedInterceptors中
initInterceptors();
}
protected void extendInterceptors(List<Object> interceptors) {
}
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
// 扫描已注册的MappedInterceptor的Bean,添加到mappedInterceptors中
// 1. MappedInterceptor会根据请求路径做匹配,是否进行拦截
mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
// 1. 将interceptors初始化成HandlerInterceptor类型,添加到adaptedInterceptors中
// 注意,HandlerInterceptor无需进行路径匹配,直接拦截全部
this.adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
*
* @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 1... AbstractUrlHandlerMapping,RequestMappingInfoHandlerMapping。获得处理器(HandlerMethod、HandlerExecutionChain)
Object handler = getHandlerInternal(request);
// 获得不到,则使用默认处理器
if (handler == null) {
handler = getDefaultHandler();
}
// 99. 还是获得不到,则返回null
if (handler == null) {
return null;
}
// Bean name or resolved handler?
// 如果找到的处理器是String类型,则从Spring容器中找到对应的Bean作为处理器
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 2.. 创建HandlerExecutionChain对象(包含处理器和拦截器)
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
} else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
// 针对跨域请求的处理
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ?
this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// xmlController 已经封装 HandlerExecutionChain。@Controller 未封装
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// 请求路径
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
// 1. 添加拦截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
// 需要匹配,若路径匹配,则添加到 chain 中
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
// 2. 拦截器、请求匹配
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
// 无需匹配,直接添加到 chain 中
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
}
1. getHandlerExecutionChain()
<bean name="/ctl1" class="com.listao.controller.ctl.Ctl1"/>
只能写SpringMVC配置文件中
<bean name="/ctl1" class="com.listao.controller.ctl.Ctl1"/>
<mvc:interceptors>
<!-- 拦截具体请求 -->
<mvc:interceptor>
<mvc:mapping path="/ctl1"/>
<bean class="com.listao.interceptor.HandlerMappingInterceptor"/>
</mvc:interceptor>
<!-- 拦截所有请求 -->
<bean class="com.listao.interceptor.HandlerMappingInterceptor1"/>
</mvc:interceptors>
2. UrlPathHelper
public class UrlPathHelper {
private boolean alwaysUseFullPath = false;
public String getLookupPathForRequest(HttpServletRequest request) {
// 1..
String pathWithinApp = getPathWithinApplication(request);
// Always use full path within current servlet context?
if (this.alwaysUseFullPath) {
return pathWithinApp;
}
// Else, use path within current servlet mapping if applicable
String rest = getPathWithinServletMapping(request, pathWithinApp);
if (StringUtils.hasLength(rest)) { // skip
return rest;
}
else {
// 2.
return pathWithinApp;
}
}
public String getPathWithinApplication(HttpServletRequest request) {
String contextPath = getContextPath(request); // `/spring_mymvc`
String requestUri = getRequestUri(request); // `/spring_mymvc/userList`
// 1..
String path = getRemainingPath(requestUri, contextPath, true); // `/userList`
if (path != null) {
// Normal case: URI contains context path.
return (StringUtils.hasText(path) ? path : "/");
}
else {
return requestUri;
}
}
@Nullable
private String getRemainingPath(String requestUri, String mapping, boolean ignoreCase) {
int index1 = 0;
int index2 = 0;
for (; (index1 < requestUri.length()) && (index2 < mapping.length()); index1++, index2++) {
char c1 = requestUri.charAt(index1);
char c2 = mapping.charAt(index2);
if (c1 == ';') {
index1 = requestUri.indexOf('/', index1);
if (index1 == -1) {
return null;
}
c1 = requestUri.charAt(index1);
}
if (c1 == c2 || (ignoreCase && (Character.toLowerCase(c1) == Character.toLowerCase(c2)))) {
continue;
}
return null;
}
if (index2 != mapping.length()) {
return null;
}
else if (index1 == requestUri.length()) {
return "";
}
else if (requestUri.charAt(index1) == ';') {
index1 = requestUri.indexOf('/', index1);
}
return (index1 != -1 ? requestUri.substring(index1) : "");
}
}
6. BeanNameUrlHandlerMapping
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
/**
* Checks name and aliases of the given bean for URLs, starting with "/".
*/
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
// 如果是以 / 开头,添加到 urls
if (beanName.startsWith("/")) {
urls.add(beanName);
}
// 获得 beanName 的别名们,如果以 / 开头,则添加到 urls
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
}
1. AbsUrlHandlerMapping
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
/**
* 是否延迟加载处理器,默认关闭
*/
private boolean lazyInitHandlers = false;
/**
* 路径和处理器的映射
* <p>
* KEY:路径 lookupHandler(String, HttpServletRequest)
*/
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
// 调用另外一个重载方法完成注册
for (String urlPath : urlPaths) {
// 1..
registerHandler(urlPath, beanName);
}
}
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
// 如果handler是String类型并且没有设置lazyInitHandlers,则从springmvc容器中获取handler
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
// 1. IOC#bean
resolvedHandler = applicationContext.getBean(handlerName);
}
}
// 是否已经存在对应的handler
Object mappedHandler = this.handlerMap.get(urlPath);
// 检验mappedHandler是否已存在,如果已存在,并且不是当前resolvedHandler对象,则抛出异常
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
} else {
// 如果是/根路径,则设置为rootHandler
if (urlPath.equals("/")) {
if (logger.isTraceEnabled()) {
logger.trace("Root mapping to " + getHandlerDescription(handler));
}
// "/"设置为rootHandler
setRootHandler(resolvedHandler);
}
// 如果是/*路径,则设置为默认处理器
else if (urlPath.equals("/*")) {
if (logger.isTraceEnabled()) {
logger.trace("Default mapping to " + getHandlerDescription(handler));
}
// 对"/*"的匹配设置默认的handler
setDefaultHandler(resolvedHandler);
} else {
// 2. 其余的路径绑定关系则存入handlerMap中
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isTraceEnabled()) {
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
// --------------------------------------------------------------------------------
/**
* Look up a handler for the URL path of the given request.
*
* @param request current HTTP request
* @return the handler instance, or {@code null} if none found
*/
@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
// 1... UrlPathHelper 截取用于匹配的URL有效路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
// 2.. 根据路径寻找handler,此处并不是直接从map中获取,很多handler都有通配符的写法,甚至有多个匹配项,此时需要做好选择
Object handler = lookupHandler(lookupPath, request);
// 如果找不到处理器,则使用rootHandler或defaultHandler处理器
if (handler == null) { // skip
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
// 如果是根路径,则使用rootHandler处理器
if (StringUtils.matchesCharacter(lookupPath, '/')) {
// 如果请求的路径仅仅是“/”,那么使用RootHandler进行处理
rawHandler = getRootHandler();
}
// 如果无法找到handler,则使用默认的handler
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
// 如果找到的处理器是String类型,则从容器中找到该beanName对应的Bean作为处理器
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);
}
// 空方法,校验处理器。目前暂无子类实现该方法
validateHandler(rawHandler, request);
// 创建处理器(HandlerExecutionChain对象)
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
return handler;
}
@Nullable
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
// 1. 直接根据url进行查找handler
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
// 如果找到的处理器是String类型,则从容器中找到该beanName对应的Bean作为处理器
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 空方法,校验处理器。目前暂无子类实现该方法
validateHandler(handler, request);
// 2.. 创建处理器
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
// 通过表达式进行匹配具体通过antPathMatcher实现
List<String> matchingPatterns = new ArrayList<>();
// 情况二,Pattern匹配合适的,并添加到 matchingPatterns 中
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) { // match
// 路径通过Pattern匹配成功
matchingPatterns.add(registeredPattern);
} else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
// 获得首个匹配(最优)的结果
String bestMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) { // skip
// 排序
matchingPatterns.sort(patternComparator);
if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
logger.trace("Matching patterns " + matchingPatterns);
}
bestMatch = matchingPatterns.get(0);
}
if (bestMatch != null) { // skip
// 获得bestMatch对应的处理器
handler = this.handlerMap.get(bestMatch);
if (handler == null) {
if (bestMatch.endsWith("/")) {
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
}
// 如果获得不到,抛出IllegalStateException异常
if (handler == null) {
throw new IllegalStateException(
"Could not find handler for best pattern match [" + bestMatch + "]");
}
}
// Bean name or resolved handler?
// 如果找到的处理器是String类型,则从容器中找到该beanName对应的Bean作为处理器
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 空方法,校验处理器。目前暂无子类实现该方法
validateHandler(handler, request);
// 获得最匹配的路径
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
// 通过sort方法进行排序,第一个作为bestPatternMatch,不过有可能有多个pattern的顺序相同,也就是sort方法返回0,这里就是处理这种情况
Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
logger.trace("URI variables " + uriTemplateVariables);
}
// 创建处理器
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {
// 1. 创建HandlerExecutionChain对象
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
// 2. 添加PathExposingHandlerInterceptor拦截器,到chain中
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
// 3. 添加UriTemplateVariablesHandlerInterceptor拦截器,到chain中
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {
// 最佳匹配的路径
private final String bestMatchingPattern;
// 被匹配的路径
private final String pathWithinMapping;
public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) {
this.bestMatchingPattern = bestMatchingPattern;
this.pathWithinMapping = pathWithinMapping;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, handler);
request.setAttribute(INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
return true;
}
}
private class UriTemplateVariablesHandlerInterceptor extends HandlerInterceptorAdapter {
private final Map<String, String> uriTemplateVariables;
public UriTemplateVariablesHandlerInterceptor(Map<String, String> uriTemplateVariables) {
this.uriTemplateVariables = uriTemplateVariables;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposeUriTemplateVariables(this.uriTemplateVariables, request);
return true;
}
}
}
2. AbsDetectingUrlHandlerMapping
public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping {
/**
* Calls the {@link #detectHandlers()} method in addition to the
* superclass's initialization.
*/
@Override
public void initApplicationContext() throws ApplicationContextException {
// 1... AbstractHandlerMapping
super.initApplicationContext();
// 2.. 自动探测处理器
detectHandlers();
}
protected void detectHandlers() throws BeansException {
// 从spring上下文获取所有Object类型的bean名称
ApplicationContext applicationContext = obtainApplicationContext();
// 1. 获取容器中所有bean的名字
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
// 对每一个beanName解析url,如果能解析到就注册到父类的map中
for (String beanName : beanNames) {
// 2... BeanNameUrlHandlerMapping 使用beanName解析url,模板方法,有子类实现
String[] urls = determineUrlsForHandler(beanName);
// 如果该bean存在对应的url,则添加该处理器
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
// 3... AbstractUrlHandlerMapping 调用父类的方法,往handlerMap中添加注册器
registerHandler(urls, beanName);
}
}
if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
}
}
}
7. RequestMappingHandlerMapping
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware {
/**
* RequestMappingInfo 的构建器
*/
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {
// 构建 RequestMappingInfo.BuilderConfiguration 对象
this.config = new RequestMappingInfo.BuilderConfiguration();
// 设置urlPathHelper默认为UrlPathHelper.class
this.config.setUrlPathHelper(getUrlPathHelper());
// 默认为AntPathMatcher,路径匹配校验器
this.config.setPathMatcher(getPathMatcher());
// 是否支持后缀补充,默认为true
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
// 是否添加"/"后缀,默认为true
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
// 是否采用mediaType匹配模式,比如.json/ .xml模式的匹配,默认为false
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
// mediaType处理类
this.config.setContentNegotiationManager(getContentNegotiationManager());
// 1... AbstractHandlerMethodMapping
super.afterPropertiesSet();
}
/**
* Uses method and type-level @{@link RequestMapping} annotations to create
* the RequestMappingInfo.
*
* @return the created RequestMappingInfo, or {@code null} if the method
* does not have a {@code @RequestMapping} annotation.
* @see #getCustomMethodCondition(Method)
* @see #getCustomTypeCondition(Class)
*/
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 1.. @RequestMapping创建RequestMappingInfo
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 基于类上的 @RequestMapping 注解,合并进去
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
// 如果有前缀,则设置到 info 中
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
/**
* Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
* supplying the appropriate custom {@link RequestCondition} depending on whether
* the supplied {@code annotatedElement} is a class or method.
*
* @see #getCustomTypeCondition(Class)
* @see #getCustomMethodCondition(Method)
*/
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
// 1. @RequestMapping
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
// 获得自定义的条件。目前都是空方法,可以无视
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
// 2..
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
/**
* Create a {@link RequestMappingInfo} from the supplied
* {@link RequestMapping @RequestMapping} annotation, which is either
* a directly declared annotation, a meta-annotation, or the synthesized
* result of merging annotation attributes within an annotation hierarchy.
*/
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
// 1. 创建RequestMappingInfo.Builder对象,设置对应属性
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
// 2. RequestMappingInfo
return builder.options(this.config).build();
}
@Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
// 1. AbstractHandlerMethodMapping
super.registerHandlerMethod(handler, method, mapping);
updateConsumesCondition(mapping, method);
}
private void updateConsumesCondition(RequestMappingInfo info, Method method) {
ConsumesRequestCondition condition = info.getConsumesCondition();
if (!condition.isEmpty()) {
for (Parameter parameter : method.getParameters()) {
// 1.
MergedAnnotation<RequestBody> annot = MergedAnnotations.from(parameter).get(RequestBody.class);
if (annot.isPresent()) {
condition.setBodyRequired(annot.getBoolean("required"));
break;
}
}
}
}
}
1. AbsHandlerMethodMapping
/**
* 抽象的基础类通过map的映射关系来存储request和handlerMethod之间的关系
* Abstract base class for {@link HandlerMapping} implementations that define
* a mapping between a request and a {@link HandlerMethod}.
*
* <p>For each registered handler method, a unique mapping is maintained with
* subclasses defining the details of the mapping type {@code <T>}.
*
* @param <T> the mapping for a {@link HandlerMethod} containing the conditions
* needed to match the handler method to an incoming request.
* 此泛型类型用来代表匹配handler的条件专门使用的一种类,这里的条件不只是url,还可以是其他条件
* eg:请求方式,请求参数等都可以作为条件,默认使用的RequestMappingInfo
* @since 3.1
*/
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
private final MappingRegistry mappingRegistry = new MappingRegistry();
// Handler method detection
/**
* Detects handler methods at initialization.
*
* @see #initHandlerMethods
*/
@Override
public void afterPropertiesSet() {
// 1.. 初始化处理器的方法们
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
*
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
// 遍历Bean,逐个处理
for (String beanName : getCandidateBeanNames()) {
// 排除目标代理类,AOP 相关,可查看注释
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { // scopedTarget.
// 1..
processCandidateBean(beanName);
}
}
// 初始化处理器的方法们,目前是空方法,暂无具体的实现
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
// 获得 Bean 对应的 Class 对象
Class<?> beanType = null;
try {
// 1.
beanType = obtainApplicationContext().getType(beanName);
} catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
// 2.. 判断Bean是否为处理器(@Controller、@RequestMapping)
if (beanType != null && isHandler(beanType)) {
// 3.. 扫描处理器方法
detectHandlerMethods(beanName);
}
}
@Override
protected boolean isHandler(Class<?> beanType) {
// 1. @Controller、@RequestMapping注解
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
/**
* Look for handler methods in the specified handler bean.
*
* @param handler either a bean name or an actual handler instance
* @see #getMappingForMethod
*/
protected void detectHandlerMethods(Object handler) {
// 获取handler的类型
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
// 1. 如果是cglib代理的子对象类型,则返回父类型,否则直接返回传入的类型
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 保存handler与匹配条件的对应关系,用于给registerHandlerMethod传入匹配条件
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// 2... RequestMappingHandlerMapping (@RequestMapping => RequestMappingInfo)
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));
}
// RequestMappingInfo 将符合要求的method注册起来,也就是保存到三个map中
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
// 3... RequestMappingHandlerMapping
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
// 1.. AbstractHandlerMethodMapping$MappingRegistry 内部类
this.mappingRegistry.register(mapping, handler, method);
}
/**
* Create the HandlerMethod instance.
*
* @param handler either a bean name or an actual handler instance
* @param method the target method
* @return the created HandlerMethod
*/
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
// 如果 handler 类型为 String, 说明对应一个 Bean 对象的名称
// 例如 UserController 使用 @Controller 注解后,默认入参 handler 就是它的 beanName ,即 `userController`
if (handler instanceof String) {
// 1...
return new HandlerMethod((String) handler,
obtainApplicationContext().getAutowireCapableBeanFactory(), method);
}
// 如果 handler 类型非 String ,说明是一个已经是一个 handler 对象,就无需处理,直接创建 HandlerMethod 对象
return new HandlerMethod(handler, method);
}
/**
* A registry that maintains all mappings to handler methods, exposing methods
* to perform lookups and providing concurrent access.
* <p>Package-private for testing purposes.
*/
class MappingRegistry {
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
private final MultiValueMap<String, T> urlLookup = 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();
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
Class<?>[] parameterTypes = method.getParameterTypes();
if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
}
// 获得写锁
this.readWriteLock.writeLock().lock();
try {
// 1.. 创建HandlerMethod对象
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 校验当前mapping是否存在对应的HandlerMethod对象,如果已存在但不是当前的handlerMethod对象则抛出异常
validateMethodMapping(handlerMethod, mapping);
// 将mapping与handlerMethod的映射关系保存至this.mappingLookup
this.mappingLookup.put(mapping, handlerMethod);
// 获得mapping对应的普通URL数组
List<String> directUrls = getDirectUrls(mapping);
// 将url和mapping的映射关系保存至this.urlLookup
for (String url : directUrls) {
// 2. (`/userList`, RequestMappingInfo)
this.urlLookup.add(url, mapping);
}
// 初始化nameLookup
String name = null;
if (getNamingStrategy() != null) {
// 获得Mapping的名字
name = getNamingStrategy().getName(handlerMethod, mapping);
// 将mapping的名字与HandlerMethod的映射关系保存至this.nameLookup
addMappingName(name, handlerMethod);
}
// 初始化CorsConfiguration配置对象
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) { // skip
this.corsLookup.put(handlerMethod, corsConfig);
}
// 3. 创建MappingRegistration对象
// 并与mapping映射添加到registry注册表中
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
} finally {
// 释放写锁
this.readWriteLock.writeLock().unlock();
}
}
private List<String> getDirectUrls(T mapping) {
List<String> urls = new ArrayList<>(1);
// 遍历 Mapping 对应的路径
for (String path : getMappingPathPatterns(mapping)) {
// 非**模式**路径
if (!getPathMatcher().isPattern(path)) {
urls.add(path);
}
}
return urls;
}
private void addMappingName(String name, HandlerMethod handlerMethod) {
// 获得 Mapping 的名字,对应的 HandlerMethod 数组
List<HandlerMethod> oldList = this.nameLookup.get(name);
if (oldList == null) {
oldList = Collections.emptyList();
}
// 如果已经存在,则不用添加
for (HandlerMethod current : oldList) {
if (handlerMethod.equals(current)) {
return;
}
}
// 添加到 nameLookup 中
List<HandlerMethod> newList = new ArrayList<>(oldList.size() + 1);
newList.addAll(oldList);
newList.add(handlerMethod);
// 1.
this.nameLookup.put(name, newList);
}
/**
* Return matches for the given URL path. Not thread-safe.
*
* @see #acquireReadLock()
*/
@Nullable
public List<T> getMappingsByUrl(String urlPath) {
// 1.
return this.urlLookup.get(urlPath);
}
/**
* Return all mappings and handler methods. Not thread-safe.
*
* @see #acquireReadLock()
*/
public Map<T, HandlerMethod> getMappings() {
return this.mappingLookup;
}
}
// ------------------------------------------------------------------------------------------------
// Handler method lookup
/**
* Look up a handler method for the given request.
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 1... 获取访问的路径,一般类似于request.getServletPath(),返回不含contextPath的访问路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
// 获得读锁
this.mappingRegistry.acquireReadLock();
try {
// 2.. 获取HandlerMethod作为handler对象,这里涉及到路径匹配的优先级
// 优先级: 精确匹配 > 最长路径匹配 > 扩展名匹配
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
// handlerMethod内部包含有bean对象,其实指的是对应的controller
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
} finally {
// 释放读锁
this.mappingRegistry.releaseReadLock();
}
}
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
// Match数组,存储匹配上当前请求的结果(Mapping + HandlerMethod)
List<Match> matches = new ArrayList<>();
// 1... AbstractHandlerMethodMapping$MappingRegistry 首先根据lookupPath获取到匹配条件 => RequestMappingInfo
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
// 2.. 将找到的匹配条件添加到matches
addMatchingMappings(directPathMatches, matches, request);
}
// 如果不能直接使用lookupPath得到匹配条件,则将所有匹配条件加入matches
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
// 将包含匹配条件和handler的matches排序,并取第一个作为bestMatch,如果前面两个排序相同则抛出异常
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
// 创建MatchComparator对象,排序matches结果,排序器
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
// 获得首个Match对象,也就是最匹配的
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
// 比较bestMatch和secondBestMatch,如果相等,说明有问题,抛出IllegalStateException异常
// 因为,两个优先级一样高,说明无法判断谁更优先
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
// 处理首个Match对象
handleMatch(bestMatch.mapping, lookupPath, request);
// 返回首个Match对象的handlerMethod属性
return bestMatch.handlerMethod;
}
// 如果匹配不到,则处理不匹配的情况
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
// 遍历 Mapping 数组
for (T mapping : mappings) {
// 1... RequestMappingInfoHandlerMapping 执行匹配
T match = getMatchingMapping(mapping, request);
if (match != null) {
// 如果匹配,则创建 Match 对象。(RequestMappingInfo, HandlerMethod)
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
}
1. MethodIntrospector
public final class MethodIntrospector {
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
final Map<Method, T> methodMap = new LinkedHashMap<>();
Set<Class<?>> handlerTypes = new LinkedHashSet<>();
Class<?> specificHandlerType = null;
if (!Proxy.isProxyClass(targetType)) {
specificHandlerType = ClassUtils.getUserClass(targetType);
handlerTypes.add(specificHandlerType);
}
handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
for (Class<?> currentHandlerType : handlerTypes) {
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
// 1... ReflectionUtils
ReflectionUtils.doWithMethods(currentHandlerType, method -> {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return methodMap;
}
}
2. ReflectionUtils
public abstract class ReflectionUtils {
/**
* 执行给定回调操作在给定类和父类(或者给定的接口或父接口)的所有匹配方法
* <p>
* Perform the given callback operation on all matching methods of the given
* class and superclasses (or given interface and super-interfaces).
* <p>The same named method occurring on subclass and superclass will appear
* twice, unless excluded by the specified {@link MethodFilter}.
*
* @param clazz the class to introspect
* @param mc the callback to invoke for each method
* @param mf the filter that determines the methods to apply the callback to
* @throws IllegalStateException if introspection fails
*/
public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) {
// 1. Keep backing up the inheritance hierarchy.
// 从缓存中获取clazz的所有声明的方法,包括它的所有接口中所有默认方法;没有时就从{@code clazz}中获取,再添加到缓存中,
Method[] methods = getDeclaredMethods(clazz, false);
// 遍历所有方法
for (Method method : methods) {
// 如果mf不为null 且 method不满足mf的匹配要求
if (mf != null && !mf.matches(method)) {
// 跳过该method
continue;
}
try {
// 2... AbstractHandlerMethodMapping 对method执行回调操作
mc.doWith(method);
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
}
}
// 如果clazz的父类不为nulll且(mf不是与未在{@code java.lang.Object}上声明的所有非桥接非合成方法匹配的预购建方法过滤器或者clazz的父类不为Object
if (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) {
// 递归方法
// 执行给定回调操作在clazz的父类的所有匹配方法, 子类和父类发生的相同命名方法将出现两次,
// 子类和父类发生的相同命名方法将出现两次,除非被mf排查
doWithMethods(clazz.getSuperclass(), mc, mf);
}
// 如果clazz是接口
else if (clazz.isInterface()) {
// 遍历clazz的所有接口
for (Class<?> superIfc : clazz.getInterfaces()) {
// 递归方法
// 执行给定回调操作在superIfc的所有匹配方法, 子类和父类发生的相同命名方法将出现两次,
// 子类和父类发生的相同命名方法将出现两次,除非被mf排查
doWithMethods(superIfc, mc, mf);
}
}
}
}
3. HandlerMethod
public class HandlerMethod {
private final Object bean;
// 此属性用于新建HandlerMethod时传入的Handler是String的情况,需要使用beanFactory根据传入的String作为beanName获取到对应的bean,并设置为Handler
@Nullable
private final BeanFactory beanFactory;
// bean类型
private final Class<?> beanType;
private final Method method;
// 如果method是bridge method,则设置为其所对应的原有方法,否则直接设置为method
private final Method bridgedMethod;
// 处理请求的方法的参数
private final MethodParameter[] parameters;
@Nullable
private HttpStatus responseStatus;
@Nullable
private String responseStatusReason;
/**
* Create an instance from a bean name, a method, and a {@code BeanFactory}.
* The method {@link #createWithResolvedBean()} may be used later to
* re-create the {@code HandlerMethod} with an initialized bean.
*/
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
Assert.hasText(beanName, "Bean name is required");
Assert.notNull(beanFactory, "BeanFactory is required");
Assert.notNull(method, "Method is required");
this.bean = beanName;
this.beanFactory = beanFactory;
Class<?> beanType = beanFactory.getType(beanName);
if (beanType == null) {
throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
}
this.beanType = ClassUtils.getUserClass(beanType);
this.method = method;
// 1.
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
// 2..
this.parameters = initMethodParameters();
evaluateResponseStatus();
this.description = initDescription(this.beanType, this.method);
}
private MethodParameter[] initMethodParameters() {
int count = this.bridgedMethod.getParameterCount();
MethodParameter[] result = new MethodParameter[count];
for (int i = 0; i < count; i++) {
result[i] = new HandlerMethodParameter(i);
}
return result;
}
/**
* A MethodParameter with HandlerMethod-specific behavior.
*/
protected class HandlerMethodParameter extends SynthesizingMethodParameter {
@Nullable
private volatile Annotation[] combinedAnnotations;
}
}
2. ReqMappingInfoHandlerMapping
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
private static final Method HTTP_OPTIONS_HANDLE_METHOD;
static {
try {
HTTP_OPTIONS_HANDLE_METHOD = HttpOptionsHandler.class.getMethod("handle");
} catch (NoSuchMethodException ex) {
// Should never happen
throw new IllegalStateException("Failed to retrieve internal handler method for HTTP OPTIONS", ex);
}
}
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
try {
// 1... AbstractHandlerMethodMapping
return super.getHandlerInternal(request);
} finally {
ProducesRequestCondition.clearMediaTypesAttribute(request);
}
}
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
// 1... RequestMappingInfo => 新RequestMappingInfo
return info.getMatchingCondition(request);
}
}
1. RequestMappingInfo
/**
* 此类使用7个变量来保存7种类型的RequestCondition对象
* <p>
* Request mapping information. Encapsulates the following request mapping conditions:
* <ol>
* <li>{@link PatternsRequestCondition}
* <li>{@link RequestMethodsRequestCondition}
* <li>{@link ParamsRequestCondition}
* <li>{@link HeadersRequestCondition}
* <li>{@link ConsumesRequestCondition}
* <li>{@link ProducesRequestCondition}
* <li>{@code RequestCondition} (optional, custom request condition)
* </ol>
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
private static final PatternsRequestCondition EMPTY_PATTERNS = new PatternsRequestCondition();
private static final RequestMethodsRequestCondition EMPTY_REQUEST_METHODS = new RequestMethodsRequestCondition();
private static final ParamsRequestCondition EMPTY_PARAMS = new ParamsRequestCondition();
private static final HeadersRequestCondition EMPTY_HEADERS = new HeadersRequestCondition();
private static final ConsumesRequestCondition EMPTY_CONSUMES = new ConsumesRequestCondition();
private static final ProducesRequestCondition EMPTY_PRODUCES = new ProducesRequestCondition();
private static final RequestConditionHolder EMPTY_CUSTOM = new RequestConditionHolder(null);
// 名字
@Nullable
private final String name;
// 请求路径的条件
private final PatternsRequestCondition patternsCondition;
// 请求方法的条件
private final RequestMethodsRequestCondition methodsCondition;
// 请求参数的条件
private final ParamsRequestCondition paramsCondition;
// 请求头的条件
private final HeadersRequestCondition headersCondition;
// 可消费的 Content-Type 的条件
private final ConsumesRequestCondition consumesCondition;
// 可生产的 Content-Type 的条件
private final ProducesRequestCondition producesCondition;
// 自定义的条件
private final RequestConditionHolder customConditionHolder;
public RequestMappingInfo(@Nullable String name, @Nullable PatternsRequestCondition patterns,
@Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params,
@Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes,
@Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) {
this.name = (StringUtils.hasText(name) ? name : null);
// 3.
this.patternsCondition = (patterns != null ? patterns : EMPTY_PATTERNS);
this.methodsCondition = (methods != null ? methods : EMPTY_REQUEST_METHODS);
this.paramsCondition = (params != null ? params : EMPTY_PARAMS);
this.headersCondition = (headers != null ? headers : EMPTY_HEADERS);
this.consumesCondition = (consumes != null ? consumes : EMPTY_CONSUMES);
this.producesCondition = (produces != null ? produces : EMPTY_PRODUCES);
this.customConditionHolder = (custom != null ? new RequestConditionHolder(custom) : EMPTY_CUSTOM);
this.hashCode = calculateHashCode(
this.patternsCondition, this.methodsCondition, this.paramsCondition, this.headersCondition,
this.consumesCondition, this.producesCondition, this.customConditionHolder);
}
/**
* Checks if all conditions in this request mapping info match the provided request and returns
* a potentially new request mapping info with conditions tailored to the current request.
* <p>For example the returned instance may contain the subset of URL patterns that match to
* the current request, sorted with best matching patterns on top.
*
* @return a new instance in case all conditions match; or {@code null} otherwise
*/
@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
// 匹配 methodsCondition、paramsCondition、headersCondition、consumesCondition、producesCondition
// 如果任一为空,则返回 null ,表示匹配失败
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
if (methods == null) {
return null;
}
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
if (params == null) {
return null;
}
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
if (headers == null) {
return null;
}
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
if (consumes == null) {
return null;
}
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (produces == null) {
return null;
}
// 4. PatternsRequestCondition
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
/* 5.
* 创建匹配的 RequestMappingInfo 对象
* 为什么要创建 RequestMappingInfo 对象呢?
*
* 因为当前 RequestMappingInfo 对象,一个 methodsCondition 可以配置 GET、POST、DELETE 等等条件,
* 但是实际就匹配一个请求类型,此时 methods 只代表其匹配的那个。
*/
return new RequestMappingInfo(this.name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
private static class DefaultBuilder implements Builder {
@Override
@SuppressWarnings("deprecation")
public RequestMappingInfo build() {
// 1.
PatternsRequestCondition patternsCondition = ObjectUtils.isEmpty(this.paths) ? null :
new PatternsRequestCondition(
this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
this.options.getFileExtensions());
ContentNegotiationManager manager = this.options.getContentNegotiationManager();
// 2.
return new RequestMappingInfo(this.mappingName, patternsCondition,
ObjectUtils.isEmpty(this.methods) ?
null : new RequestMethodsRequestCondition(this.methods),
ObjectUtils.isEmpty(this.params) ?
null : new ParamsRequestCondition(this.params),
ObjectUtils.isEmpty(this.headers) ?
null : new HeadersRequestCondition(this.headers),
ObjectUtils.isEmpty(this.consumes) && !this.hasContentType ?
null : new ConsumesRequestCondition(this.consumes, this.headers),
ObjectUtils.isEmpty(this.produces) && !this.hasAccept ?
null : new ProducesRequestCondition(this.produces, this.headers, manager),
this.customCondition);
}
}
}
2. PatternsRequestCondition
public class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {
private final static Set<String> EMPTY_PATH_PATTERN = Collections.singleton("");
/**
* 需要匹配的路径
*/
private final Set<String> patterns;
/**
* 路径匹配器 AntPathMatcher
*/
private final PathMatcher pathMatcher;
/**
* 路径解析器 UrlPathHelper
*/
private final UrlPathHelper pathHelper;
/**
* Creates a new instance with the given URL patterns. Each pattern that is
* not empty and does not start with "/" is prepended with "/".
* @param patterns 0 or more URL patterns; if 0 the condition will match to
* every request.
*/
public PatternsRequestCondition(String... patterns) {
this(patterns, null, null, true, true, null);
}
@Deprecated
public PatternsRequestCondition(String[] patterns, @Nullable UrlPathHelper urlPathHelper,
@Nullable PathMatcher pathMatcher, boolean useSuffixPatternMatch,
boolean useTrailingSlashMatch, @Nullable List<String> fileExtensions) {
// 1. 需要匹配的路径表达式集合,默认会加上 `/` 开头
this.patterns = initPatterns(patterns);
// 路径解析器 UrlPathHelper
this.pathHelper = urlPathHelper != null ? urlPathHelper : UrlPathHelper.defaultInstance;
// 路径匹配器 AntPathMatcher
this.pathMatcher = pathMatcher != null ? pathMatcher : new AntPathMatcher();
this.useSuffixPatternMatch = useSuffixPatternMatch;
this.useTrailingSlashMatch = useTrailingSlashMatch;
if (fileExtensions != null) {
for (String fileExtension : fileExtensions) {
if (fileExtension.charAt(0) != '.') {
fileExtension = "." + fileExtension;
}
this.fileExtensions.add(fileExtension);
}
}
}
private static Set<String> initPatterns(String[] patterns) {
if (!hasPattern(patterns)) {
return EMPTY_PATH_PATTERN;
}
Set<String> result = new LinkedHashSet<>(patterns.length);
for (String pattern : patterns) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
// 1. 没有以 `/` 开头则加上
pattern = "/" + pattern;
}
result.add(pattern);
}
return result;
}
private static boolean hasPattern(String[] patterns) {
if (!ObjectUtils.isEmpty(patterns)) {
for (String pattern : patterns) {
if (StringUtils.hasText(pattern)) {
return true;
}
}
}
return false;
}
@Override
@Nullable
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
// 通过路径解析器 UrlPathHelper 获取请求路径
String lookupPath = this.pathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
// 1.. 获取该条件中匹配请求路径的路径
List<String> matches = getMatchingPatterns(lookupPath);
// 如果不为空表示匹配,符合条件,则返回 PatternsRequestCondition 对象
return !matches.isEmpty() ? new PatternsRequestCondition(new LinkedHashSet<>(matches), this) : null;
}
public List<String> getMatchingPatterns(String lookupPath) {
List<String> matches = null;
for (String pattern : this.patterns) {
// 2..
String match = getMatchingPattern(pattern, lookupPath);
if (match != null) {
matches = (matches != null ? matches : new ArrayList<>());
matches.add(match);
}
}
if (matches == null) {
return Collections.emptyList();
}
if (matches.size() > 1) {
matches.sort(this.pathMatcher.getPatternComparator(lookupPath));
}
return matches;
}
@Nullable
private String getMatchingPattern(String pattern, String lookupPath) {
// 3.
if (pattern.equals(lookupPath)) {
// 直接相等,匹配,则直接返回
return pattern;
}
if (this.useSuffixPatternMatch) {
if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {
for (String extension : this.fileExtensions) {
if (this.pathMatcher.match(pattern + extension, lookupPath)) {
return pattern + extension;
}
}
}
else {
boolean hasSuffix = pattern.indexOf('.') != -1;
if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
return pattern + ".*";
}
}
}
// 通过路径匹配器 AntPathMatcher 判断是否匹配
if (this.pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
if (this.useTrailingSlashMatch) {
if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
return pattern + "/";
}
}
return null;
}
}
1. RequestCondition
/**
* 此接口专门用于保存从request提取出的用于匹配handler的条件
* <p>
* Contract for request mapping conditions.
*
* <p>Request conditions can be combined via {@link #combine(Object)}, matched to
* a request via {@link #getMatchingCondition(HttpServletRequest)}, and compared
* to each other via {@link #compareTo(Object, HttpServletRequest)} to determine
* which is a closer match for a given request.
*
* @param <T> the type of objects that this RequestCondition can be combined
* with and compared to
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @since 3.1
*/
public interface RequestCondition<T> {
T combine(T other);
@Nullable
T getMatchingCondition(HttpServletRequest request);
int compareTo(T other, HttpServletRequest request);
}