Spring Core 官方文档阅读笔记(十五)
注意啦,现在开始填坑了!!!
1. Handler
Handler,在spring中标识了一个处理,对应的HandlerMapping即为该处理的映射,HandlerAdapter即为该处理的适配器。那么问题来了。。。HandlerMapping是将处理与什么组成映射关系,而HandlerAdapter又是要适配什么?我们一步一步来解析。
先来回顾一下我们前面整理的,DispatcherServlet处理一个请求的过程:
- 绑定WebApplicationContext到请求上。
- 绑定LocaleResolver到请求上。
- 绑定ThemeResolver到请求上。
- 检查请求是否是Multipart,如果是,则将请求包装成MultipartHttpServletRequest。
- 搜索对应的Handler,并获取Model。
- 返回Model,并渲染视图。
在第五步,就开始搜索对应的Handler了,还是先看代码,DispatcherServlet的doDispatch方法:
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
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.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
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 (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
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);
}
}
}
}
在检查完multipart以后,通过getHandler(processedRequest)方法获取Handler。点进去看一下getHandler方法:
/**
* 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
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
逐步看一下,首先,这个getHandler处理的第一步是遍历了一个this.handlerMappings。这个handlerMappings是哪里来的呢?看一下声明的地方:
/** List of HandlerMappings used by this servlet */
private List<HandlerMapping> handlerMappings;
根据注释,这个handlerMappings是当前servlet所使用的的一个HandlerMapping的list集合。再看这个集合是赋值的地方:
/**
* 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) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.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.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
还是老样子,先看注释,上面说通过这个方法来初始化HandlerMappings,而且如果没有HandlerMapping被定义在BeanFactory中,将会默认使用BeanNameUrlHandlerMapping。这两个又是什么鬼?来看一下:
- HandlerMapping
/**
* 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.annotation.DefaultAnnotationHandlerMapping}
* 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.annotation.DefaultAnnotationHandlerMapping
*/
public interface HandlerMapping {
/**
* Name of the {@link HttpServletRequest} attribute that contains the path
* within the handler mapping, in case of a pattern match, or the full
* relevant URI (typically within the DispatcherServlet's mapping) else.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. URL-based HandlerMappings will
* typically support it, but handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
/**
* Name of the {@link HttpServletRequest} attribute that contains the
* best matching pattern within the handler mapping.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. URL-based HandlerMappings will
* typically support it, but handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
/**
* Name of the boolean {@link HttpServletRequest} attribute that indicates
* whether type-level mappings should be inspected.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations.
*/
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
/**
* Name of the {@link HttpServletRequest} attribute that contains the URI
* templates map, mapping variable names to values.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. URL-based HandlerMappings will
* typically support it, but handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
/**
* Name of the {@link HttpServletRequest} attribute that contains a map with
* URI matrix variables.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations and may also not be present depending on
* whether the HandlerMapping is configured to keep matrix variable content
* in the request URI.
*/
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
/**
* Name of the {@link HttpServletRequest} attribute that contains the set of
* producible MediaTypes applicable to the mapped handler.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. Handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
/**
* Return a handler and any interceptors for this request. The choice may be made
* on request URL, session state, or any factor the implementing class chooses.
* <p>The returned HandlerExecutionChain contains a handler Object, rather than
* even a tag interface, so that handlers are not constrained in any way.
* For example, a HandlerAdapter could be written to allow another framework's
* handler objects to be used.
* <p>Returns {@code null} if no match was found. This is not an error.
* The DispatcherServlet will query all registered HandlerMapping beans to find
* a match, and only decide there is an error if none can find a handler.
* @param request current HTTP request
* @return a HandlerExecutionChain instance containing handler object and
* any interceptors, or {@code null} if no mapping found
* @throws Exception if there is an internal error
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
emmm。。。是个接口,来读一下注释。这个接口定义了请求和处理之间的映射关系。而真正的处理则被包含在HandlerExecutionChain中,其中包括HandlerInterceptor实现。而DispatcherServlet也会先调用HandlerInterceptor的preHandler,当所有的preHandler方法都返回true的时候,才会去执行handler。
- BeanNameUrlHandlerMapping
/**
* Implementation of the {@link org.springframework.web.servlet.HandlerMapping}
* interface that map from URLs to beans with names that start with a slash ("/"),
* similar to how Struts maps URLs to action names.
*
* <p>This is the default implementation used by the
* {@link org.springframework.web.servlet.DispatcherServlet}, along with
* {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}.
* Alternatively, {@link SimpleUrlHandlerMapping} allows for customizing a
* handler mapping declaratively.
*
* <p>The mapping is from URL to bean name. Thus an incoming URL "/foo" would map
* to a handler named "/foo", or to "/foo /foo2" in case of multiple mappings to
* a single handler. Note: In XML definitions, you'll need to use an alias
* name="/foo" in the bean definition, as the XML id may not contain slashes.
*
* <p>Supports direct matches (given "/test" -> registered "/test") and "*"
* matches (given "/test" -> registered "/t*"). Note that the default is
* to map within the current servlet mapping if applicable; see the
* {@link #setAlwaysUseFullPath "alwaysUseFullPath"} property for details.
* For details on the pattern options, see the
* {@link org.springframework.util.AntPathMatcher} javadoc.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see SimpleUrlHandlerMapping
*/
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<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是HandlerMapping的一个实现类,通过以"/"开头的名称构建URL和Bean的映射关系。并且,这个是个被DispatcherServlet使用的默认实现类。
大致了解了HandlerMapping和BeanNameUrlHandlerMapping,我们接着看initHandlerMapping的逻辑,首先,判断detectAllHandlerMappings是否是true,从字面意思上来看,这个值就是控制是否需要查找所有的HandlerMapping。来看看它的定义:
/** Detect all HandlerMappings or just expect "handlerMapping" bean? */
private boolean detectAllHandlerMappings = true;
查找所有HandlerMappings或者只需要名为handlerMapping的bean。
那么我们就可以猜测,根据这个值而分出来的分支就是在做这个事情,查找所有HandlerMapping或者只查找名为handlerMapping的bean。
接着看代码,如果detectAllHandlerMappings为true,则调用
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false)
来查找ApplicationContext以及父类Context下所有的HandlerMapping,这里注意后面两个boolean参数,分别是includeNonSingletons和allowEagerInit,includeNonSingletons用于控制是否包含非singleton的bean,而allowEagerInit则用于控制是否需要初始化lazy-init的单例bean和被FactoryBean创建的对象。
通过上面这个方法找到容器中所有的HandlerMapping类型的bean,然后塞到this.handlerMappings里,进行排序。
而如果detectAllHandlerMapping是false,则只通过
context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
来查找名为handlerMapping的HandlerMapping。
倘若这两个分支查找的最终结果是null,则调用
getDefaultStrategies(context, HandlerMapping.class);
来创建默认的HandlerMapping。这个默认的是什么呢?记得我们之前说过,DispatcherServlet在WebApplicationContext中检查每个特殊的Bean,如果没有匹配的Bean,它将使用DispatcherServlet.properties中配置的默认类型。没错,这个默认的HandlerMapping也是在这个文件中定义的。
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
默认的即是BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping。
考虑一下,既然BeanNameUrlHandlerMapping是DispatcherServlet默认的HandlerMapping,那么它一定是能够被容器找到的,也就是说,在执行到BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false)方法的时候,BeanNameUrlHandlerMapping的实例已经存在于容器中了,那么是什么时候被实例化的呢?
Spring在初始化BeanFactory的时候,会查询并调用所有的BeanPostProccessor,其中就包括ApplicationContextAwareProccessor,而这个方法中则会调用setApplicationContext方法去设置ApplicationContext。ApplicationObjectSupport实现了ApplicationContextAware接口,在它的setApplicationContext方法中调用了initApplicationContext方法。恰好BeanNameUrlHandlerMapping的父类AbstractDetectingUrlHandlerMapping重写了这个方法:
/**
* Calls the {@link #detectHandlers()} method in addition to the
* superclass's initialization.
*/
@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
detectHandlers();
}
在detectHandlers方法中,查询了所有Handler。
/**
* Register all handlers found in the current ApplicationContext.
* <p>The actual URL determination for a handler is up to the concrete
* {@link #determineUrlsForHandler(String)} implementation. A bean for
* which no such URLs could be determined is simply not considered a handler.
* @throws org.springframework.beans.BeansException if the handler couldn't be registered
* @see #determineUrlsForHandler(String)
*/
protected void detectHandlers() throws BeansException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().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);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
}
}
}
}
同样是使用BeanFactoryUtils.beanNamesForTypeIncludingAncestors方法来获取所有Bean,然后遍历,通过BeanNameUrlHandlerMapping类实现的determineUrlsForHandler方法,来筛选:
/**
* Checks name and aliases of the given bean for URLs, starting with "/".
*/
@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);
}
只要是beanName或者alias是"/"开头的,都会添加到url的数组中。然后在detectHandlers中,通过获取到的数组,来注册Handler。
/**
* Register the specified handler for the given URL paths.
* @param urlPaths the URLs that the bean should be mapped to
* @param beanName the name of the handler bean
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
/**
* 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));
}
}
}
}
首先,通过bean名称获取handler的实例,然后从handlerMap中通过url来获取handler,如果获取到的结果不是null,则判断与前面通过bean名称获取到的实例是否相等,若相等,说明已经注册过了,直接跳过,若不等,则说明有冲突,此时就会抛出IllegalStateException异常。如果handlerMap中获取不到,说明没有注册过,那么就要将handler与url绑定起来。
我们把SpringMVC的启动过程整体串起来看一下,SpringMVC中一个HttpServletBean继承了HttpServlet,并重写了init()方法。我们知道创建servlet的时候会调用init(ServletConfig)方法,而这个方法里调用了init()。那么也就是通过HttpServletBean来创建Servlet。
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@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();
}
方法里有一大部分代码都是在准备init-param,最后有个initServletBean方法,注释上说是让子类做自定义的初始化,这个方法在HttpServletBean里是一个空方法,我们找到他的子类--FrameworkServlet,点进去看一下:
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@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");
}
}
在FrameworkServlet中重写了父类的方法,也就是最终调用的其实是FrameworkServlet中的initServletBean。也就是说,FrameworkServlet继承了HttpServletBean抽象类,而HttpServletBean又继承了HttpServlet,所以当初始化Servlet的时候,其实就是初始化的FrameworkServlet。继续看代码,在try-catch块里,先调用了initWebApplicationContext,看方法名我们可以猜到,这里应该就是初始化Spring IOC容器的地方了。
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
一行一行看,第一句代码
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
这句代码我们慢慢分析,首先,getServletContext方法是GenericServlet中的方法,用来获取ServletContext,不多说。然后我们看下WebApplicationContextUtils如何通过这个ServletContext来获取一个WebApplicationContext:
/**
* Find the root {@code WebApplicationContext} for this web app, typically
* loaded via {@link org.springframework.web.context.ContextLoaderListener}.
* <p>Will rethrow an exception that happened on root context startup,
* to differentiate between a failed context startup and no context at all.
* @param sc the ServletContext to find the web application context for
* @return the root WebApplicationContext for this web app, or {@code null} if none
* @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
*/
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
只有一个处理,点进去看看:
/**
* Find a custom {@code WebApplicationContext} for this web app.
* @param sc the ServletContext to find the web application context for
* @param attrName the name of the ServletContext attribute to look for
* @return the desired WebApplicationContext for this web app, or {@code null} if none
*/
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (attr instanceof Exception) {
throw new IllegalStateException((Exception) attr);
}
if (!(attr instanceof WebApplicationContext)) {
throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
}
return (WebApplicationContext) attr;
}
直接从ServletContext里获取了一个key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的属性,这个常量的值是什么呢:
/**
* Context attribute to bind root WebApplicationContext to on successful startup.
* <p>Note: If the startup of the root context fails, this attribute can contain
* an exception or error as value. Use WebApplicationContextUtils for convenient
* lookup of the root WebApplicationContext.
* @see org.springframework.web.context.support.WebApplicationContextUtils#getWebApplicationContext
* @see org.springframework.web.context.support.WebApplicationContextUtils#getRequiredWebApplicationContext
*/
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
留意一下注释,这个attribute在startUp成功的时候被绑定在root WebApplicationContext上。接着看,如果root context的startup处理失败,这个属性就包含了一个异常或者错误的信息。那我们就可以推断,这个startup指的就是root context。前面我们整理Spring MVC的上下文结构的时候提到,上下文分为root context和servlet context,root context包含了一些基础服务、数据库连接等,而servlet context则包含了controller、service等。而上面代码在获取的时候是直接从servletContext中获取的,那就可以推断,在SpringMVC中,root context等同于servletContext,而servlet context就是Spring容器的WebApplicationContext。再回到上面的问题,在root startup的时候进行绑定,那我们去找找servletContext的startUp处理:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
* implementations present on the application classpath.
* <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
* Servlet 3.0+ containers will automatically scan the classpath for implementations
* of Spring's {@code WebApplicationInitializer} interface and provide the set of all
* such types to the {@code webAppInitializerClasses} parameter of this method.
* <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
* this method is effectively a no-op. An INFO-level log message will be issued notifying
* the user that the {@code ServletContainerInitializer} has indeed been invoked but that
* no {@code WebApplicationInitializer} implementations were found.
* <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
* they will be instantiated (and <em>sorted</em> if the @{@link
* org.springframework.core.annotation.Order @Order} annotation is present or
* the {@link org.springframework.core.Ordered Ordered} interface has been
* implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
* method will be invoked on each instance, delegating the {@code ServletContext} such
* that each instance may register and configure servlets such as Spring's
* {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
* or any other Servlet API componentry such as filters.
* @param webAppInitializerClasses all implementations of
* {@link WebApplicationInitializer} found on the application classpath
* @param servletContext the servlet context to be initialized
* @see WebApplicationInitializer#onStartup(ServletContext)
* @see AnnotationAwareOrderComparator
*/
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
servlet容器启动的时候会调用上面的onStartup方法,遍历实例化的WebApplicationInitializer,然后调用WebApplicationInitializer的onStartup方法。这个实例化的WebApplicationInitializer是由我们来创建的,里面可以自定义处理,如:
/**
* @Author: kuromaru
* @Date: Created in 13:56 2019/8/5
* @Description:
* @modified:
*/
public class WebMvcInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(WebMvcConfig.class);
context.setServletContext(servletContext);
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
registration.addMapping("/");
registration.setLoadOnStartup(1);
}
}
以上就是Servlet启动的一些处理,Servlet还提供了一个监听器,用于监听servlet容器的创建和销毁:
public interface ServletContextListener extends EventListener {
/**
* Receives notification that the web application initialization
* process is starting.
*
* <p>All ServletContextListeners are notified of context
* initialization before any filters or servlets in the web
* application are initialized.
*
* @param sce the ServletContextEvent containing the ServletContext
* that is being initialized
*
* @implSpec
* The default implementation takes no action.
*/
default public void contextInitialized(ServletContextEvent sce) {}
/**
* Receives notification that the ServletContext is about to be
* shut down.
*
* <p>All servlets and filters will have been destroyed before any
* ServletContextListeners are notified of context
* destruction.
*
* @param sce the ServletContextEvent containing the ServletContext
* that is being destroyed
*
* @implSpec
* The default implementation takes no action.
*/
default public void contextDestroyed(ServletContextEvent sce) {}
}
Spring提供了这个接口的实现类ContextLoaderListener,这个类大家应该很熟悉,我们在配置web.xml的时候,经常会配一个listener:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
当我们配置了这个listener,servletContext被加载的时候就会触发相应的方法去加载ApplicationContext:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
/**
* Create a new {@code ContextLoaderListener} that will create a web application
* context based on the "contextClass" and "contextConfigLocation" servlet
* context-params. See {@link ContextLoader} superclass documentation for details on
* default values for each.
* <p>This constructor is typically used when declaring {@code ContextLoaderListener}
* as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
* required.
* <p>The created application context will be registered into the ServletContext under
* the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
* and the Spring application context will be closed when the {@link #contextDestroyed}
* lifecycle method is invoked on this listener.
* @see ContextLoader
* @see #ContextLoaderListener(WebApplicationContext)
* @see #contextInitialized(ServletContextEvent)
* @see #contextDestroyed(ServletContextEvent)
*/
public ContextLoaderListener() {
}
/**
* Create a new {@code ContextLoaderListener} with the given application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based
* registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
* API.
* <p>The context may or may not yet be {@linkplain
* org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
* (a) is an implementation of {@link ConfigurableWebApplicationContext} and
* (b) has <strong>not</strong> already been refreshed (the recommended approach),
* then the following will occur:
* <ul>
* <li>If the given context has not already been assigned an {@linkplain
* org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
* <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
* the application context</li>
* <li>{@link #customizeContext} will be called</li>
* <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer org.springframework.context.ApplicationContextInitializer ApplicationContextInitializers}
* specified through the "contextInitializerClasses" init-param will be applied.</li>
* <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
* </ul>
* If the context has already been refreshed or does not implement
* {@code ConfigurableWebApplicationContext}, none of the above will occur under the
* assumption that the user has performed these actions (or not) per his or her
* specific needs.
* <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* <p>In any case, the given application context will be registered into the
* ServletContext under the attribute name {@link
* WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
* application context will be closed when the {@link #contextDestroyed} lifecycle
* method is invoked on this listener.
* @param context the application context to manage
* @see #contextInitialized(ServletContextEvent)
* @see #contextDestroyed(ServletContextEvent)
*/
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
这里我们就可以看到,在servlet容器初始化的时候,会触发contextinitialized处理,在这个处理里面,通过initWebApplicationContext方法初始化WebApplicationContext。这个方法被定义在父类ContextLoader中,点进去看看:
/**
* Initialize Spring's web application context for the given servlet context,
* using the application context provided at construction time, or creating a new one
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #ContextLoader(WebApplicationContext)
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
可以看到,在这个方法里设置了WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性。但是我们自己的WebMvcInitializer中,并没有配置listener,所以,这里获取到的rootContext其实是个null。如果想要注册ContextLoaderListener,可以这么写:
/**
* @Author: kuromaru
* @Date: Created in 13:56 2019/8/5
* @Description:
* @modified:
*/
public class WebMvcInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(WebMvcConfig.class);
context.setServletContext(servletContext);
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
registration.addMapping("/");
registration.setLoadOnStartup(1);
servletContext.addListener(new ContextLoaderListener());
}
}
在处理的最后我们添加了ContextLoaderListener,此时,就会将WebApplicationContext绑定到ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE上(需要配置applicationContext.xml,否则会报错找不到applicationContext.xml)。
好了,我们已经清楚了ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性的配置,那么,回到initWebApplicationContext方法,我们能获取到rootContext就是null。接着往下看:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
获取完rootContext,会去判断this.webApplicationContext是否为null,这个this.webApplicationContext就是在创建DispatcherServlet的时候传进去的AnnotationConfigWebApplicationContext的实例:
public class WebMvcInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(WebMvcConfig.class);
context.setServletContext(servletContext);
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
registration.addMapping("/");
registration.setLoadOnStartup(1);
}
}
spring容器启动的时候,会先去创建一个BeanFactory,在创建BeanFactory的时候,会尝试将handler注入,并与url绑定。容器启动调用refresh方法时,会触发刷新事件,这个刷新事件是FrameServlet中注册的,在onApplicationEvent方法中会调用DispatcherServlet的onRefresh方法,这个方法里会初始化HandlerMapping,如果没有,则通过DispathcerServlet.properties中的配置创建一个,注册到容器中。
这里还要说一下Handler,我之前对Handler的理解是Handler即是Controller,后面发现这个理解太片面了,Controller是特殊的Handler,但是Handler并不全是Controller,还有如HttpRequestHandler之类的,我们也可以自定义Handler。可以这么理解,Spring MVC默认将Controller当成Handler来处理。
在SpringMVC里,默认会把Controller做为handler,但是handler并不全是controller,Spring启动的时候,会尝试通过WebMvcConfigurationSupport去完成配置项,在这一步处理中,会尝试注入RequestMappingHandlerMapping类型的实例,而这个RequestMappingHandlerMapping继承了AbstractHandlerMethodMapping类,父类AbstractHandlerMethodMapping又实现了InitializerBean接口,实现了afterProperitesSet方法,在方法里尝试去初始化HandlerMethod。首先,还是要遍历所有容器托管的Bean,然后对于名称不是“scopedTarget.”开头的,都去判断一下是否是Handler,而判断是否为Handler的标准就是是否是以@Controller或者@RequestMapping注解的。如果是以这两个的任意一个注解的类,则去获取类中被@RequestMapping注解的方法,并通过注解的参数(如value、methode)生成一个RequestMappingInfo,从而将uri和方法对应起来。然后,会使用handler(即controller)和Method(反射)注册一个HandlerMethod,然后以RequestMappingInfo为key,HandlerMethod为value保存到名为mappingLookup的map中。并且将此RequestMappingInfo对应的所有uri遍历出来,以uri为key,RequestMappingInfo为value,保存在名为urlLookUp的map中。同时,会根据Handler和HandlerMehod的名称,生成一个方法的摘要名,如WebController的hello()方法,生成的名称即为WC#hello,并以此为key,HandlerMethod为value,存入名为nameLookUp的Map中。
HandlerMapping的初始化在HandlerMethod之后进行。那么当HandlerMapping初始化的时候,就可以查询到两个HandlerMapping:BeanNameUrlHandlerMapping和RequestMappingHandlerMapping。
这里看到几个比较实用的工具类,在这标注一下:AnnotatedElementUtils、BeanFactoryUtils、AopUtils
好了,handlerMapping的相关处理我们都已经整理好了,继续看DispatcherServlet中getHandler的逻辑:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
遍历获取到的HandlerMapping,然后通过getHandler方法获取一个HandlerExecutionChain。这个getHandler方法是HandlerMapping接口里声明的接口方法,在AbstractHandlerMapping抽象类里实现了这个方法。一起来看一下。
/**
* 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
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
注意最开始的getHandlerInternal方法,这个方法被声明在AbstractHandlerMapping抽象类中,是一个抽象方法,它被AbstractHandlerMethodMapping和AbstractUrlHandlerMapping实现。根据类名,我们也很好推测,AbstractHandlerMethodMapping是RequestMappingHandlerMapping的父类,而AbstractUrlHandlerMapping则是BeanNameUrlHandlerMapping的父类,因此,在调用此方法时,根据实例的类型,调用不同的方法实现。如果是RequestMappingHandlerMapping,则去urlLookUp中查找对应的RequestMappingInfo,然后再去mappingLookup中根据RequestMappingInfo去查找对应的Handler;如果是BeanNameUrlHandlerMapping,则直接去handlerMap中查找对应的handler。
如果最终找不到对应的handlerMap,则会抛出404异常。
找到Handler以后,则通过Handler和Request生成HandlerExecutionChain,其中就把uri对应的Interceptor,并挂载到执行链中。这里的Interceptor集合的获取比较麻烦,先是通过WebMvcConfigurationSupport里注入各个HandlerMapping时绑定interceptor,然后在初始化interceptor的时候,通过下面的方法
BeanFactoryUtils.beansOfTypeIncludingAncestors(
obtainApplicationContext(), MappedInterceptor.class, true, false).values()
来查找,也就是查找容器里所有的MappedInterceptor类型的实例,然后把两部分interceptor合并在一起。
至此,我们获取到了mappedHandler。那么在doDispatch的主逻辑当中,获取完mappedHandler以后,紧接着就是要获取一个HandlerAdapter。
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
跟获取HandlerExecutionChain类似,这个也是遍历了一个叫handlerAdapter的属性。还是来看下一下这个属性是在哪里设值的。之前在整理handlerMapping的时候,我们看到有一个initStrategies方法,这里面做了一系列的初始化工作,handlerAdapter的初始化也在这个方法里被执行:
/**
* Initialize the HandlerAdapters used by this class.
* <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
* we default to SimpleControllerHandlerAdapter.
*/
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
看起来眼熟吧~这个跟获取HandlerMapping的逻辑几乎一样,也是通过BeanFactoryUtils.beansOfTypeIncludingAncestors方法去容器里获取所有已注入的HandlerAdapter。那么容器里有哪些HandlerAdapter呢?前面我们说Spring MVC通过WebMvcConfigurationSupport来完成配置,那我们去这个里面找一下,看有没有。找之前,我们还得先看一下HandlerAdapter的实现类,有下面几个:
- AbstractHandlerMethodAdapter
- HttpRequestHandlerAdapter
- RequestMappingHandlerAdapter
- SimpleControllerHandlerAdapter
- SimpleServletHandlerAdapter
第一个是一个抽象类,这个先不考虑,第五个SimpleServletHandlerAdapter好像也没啥用,查了一下,并没有哪个地方调用,而且在注释中也说默认情况下不激活,暂时不考虑。那剩下的就还有三个,我们去WebMvcConfigurationSupport中找一下:
/**
* Returns a {@link RequestMappingHandlerAdapter} for processing requests
* through annotated controller methods. Consider overriding one of these
* other more fine-grained methods:
* <ul>
* <li>{@link #addArgumentResolvers} for adding custom argument resolvers.
* <li>{@link #addReturnValueHandlers} for adding custom return value handlers.
* <li>{@link #configureMessageConverters} for adding custom message converters.
* </ul>
*/
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
configureAsyncSupport(configurer);
if (configurer.getTaskExecutor() != null) {
adapter.setTaskExecutor(configurer.getTaskExecutor());
}
if (configurer.getTimeout() != null) {
adapter.setAsyncRequestTimeout(configurer.getTimeout());
}
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
return adapter;
}
...
/**
* Returns a {@link HttpRequestHandlerAdapter} for processing requests
* with {@link HttpRequestHandler HttpRequestHandlers}.
*/
@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
return new HttpRequestHandlerAdapter();
}
/**
* Returns a {@link SimpleControllerHandlerAdapter} for processing requests
* with interface-based controllers.
*/
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
...
如上,三个类的实例都有配置。RequestMappingHandlerAdapter和RequestMappingHandlerMapping的初始化套路也基本一样,而且RequestMappingHandlerAdapter也实现了InitializerBean,在对应的afterPropertiesSet方法中,初始化了ControllerAdvice ,然后对应的argumentResolver、initBinderArgumentResolvers和returnValueHandlers也做了初始化。这里简单介绍一下ControllerAdvice,字面意思是Controller的增强,一个被@ControllerAdvice注解的类,可以使用@ExceptionHandler、@InitBinder和@ModeAttribute来注解方法。详细的后面再介绍。
接着看doDispatch的逻辑
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
取得所有的handlerAdapter,然后遍历判断是否适用于当前的handler,如果handler是Controller类型的话,则适用SimpleControllerHandlerAdapter;如果handler是HttpRequestHandler,则适用HttpRequestHandlerAdapter;如果是HandlerMethod,则适用RequestMappingHandlerAdapter。
OK,到此为止,HandlerMapping、HandlerMethod以及HandlerAdaptor都基本整理完了。
原文地址:https://www.cnblogs.com/kuromaru/p/12935571.html
- 学习manacher(最长公共回文串算法)
- Apache Spark 2.3 加入支持Native Kubernetes及新特性文档下载
- Oracle 12c 多租户专题|隔离PDB的磁盘IO
- golang 裸写一个pool池控制协程的大小
- 2014---多校训练2(ZCC Loves Codefires)
- 完整的golang 多协程+信道 任务处理示例
- 2014---多校训练一(A Couple doubi)
- hdu----(2586)How far away ?(DFS/LCA/RMQ)
- Golang控制goroutine的启动与关闭
- spring-boot-starter-swagger迎新伙伴支持,加速更新进度(1.3.0.RELEASE)
- poj----(1470)Closest Common Ancestors(LCA)
- 测试一下golang协程资源占有率
- poj----1330Nearest Common Ancestors(简单LCA)
- fasthttp中的协程池实现
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释