MVC
首先引用《Spring in Action》上 的一张图来了解 Spring MVC 的核心组件和大致处理流程:
从上图中看到
- DispatcherServlet 是 SpringMVC 中的前端控制器(Front Controller), 负责接收 Request 并将 Request 转发给对应的处理组件。
- HanlerMapping 是 SpringMVC 中完成 url 到 Controller 映射的组件。 DispatcherServlet 接收 Request,然后从 HandlerMapping 查找处理 Request 的 Controller。
- Controller 处理 Request,并返回 ModelAndView 对象,Controller 是 SpringMVC 中负责处理 Request 的组件(类似于 Struts2 中的 Action),ModelAndView 是封装结果 视图的组件。
- 第4,5,6步视图解析器解析 ModelAndView 对象并返回对应的视图给客户端。
容器初始化时会建立所有 url 和 Controller 中的 Method 的对应关系,保存到 HandlerMapping 中,用户请求是根据 Request 请求的 url 快速定位到 Controller 中的某个方法。在 Spring 中先将 url 和 Controller 的对应关 系,保存到 Map<url,Controller>中。Web 容器启动时会通知 Spring 初始化容器(加载 Bean 的定义信息和初始化所有单例 Bean),然后 SpringMVC 会遍历容器中的 Bean,获 取每一个 Controller 中的所有方法访问的 url,然后将 url 和 Controller 保存到一个 Map 中;这样就可以根据 Request 快速定位到 Controller,因为最终处理 Request 的是 Controller 中的方法,Map 中只保留了 url 和 Controller 中的对应关系,所以要根据 Request 的 url 进一步确认 Controller 中的 Method,这一步工作的原理就是拼接 Controller 的 url(Controller 上@RequestMapping 的值)和方法的 url(Method 上 @RequestMapping 的值),与 request 的 url 进行匹配,找到匹配的那个方法;确定处 理请求的 Method 后,接下来的任务就是参数绑定,把 Request 中参数绑定到方法的形 式参数上,这一步是整个请求处理过程中最复杂的一个步骤。
源码分析
根据上面分析的 Spring MVC 工作机制,从三个部分来分析 Spring MVC 的源代码。 其一,ApplicationContext 初始化时用 Map 保存所有 url 和 Controller 类的对应关系; 其二,根据请求 url 找到对应的 Controller,并从 Controller 中找到处理请求的方法; 其三,Request 参数绑定到方法的形参,执行方法处理请求,并返回结果视图。
初始化阶段
我们首先找到 DispatcherServlet 这个类,必然是寻找 init()方法。然后,我们发现其 init 方法其实在父类 HttpServletBean 中,其源码如下:
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
这段代码中最主要的逻辑就是初始化 IOC 容器,最终会调用 refresh()方法.
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
//本地文件上传
initMultipartResolver(context);
//本地化解析
initLocaleResolver(context);
//主题解析
initThemeResolver(context);
//通过HandlerMapping,将请求映射到处理器
initHandlerMappings(context);
//通过HandlerAdapter进行多类型的参数动态匹配
initHandlerAdapters(context);
//如果执行过程中遇到异常,将交给HandlerExceptionResolver来解析
initHandlerExceptionResolvers(context);
//直接解析请求到视图名
initRequestToViewNameTranslator(context);
//通过viewResolver解析逻辑视图到具体视图实现
initViewResolvers(context);
//flash映射管理器
initFlashMapManager(context);
}
到这一步就完成了 Spring MVC 的九大组件的初始化。接下来,我们来看 url 和 Controller 的 关 系 是 如 何 建 立 的 呢 ? HandlerMapping 的 子 类 AbstractDetectingUrlHandlerMapping 实现了 initApplicationContext()方法,所以 我们直接看子类中的初始化容器方法。
@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
detectHandlers();
}
protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
for (String beanName : beanNames) {
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
registerHandler(urls, beanName);
}
}
if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
}
}
运行调用阶段
这一步步是由请求触发的,所以入口为 DispatcherServlet 的核心方法为 doService(), doService()中的核心逻辑由 doDispatch()实现,源代码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 2.取得处理当前请求的 Controller,这里也称为 hanlder,处理器,
// 第一个步骤的意义就在这里体现了.这里并不是直接返回 Controller,
// 而是返回的 HandlerExecutionChain 请求处理器链对象,
// 该对象封装了 handler 和 interceptors.
mappedHandler = getHandler(processedRequest);
// 如果 handler 为空,则返回 404
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//3. 获取处理 request 的处理器适配器 handler adapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//4.实际的处理器处理请求,返回结果视图对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
getHandler(processedRequest)方法实际上就是从 HandlerMapping 中找到 url 和 Controller 的对应关系。也就是 Map<url,Controller>。我们知道,最终处理 Request 的是 Controller 中的方法,我们现在只是知道了 Controller,我们如何确认 Controller 中处理 Request 的方法呢?继续往下看。
从 Map<urls,beanName>中取得 Controller 后,经过拦截器的预处理方法,再通过反 射获取该方法上的注解和参数,解析方法和参数上的注解,然后反射调用方法获取ModelAndView 结果视图。最后,调用的就是 RequestMappingHandlerAdapter 的 handle()中的核心逻辑由 handleInternal(request, response, handler)实现。
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
通过上面的代码分析,已经可以找到处理 Request 的 Controller 中的方法了,现在看如 何解析该方法上的参数,并反射调用该方法。
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
invocableMethod.invokeAndHandle()最终要实现的目的就是:完成 Request 中的参 数和方法参数上数据的绑定。Spring MVC 中提供两种 Request 参数到方法中参数的绑 定方式:
1、通过注解进行绑定,@RequestParam。
2、通过参数名称进行绑定。
使用注解进行绑定,我们只要在方法参数前面声明@RequestParam(“name”),就可以 将 request 中参数 name 的值绑定到方法的该参数上。使用参数名称进行绑定的前提是 必须要获取方法中参数的名称,Java 反射只提供了获取方法的参数的类型,并没有提供 获取参数名称的方法。SpringMVC 解决这个问题的方法是用 asm 框架读取字节码文件, 来获取方法的参数名称。asm 框架是一个字节码操作框架,关于 asm 更多介绍可以参考 其官网。个人建议,使用注解来完成参数绑定,这样就可以省去 asm 框架的读取字节码 的操作。
调用时序图:
Spring MVC 使用优化建议
Controller 如果能保持单例,尽量使用单例
这样可以减少创建对象和回收对象的开销。也就是说,如果 Controller 的类变量和实例 变量可以以方法形参声明的尽量以方法的形参声明,不要以类变量和实例变量声明,这 样可以避免线程安全问题。
处理 Request 的方法中的形参务必加上@RequestParam 注解
这样可以避免 Spring MVC 使用 asm 框架读取 class 文件获取方法参数名的过程。即便 Spring MVC 对读取出的方法参数名进行了缓存,如果不要读取 class 文件当然是更好。
缓存 URL
阅读源码的过程中,我们发现 Spring MVC 并没有对处理 url 的方法进行缓存,也就是 说每次都要根据请求 url 去匹配 Controller 中的方法 url,如果把 url 和 Method 的关系缓存起来,会不会带来性能上的提升呢?有点恶心的是,负责解析 url 和 Method 对应 关系的 ServletHandlerMethodResolver 是一个 private 的内部类,不能直接继承该类 增强代码,必须要该代码后重新编译。当然,如果缓存起来,必须要考虑缓存的线程安 全问题。