1.序言
上一篇文章深入分析JavaWeb---第14章 SpringMVC的工作机制从思想层面,粗粒度的介绍了SpringMVC的几个核心关键点。这个文章从代码层面,细粒度介绍细节。
2.1 SpringMVC初始化时序图
额外说明:
时序1.HttpServlet初始化调用子类HttpServletBean的init方法。这个方法的作用:获取Servlet中的init参数,并创建一个BeanWrapper对象,然后由子类真正执行BeanWrapper的初始化工作。
时序3.Spring容器的创建是在FrameworkServlet的initServletBean()方法中完成的,这个方法会创建WebApplicationContext对象。
时序6.Spring容器在加载时,会调用DispatcherServlet的initStrategies方法完成:SpringMVC框架需要的8个组件。具体细节可见上篇文章.
一句话总结:SpringMVC初始化工作,创建了WebApplicationContext对象。Spring容器创建时调用onApplicationEvent事件,调用onRefresh方法。在onRefresh方法之中,会去调用DispatcherServlet的initStrategies()方法:初始化HandlerMapping、HandlerAdapter、ViewResolver等8个组件。
2.2 HandlerMapping初始化
1.HandlerMapping是如何将URL映射到具体bean上,执行具体业务逻辑代码的?
答:HandlerMapping并没有规定这个URL与应用的处理类如何映射。在HandlerMapping接口中只定义了根据一个URL必须返回一个由HandlerExecutionChain代表的处理链,在这个处理链中可以添加任意的HandlerAdapters实例处理这个URL对应的请求。这种设计思路便是“职责链设计模式”的具体实现。
HandlerMapping中抽象方法,如下所示:
/**
* Return a handler and any interceptors for this request.
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
2.SpringMVC提供的有关HandlerMapping的类结构图
SpringMVC提供了一个HandlerMapping的抽象类AbstractHandlerMapping,而1>AbstractHandlerMapping实现了Ordered接口:可以让HandlerMapping通过设置setOrder方法提高初始化优先级;
/**
* Specify the order value for this HandlerMapping bean.
* Default value is {@code Integer.MAX_VALUE}, meaning that it's non-ordered.
* @see org.springframework.core.Ordered#getOrder()
*/
public final void setOrder(int order) {
this.order = order;
}
2>AbstractHandlerMapping同时还继承了WebApplicationObjectSupport类:可以通过重写initApplicationContext方法实现初始化的一些工作。
/**
* Initializes the interceptors.
* @see #extendInterceptors(java.util.List)
* @see #initInterceptors()
*/
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
}
3.以SimpleUrlHandlerMapping这个具体实现类,来探究初始化细节
额外说明:
时序3:将AbstractHandlerMapping中定义的List
扩充:AbstractHandlerMapping中定义的List
涉及到的源代码,如下所示:
private final List
时序5:将SimpleUrlHandlerMapping类中定义的Map
直白来讲就是:将urlMap中url对应的handler类名,通过ApplicationContext().getBean(handlerName);实例化出来,同时保存handlerMap中。
涉及到的源代码,如下所示:
SimpleUrlHandlerMapping中的方法和属性:
private final Map urlMap = new LinkedHashMap();
/**
* Register all handlers specified in the URL map for the corresponding paths.
* @param urlMap Map with URL paths as keys and handler beans or bean names as values
* @throws BeansException if a handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandlers(Map urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
for (Map.Entry entry : urlMap.entrySet()) {
String url = entry.getKey();
Object handler = entry.getValue();
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
registerHandler(url, handler); //-------核心点----------
}
}
}
AbstractUrlHandlerMapping中的方法和属性:
private final Map handlerMap = new LinkedHashMap();
/**
* Register the specified handler for the given URL path.
* @param urlPath the URL the bean should be mapped to
* @param handler the handler instance or handler bean name String
* (a bean name will automatically be resolved into the corresponding handler bean)
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
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.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = getApplicationContext().getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isInfoEnabled()) {
logger.info("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isInfoEnabled()) {
logger.info("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler); //-------核心点----------
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
一句话总结:HandlerMapping初始化,将所有的interceptors包装成HandlerInterceptor对象,同时保存在adaptedInteceptors列表中;将urlMap集合中url对应的handler实例化出来,同时保存在handlerMap中。
2.3 HandlerAdapter初始化
HandlerMapping可以完成URL与Handler的映射关系。因为SpringMVC首先帮助我们把URL对应到一个Handler,那么这个Handler必定符合某种规则:最常见的办法就是我们所有的Handler都继承某个接口。
1.HanderAdapter接口源代码:
其中handle方法是处理业务逻辑的核心方法。
public interface HandlerAdapter {
/**
* 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.
* A typical implementation:
*
{@code
* return (handler instanceof MyHandler);
* }
* @param handler handler object to check
* @return whether or not this object can use the given handler
*/
boolean supports(Object handler);
/**
* 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 handler to use. This object must have previously been passed
* to the {@code supports} method of this interface, which must have
* returned {@code true}.
* @throws Exception in case of errors
* @return ModelAndView object with the name of the view and the required
* model data, or {@code null} if the request has been handled directly
*/
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/**
* 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 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);
}
2.SpringMVC中提供了如下三个典型简单HandlerAdapter接口实现类:
1》.HttpRequestHandlerAdapter:可以实现HttpRequestHandler接口,所有的Handler可以实现其如下的方法,方法没有返回值。
void handleRequest(HttpServlet request, HttpServletResponse response)
2》.SimpleControllerHandlerAdapter:可以实现Controller接口,所有的Handler可以实现如下方法,该方法返回ModelAndView对象,用于后续的模板渲染。
public ModelAndView handleRequest(HttpServlet request, HttpServletResponse response, Object handler)
3》.SimpleServletHandlerAdapter:可直接实现Servlet接口,可以将一个Servlet座位一个Handler来处理请求。
3.HandlerAdapter初始化 和 匹配URL细节
HandlerAdapter初始化并没有什么特别之处,只是简单地创建一个HandlerAdapter,将这个HandlerAdapter保存在DispatcherServlet的handlerAdapters集合中。
/** List of HandlerAdapters used by this servlet */
private List handlerAdapters;
匹配URL细节:
当SpringMVC将某个URL,通过HandlerMapping接口的getHander方法,对应到某个HandlerExecutionChain对象。这个HandlerExecutionChain对象包含一个Object handler。Spring会根据这个Object handler在DispatcherServlet的handlerAdapters集合中,通过HandlerAdapter接口中的
boolean supports(Object handler);
方法找到某个具体的handlerAdapter对象。然后调用这个具体的handlerAdapter对象中方法处理请求。
2.4 Control调用逻辑时序图
1.整个SpringMVC的调用,都是从DispatcherServlet的doService方法开始的。
这个doService方法,官方源码方法描述信息是:
/**
* 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 {}
根据英文备注可知,doService方法做了两项重要工作:
工作1:
Exposes the DispatcherServlet-specific request attributes(暴露Spring容器相关的属性)。即首先将ApplicationContext、localResolver、themeResolver等对象,通过request.setAttribute()方法设置到到request中,以便后面的处理流程使用。
工作2:
and delegates to doDispatch for the actual dispatching.(将真正的处理逻辑代理给doDispatch方法处理)。即:doDispatch这个方法才是主要处理用户请求的地方。
2.处理细节:
Control处理的关键就是在DispatcherServlet的handlerMapping集合中,根据请求的URL通过getHandler方法匹配成功后,将会返回一个处理链HandlerExecutionChain对象。而在这个HandlerExecutionChain对象中将会包含用户自定义的多个HandlerInterceptor对象。在HandlerInterceptor接口中定义的3个方法中:preHandler和postHandler分别在handler执行前和执行后运行, afterCompletion在view渲染完成、DispatcherServlet返回之前执行。这里需要注意的地方是,当preHandler返回false时,当前的请求将在执行完afterCompletion后直接返回,Handler也将不再执行。
3.联想问题:
在HandlerMapping初始化的过程中,有一个很重要的步骤是registerHandlers:注册处理器,实例化出具体的Handler对象。
但是在HandlerAdapter初始化的时候,又会再一次创建HandlerAdapter,同时将这个HandlerAdapter保存在DispatcherServlet的handlerAdapters集合中。
问这两个初始化过程,实例化出来的Handler有什么区别吗?
答:有区别,前者HandlerMapping初始化时,注册的Handler类型是Object;而HandlerAdapter初始化,保存在DispatcherServlet的handlerAdapters集合中的类型是HandlerAdapter。
将Handler类型从HandlerExecutionChain中的Object类型,转变为HandlerAdapter类型。需要遍历DispatcherServlet的handlerAdapters集合中的HandlerAdapter,调用boolean supports(Object handler);方法。找寻到第一个匹配的即可。
4.Control调用逻辑时序图:
常有欲以观其徼!!!