要了解SpringMVC中数据是怎么绑定到Controller的参数上的之前我们需要知道SpringMVC是从哪里开始解析数据的。首先我们回顾下DispatcherServlet中的doDispatch中的如下代码:
mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
首先SpringMVC要充HandlerMapping中获取对应的HandlerExecutionChain类型的handler,那么这是怎么获取的呢?我们以SpringMVC使用的默认HandlerMapping即org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping为例进行讲解。
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<String>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = getApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
}
BeanNameUrlHandlerMapping仅仅实现了一个determineUrlsForHandler方法。在此之前它在doDispatch方法中被调用的getHandler方法是在父类AbstractHandlerMapping中实现的。
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//获取内部Handler
Object handler = getHandlerInternal(request);
//内部Handler不存在的情况下获取默认handler
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
//如果返回的是一个bean name则转化成一个bean
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
//根据获取的handler封装成一个执行链,这个执行链实际是一个由N个interceptor和一个controller组成的链式结构
return getHandlerExecutionChain(handler, request);
}
子类AbstractUrlHandlerMapping中的getHandlerInternal(HttpServletRequest)方法。
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
//没配置使用全路径的情况下获取相对路径如http://www.xxx.com/path/aaa/bbb的相对路径是/path/aaa/bbb
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//根据request的url获取handler,这里的handler就是一个controller。Spring的在创建ApplicationContext时会
//根据Controller的注解或bean定义以<url,controller>的键值对的形式将所有controller的路径映射信息保存到一个map中
//lookupHandler会利用url从map中get到这个controller
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
Object rawHandler = null;
//如果访问web应用首页映射到根目录处理器
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
//如果没有controller则使用默认controller
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
//如果是bean name转化成controller
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
//增加一个过滤器,用于暴露URI_TEMPLATE_VARIABLES_ATTRIBUTE属性
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
//省略代码若干...
return handler;
}
看完以上代码我们大致了解了在执行完getHandler后我们得到了一个由N个interceptor和一个controller构成的HandlerExecutionChain,接着我们要找到对应的HandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
//如果该种Adapter支持Handler则返回Adapter
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
它会挨个调用默认的Adapter的support方法判断是否支持该种handler如果支持则返回。返回Adapter后会直接调用handle方法获取ModelAndView。由于通常我们是使用注解来标示控制器的,这里我们以org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter为例来看看Adapter是怎么形式其责任的。
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//获取controller的class
Class<?> clazz = ClassUtils.getUserClass(handler);
//获取注解信息
Boolean annotatedWithSessionAttributes = this.sessionAnnotatedClassesCache.get(clazz);
if (annotatedWithSessionAttributes == null) {
annotatedWithSessionAttributes = (AnnotationUtils.findAnnotation(clazz, SessionAttributes.class) != null);
this.sessionAnnotatedClassesCache.put(clazz, annotatedWithSessionAttributes);
}
if (annotatedWithSessionAttributes) {
// Always prevent caching in case of session attribute management.
checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
// Prepare cached set of session attributes names.
}
else {
// Uses configured default cacheSeconds setting.
checkAndPrepare(request, response, true);
}
// 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) {
return invokeHandlerMethod(request, response, handler);
}
}
}
//调用注解下面的方法
return invokeHandlerMethod(request, response, handler);
}
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//获取方法处理器
ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
//获取controller相应的方法
Method handlerMethod = methodResolver.resolveHandlerMethod(request);
ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
//包装request和response
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ExtendedModelMap implicitModel = new BindingAwareModelMap();
//调用方法
Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
//返回ModelAndView对象
ModelAndView mav =
methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
return mav;
}
这样用户传入的url就正确地匹配到了具体的方法中了。