Spring MVC之HandlerMap 初始化
DispatcherServlet请求处理请求的过程中,会发现getHandler实际上是调用AbstractUrlHandlerMapping.getHandlerInternal()。 通过对该段代码进行走读后发现,是通过handlermap.get(urlPath)获取匹配的handler的,那么该handlerMap是在什么时候进行初始化的呢?
通过DefaultAnnotationHandlerMapping的继承关系可以去分析初始化的过程。
通过继承关系可以知道DefaultAnnotationHandlerMapping实现了ApplicationContextAware接口,这个接口的主要功能就是在Application初始化完成后会条用该接扩的setApplicationContext()方法。
AbstractDetectingUrlHandlerMapping是HandlerMapping初始化的关键入口,在初始化方法中首先调用父类的初始化方法,然后查找处理器。这个抽象类使用了模板方法模式,将具体的determineUrlsForHanlder方法延迟到子类中实现。
/** * Abstract implementation of the {@link org.springframework.web.servlet.HandlerMapping} * interface, detecting URL mappings for handler beans through introspection of all * defined beans in the application context. * * @author Juergen Hoeller * @since 2.5 * @see #determineUrlsForHandler */ public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping { private boolean detectHandlersInAncestorContexts = false; /** * Set whether to detect handler beans in ancestor ApplicationContexts. * <p>Default is "false": Only handler beans in the current ApplicationContext * will be detected, i.e. only in the context that this HandlerMapping itself * is defined in (typically the current DispatcherServlet's context). * <p>Switch this flag on to detect handler beans in ancestor contexts * (typically the Spring root WebApplicationContext) as well. */ public void setDetectHandlersInAncestorContexts(boolean detectHandlersInAncestorContexts) { this.detectHandlersInAncestorContexts = detectHandlersInAncestorContexts; } /** *在ApplicatonObjectSupport的setApplicationContext()方法中调用该方法 * Calls the {@link #detectHandlers()} method in addition to the * superclass's initialization. */ @Override public void initApplicationContext() throws ApplicationContextException { // 调用父类的initApplicationContext()方法 super.initApplicationContext(); //查找Handler 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()); } // 从上下文中获取所有的beanNames String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : getApplicationContext().getBeanNamesForType(Object.class)); // 遍历所有BeanNames for (String beanName : beanNames) { //调用子类实现方法,判定这个bean 处理的url String[] urls = determineUrlsForHandler(beanName); // 如果这个bean可以处理url则将调用父类的registerHandler方法完成 url和handler映射关系的注册 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"); } } } } /** * Determine the URLs for the given handler bean. * @param beanName the name of the candidate bean * @return the URLs determined for the bean, * or <code>null</code> or an empty array if none */ protected abstract String[] determineUrlsForHandler(String beanName); }DefaultAnnotationHandlerMapping继承了AbstractDetectingUrlHandlerMapping,实现了父类的抽象方法,
首先查找这个类是否有RequestMapping的注解以及这个Bean中所有方法是否有这个注解
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping { static final String USE_DEFAULT_SUFFIX_PATTERN = DefaultAnnotationHandlerMapping.class.getName() + ".useDefaultSuffixPattern"; private boolean useDefaultSuffixPattern = true; private final Map<Class<?>, RequestMapping> cachedMappings = new HashMap<Class<?>, RequestMapping>(); /** * Set whether to register paths using the default suffix pattern as well: * i.e. whether "/users" should be registered as "/users.*" and "/users/" too. * <p>Default is "true". Turn this convention off if you intend to interpret * your <code>@RequestMapping</code> paths strictly. * <p>Note that paths which include a ".xxx" suffix or end with "/" already will not be * transformed using the default suffix pattern in any case. */ public void setUseDefaultSuffixPattern(boolean useDefaultSuffixPattern) { this.useDefaultSuffixPattern = useDefaultSuffixPattern; } /** * Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping} * annotation on the handler class and on any of its methods. */ @Override protected String[] determineUrlsForHandler(String beanName) { ApplicationContext context = getApplicationContext(); Class<?> handlerType = context.getType(beanName); RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class); if (mapping != null) { // @RequestMapping found at type level this.cachedMappings.put(handlerType, mapping); Set<String> urls = new LinkedHashSet<String>(); String[] typeLevelPatterns = mapping.value(); if (typeLevelPatterns.length > 0) { // @RequestMapping specifies paths at type level String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType, true); for (String typeLevelPattern : typeLevelPatterns) { if (!typeLevelPattern.startsWith("/")) { typeLevelPattern = "/" + typeLevelPattern; } boolean hasEmptyMethodLevelMappings = false; for (String methodLevelPattern : methodLevelPatterns) { if (methodLevelPattern == null) { hasEmptyMethodLevelMappings = true; } else { String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern); addUrlsForPath(urls, combinedPattern); } } if (hasEmptyMethodLevelMappings || org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) { addUrlsForPath(urls, typeLevelPattern); } } return StringUtils.toStringArray(urls); } else { // actual paths specified by @RequestMapping at method level return determineUrlsForHandlerMethods(handlerType, false); } } else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) { // @RequestMapping to be introspected at method level return determineUrlsForHandlerMethods(handlerType, false); } else { return null; } } /** * Derive URL mappings from the handler's method-level mappings. * @param handlerType the handler type to introspect * @param hasTypeLevelMapping whether the method-level mappings are nested * within a type-level mapping * @return the array of mapped URLs */ protected String[] determineUrlsForHandlerMethods(Class<?> handlerType, final boolean hasTypeLevelMapping) { //在该类中返回值一直是null String[] subclassResult = determineUrlsForHandlerMethods(handlerType); if (subclassResult != null) { return subclassResult; } // 创建一个set,之所以使用set就是set中不会有重复数据 final Set<String> urls = new LinkedHashSet<String>(); Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>(); //添加类以及所有接口 handlerTypes.add(handlerType); handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces())); // 遍历handlerTypes for (Class<?> currentHandlerType : handlerTypes) { // 反射处理 ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() { public void doWith(Method method) { // 获取所有该方法上的RequestMapping注解 RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class); // 如果存在RequestMapping注解 if (mapping != null) { // 获取注解 value属性值 String[] mappedPatterns = mapping.value(); // 如果value的值不为空 if (mappedPatterns.length > 0) { // 遍历mappedPatterns for (String mappedPattern : mappedPatterns) { //如果hasTypeLevelMapping=false,同时mappedPattern不是以"/"开头则在其前面加上"/" if (!hasTypeLevelMapping && !mappedPattern.startsWith("/")) { mappedPattern = "/" + mappedPattern; } //将url添加在set中 addUrlsForPath(urls, mappedPattern); } } else if (hasTypeLevelMapping) { // empty method-level RequestMapping urls.add(null); } } } }, ReflectionUtils.USER_DECLARED_METHODS); } return StringUtils.toStringArray(urls); } /** * Derive URL mappings from the handler's method-level mappings. * @param handlerType the handler type to introspect * @return the array of mapped URLs */ protected String[] determineUrlsForHandlerMethods(Class<?> handlerType) { return null; } /** * Add URLs and/or URL patterns for the given path. * @param urls the Set of URLs for the current bean * @param path the currently introspected path */ protected void addUrlsForPath(Set<String> urls, String path) { urls.add(path); if (this.useDefaultSuffixPattern && path.indexOf('.') == -1 && !path.endsWith("/")) { urls.add(path + ".*"); urls.add(path + "/"); } } /** * Validate the given annotated handler against the current request. * @see #validateMapping */ @Override protected void validateHandler(Object handler, HttpServletRequest request) throws Exception { RequestMapping mapping = this.cachedMappings.get(handler.getClass()); if (mapping == null) { mapping = AnnotationUtils.findAnnotation(handler.getClass(), RequestMapping.class); } if (mapping != null) { validateMapping(mapping, request); } request.setAttribute(USE_DEFAULT_SUFFIX_PATTERN, this.useDefaultSuffixPattern); } /** * Validate the given type-level mapping metadata against the current request, * checking HTTP request method and parameter conditions. * @param mapping the mapping metadata to validate * @param request current HTTP request * @throws Exception if validation failed */ protected void validateMapping(RequestMapping mapping, HttpServletRequest request) throws Exception { RequestMethod[] mappedMethods = mapping.method(); if (!ServletAnnotationMappingUtils.checkRequestMethod(mappedMethods, request)) { String[] supportedMethods = new String[mappedMethods.length]; for (int i = 0; i < mappedMethods.length; i++) { supportedMethods[i] = mappedMethods[i].name(); } throw new HttpRequestMethodNotSupportedException(request.getMethod(), supportedMethods); } String[] mappedParams = mapping.params(); if (!ServletAnnotationMappingUtils.checkParameters(mappedParams, request)) { throw new UnsatisfiedServletRequestParameterException(mappedParams, request.getParameterMap()); } String[] mappedHeaders = mapping.headers(); if (!ServletAnnotationMappingUtils.checkHeaders(mappedHeaders, request)) { throw new ServletRequestBindingException("Header conditions \"" + StringUtils.arrayToDelimitedString(mappedHeaders, ", ") + "\" not met for actual request"); } } @Override protected boolean supportsTypeLevelMappings() { return true; } }
public abstract class WebApplicationObjectSupport extends ApplicationObjectSupport implements ServletContextAware { private ServletContext servletContext; //由于该类实现了ServletContextAware接口,在ServletContext初始化完成后,调用该方法 public final void setServletContext(ServletContext servletContext) { if (servletContext != this.servletContext) { this.servletContext = servletContext; if (servletContext != null) { initServletContext(servletContext); } } } @Override protected boolean isContextRequired() { return true; } /** * 在ApplicationContext初始完成后调用该方法,如果已用上下文是WebApplicationContext则调用initServeltContext */ @Override protected void initApplicationContext(ApplicationContext context) { // 调用父类initApplicationContext,也就是调用ApplicationObjectSupport.initApplictionContext()方法 super.initApplicationContext(context); if (this.servletContext == null && context instanceof WebApplicationContext) { this.servletContext = ((WebApplicationContext) context).getServletContext(); if (this.servletContext != null) { // 模板方法模式,具体实现延迟到子类中进行 initServletContext(this.servletContext); } } } /** * Subclasses may override this for custom initialization based * on the ServletContext that this application object runs in. * <p>The default implementation is empty. Called by * {@link #initApplicationContext(org.springframework.context.ApplicationContext)} * as well as {@link #setServletContext(javax.servlet.ServletContext)}. * @param servletContext the ServletContext that this application object runs in * (never <code>null</code>) */ protected void initServletContext(ServletContext servletContext) { }