从最原始的需求说起:
如果是我们自己设计这样一个Web框架,应该考虑什么问题?
如果是我,我觉得最少应该解决如下的问题:
1、我们只配置了一个DispatcherServlet,所有被这个Servlet拦截到的请求,都会交给Spring MVC处理,
那么第一步就是要把请求映射到具体Controller的方法去处理。
2、找到对应的处理方法之后,是怎么样把请求端的请求转化成服务识别的参数?
本文将从各个疑问点出发,力求以简单的方式,分析Spring MVC在处理请求和参数分析两个方面做出扼要的叙述。
- 第一个疑问
可以细化为如下几个部分:
1、Spring MVC怎么知道我有哪些Controller?
2、Spring MVC怎样处理我提供的Controller?
3、Spring MVC是怎样将请求路径和具体的Controller映射起来的?
*从Spring3.1之开始,SpringMVC提供了「 <mvc:annotation-driven/>」来标注,表示启用注解驱动。
我们不需要继承Spring的接口和类,只需要添加注解提供映射路径,就可以将客户端的请求映射到类和方法上。*
如果想知道这个注解做了些什么事情,可以查看它对应的解析器:「 AnnotationDrivenBeanDefinitionParser」。抽出来主要关注的两行:
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
...
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
这两行,分别加载了「RequestMappingHandlerMapping」和「RequestMappingHandlerAdapter」两个类。
SpringMVC加载过程中,有一步为:
initHandlerMappings(context);
它是在处理请求时,直接获取已经加载完毕的「HandlerMapping」对象,
此处加载的「RequestMappingHandlerMapping」对象,也是其中一个。
「RequestMappingHandlerMapping」主要是为带有Controller注解的类解析类级别和方法级别的「@RequestMapping」注解配置。
它是「InitializingBean」的实现类,中间经过了实现和几次抽象继承。它主要是使用父类的afterPropertiesSet方法来初始化。
//方法路径:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping<T>.initHandlerMethods
/** * Scan beans in the ApplicationContext, detect and register handler methods. * @see #isHandler(Class) * @see #getMappingForMethod(Method, Class) * @see #handlerMethodsInitialized(Map) */
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
获取到上下文中已经加载的类对象,这里是指所有的对象。可以很清楚地看到它后面的处理逻辑,遍历所有的bean对象,查看是不是符合
isHandler方法过滤,如果符合,就查找beanName对应的Bean中的提供Spring MVC服务方法。
这个isHandler在「RequestMappingHandlerMapping」中实现:
/** 查看是不是有Controller或者RequestMapping注解 */
@Override
protected boolean isHandler(Class<?> beanType) {
return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}
而后会将类对象传递到detectHandlerMethods方法中,去方法和创建路径的映射,保存在对象:
final Map
/** * Look for handler methods in a handler. * @param handler the bean name of a handler or a handler instance */
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType =
(handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
// Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
@Override
public boolean matches(Method method) {
T mapping = getMappingForMethod(method, userType);
if (mapping != null) {
mappings.put(method, mapping);
return true;
}
else {
return false;
}
}
});
for (Method method : methods) {
registerHandlerMethod(handler, method, mappings.get(method));
}
}
「RequestMappingHandlerMapping」同样实现了一个getMappingForMethod方法,用来创建「RequestMappingInfo」对象,这个对象将类的请求路径、方法的请求路径、请求类型、请求支持的数据格式封装在一起。
最后,就是调用registerHandlerMethod方法。将Controller对象、方法对象、请求路径,注册在一起。
这个方法定义在「AbstractHandlerMethodMapping」之中。
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
"' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
}
this.handlerMethods.put(mapping, newHandlerMethod);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
}
Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
this.urlMap.add(pattern, mapping);
}
}
if (this.namingStrategy != null) {
String name = this.namingStrategy.getName(newHandlerMethod, mapping);
updateNameMap(name, newHandlerMethod);
}
}
简单来说,这里做了两件事:
将RequestMappingInfo对象和处理方法映射成Map对象键值对;
将请求路径和RequestMappingInfo对象映射城Map键值对;
所以,这里会将请求路径相同的方法给覆盖了,一个请求路径最多一个处理方法。
所以从上面的描述,可以得知:
「RequestMappingHandlerMapping」,是解析Controller和「RequestMapping」注解的,
它将Controller和方法的请求路径和处理方法,做了跳转映射,首先从请求路径映射到「RequestMappingInfo」,然后可以从「RequestMappingInfo」映射到处理方法。
追根溯源,可以找到「HandlerMapping」接口的说明,「HandlerMapping」是SpringMVC提供的一个基础抽象类,它规范了请求和处理对象之间的转化。它只提供一个名为getHandler的方法。
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
从Servlet请求中获取到处理的Handler。
到这里,应该可以解答之前的第一个问题了。
- 第二个疑问
找到对应的处理方法之后,是怎么样把请求端的请求转化成服务识别的参数?
同样,可以分解为如下的几个问题:
1、SpringMVC使用什么进行参数解析?
2、SpringMVC的是如何处理「HttpServletRequest」对象的?
为什么特意提到了「HttpServletRequest」,在DispatcherServlet中,能接收到的就只是「HttpServletRequest」请求。
SpringMVC如果需要解析参数,一定得从这个对象里面去查找。HTTP请求的本质其实还是字符串,当进入SpringMVC之后,这个参数解析的过程其实没有暴露给开发者,那就意味着SpringMVC需要自己提供很多的转换功能。
看看「RequestMappingHandlerAdapter」,其实从它的类说明就可以了解一些:
/**
* An {@link AbstractHandlerMethodAdapter} that supports {@link HandlerMethod}s
* with the signature -- method argument and return types, defined in
* {@code @RequestMapping}.
*
* <p>Support for custom argument and return value types can be added via
* {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
* Or alternatively to re-configure all argument and return value types use
* {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}.
*
* @author Rossen Stoyanchev
* @since 3.1
* @see HandlerMethodArgumentResolver
* @see HandlerMethodReturnValueHandler
*/
它是处理「RequestMapping」所解析HandlerMethod中参数和返回值。
它是「AbstractHandlerMethodAdapter」类的一个子类。它还允许设置customArgumentResolvers类注入用户自定义的参数解析器,
returnValueHandlers注入用户自定义的返回值解析器。
同样,从方法签名上,可以知道,这个类也是会在容器启动的时候进行初始化的,
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
首先,会去加载3.2版本才出现的Controller增强器,它提供参数绑定,异常等方面的辅助支持,暂不做分析。
接下来出现三个if判断,分别初始化的默认的参数解析器,initBinder参数解析器,返回值处理器。
值得一提的是,在SpringMVC处理列表对象的时候,一般都会提供一个包装的对象,然后在这个包装的对象里面进行处理。
比如此处的「HandlerMethodArgumentResolverComposite」就把解析器给封装了起来,然后把具体的逻辑写在里面,
更让人想不到的是,它本身也是「HandlerMethodArgumentResolver」的实现类。
重点看第一个,「HandlerMethodArgumentResolver」接口,它是一个将参数从request中转化为方法参数的规范。
将这个接口声明了所有参数解析器的实现方法:
boolean supportsParameter(MethodParameter parameter);
该解析器是否支持此种类型的参数,MethodParameter可以由之前说的「HandlerMethod」对象获取。
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
从解析方法的单个参数。
「ModelAndViewContainer」记录模型对象和视图对象之间的对应关系,暂时不分析这个。
「NativeWebRequest」是对最原始的请求(例如:「HttpServletRequest」、「javax.portlet.ActionRequest」等等)的包装类。
SpringMVC将底层请求对象进行了包装,这样就能尽可能地屏蔽底层请求的差异性,从而使得更具有通用性,最常见的还是
「HttpServletRequest」。
从这两个方法,其实可以猜想一下,Spring MVC处理参数转化的方式:
通过supportsParameter方法决定由哪个「HandlerMethodArgumentResolver」实现类来处理当前参数;
通过resolveArgument方法来做具体参数转换逻辑;
要验证这个想法,看看具体一个实现:「RequestResponseBodyMethodProcessor」
/**
* Resolves method arguments annotated with {@code @RequestBody} and handles
* return values from methods annotated with {@code @ResponseBody} by reading
* and writing to the body of the request or response with an
* {@link HttpMessageConverter}.
*
* <p>An {@code @RequestBody} method argument is also validated if it is
* annotated with {@code @javax.validation.Valid}. In case of validation
* failure, {@link MethodArgumentNotValidException} is raised and results
* in a 400 response status code if {@link DefaultHandlerExceptionResolver}
* is configured.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
从类注释中可以得知,这个解析器,不单能解析参数被标注为@RequestBody的参数,还可以处理返回值添加@ResponseBody注解的方法,还可以处理添加了@Validate的参数校验。
supportsParameter方法很简单,主要看看resolveArgument方法。
其中有一句:
Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
这是一个很重要的方法,它将「NativeWebRequest」对象封装的「HttpServletRequest」重新解放出来,再次构造成SpringMVC的消息对象「HttpInputMessage」,再获取到InputStream对象进行参数获取:
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest,
MethodParameter methodParam, Type paramType) throws IOException, HttpMediaTypeNotSupportedException {
final HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);
InputStream inputStream = inputMessage.getBody();
if (inputStream == null) {
return handleEmptyBody(methodParam);
}
else if (inputStream.markSupported()) {
inputStream.mark(1);
if (inputStream.read() == -1) {
return handleEmptyBody(methodParam);
}
inputStream.reset();
}
else {
final PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
int b = pushbackInputStream.read();
if (b == -1) {
return handleEmptyBody(methodParam);
}
else {
pushbackInputStream.unread(b);
}
inputMessage = new ServletServerHttpRequest(servletRequest) {
@Override
public InputStream getBody() throws IOException {
// Form POST should not get here
return pushbackInputStream;
}
};
}
return super.readWithMessageConverters(inputMessage, methodParam, paramType);
}
SpringMVC内部对 「HttpServletRequest」 进行了封装,这使得实现「ServerHttpRequest」接口的「ServletServerHttpRequest」类,变成了结合了原生HttpServletRequest的功能和Spring MVC定义的操作方法,比如:
InetSocketAddress getLocalAddress();等等
private final HttpServletRequest servletRequest;
这种设计方式,将各种不同类型的请求进行了很好的屏蔽。
「RequestMappingHandlerAdapter」 将会在请求第一次到达「DispatcherServlet 」时在 initHandlerAdapters(context){…}被加载。
最后会调用到invokeHandleMethod方法,完成对方法的调用。
Spring MVC使用「HandlerAdapter」进行参数解析,每个「HandlerAdapter」对象都有一个支持的参数解析器,每个解析器都有着对应的使用场景和解析方式,在解析完参数之后,「HandlerAdapter」的handle方法将触发,并返回ModelAndView对象。
其实「DispatcherServlet 」的doDispatch方法,可以理解为分发给不同的「HandlerMapping 」对象(例如:「RequestMappingHandlerMapping 」)所持有的Handler引用。并且由此找到对应的「HandlerAdapter」对象(例如:「RequestMappingHandlerAdapter」)。
如有纰漏,欢迎指正
-EOF-