1. 程式人生 > >SpringMVC原始碼分析(二)-URL對映的註冊

SpringMVC原始碼分析(二)-URL對映的註冊

​ 首先我們來看下AbstractHandlerMethodMapping這個類,它實現了InitializingBean介面,裡面有個afterPropertiesSet()方法。這個介面是spring-beans這個元件的內容,想一想,平時使用搭建SpringMVC的時候,是不是把這個jar包也扔到專案裡頭了?對於spring-beans這個元件在這裡就 不拓展了,我們只要知道實現了InitializingBean這個介面後,spring容器在物件例項化完後進行呼叫afterPropertiesSet()方法即可(注意這裡是spring容器,而不是tomcat容器)。部分原始碼如下

public
abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean { @Override public void afterPropertiesSet() { initHandlerMethods(); } /** * Scan beans in the ApplicationContext, detect and register handler methods. */
protected void initHandlerMethods() { if (logger.isDebugEnabled()) { logger.debug("Looking for request mappings in application context: " + getApplicationContext()); } //取得容器的所有bean String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : getApplicationContext().getBeanNamesForType(Object.class)); for
(String beanName : beanNames) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { Class<?> beanType = null; try { beanType = getApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isDebugEnabled()) { logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex); } } //isHandler()方法用於提取有@Controller或者@RequestMapping的類 if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } } } handlerMethodsInitialized(getHandlerMethods());//此處SpringMVC並未做任何實現 } protected void detectHandlerMethods(final Object handler) { Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()); final Class<?> userType = ClassUtils.getUserClass(handlerType); Map<Method, T> methods = MethodIntrospector.selectMethods(userType, new MethodIntrospector.MetadataLookup<T>() { @Override public T inspect(Method method) { try { return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } } }); if (logger.isDebugEnabled()) { logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods); } for (Map.Entry<Method, T> entry : methods.entrySet()) { Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType); T mapping = entry.getValue(); registerHandlerMethod(handler, invocableMethod, mapping); } } }

​ afterPropertiesSet()其實是呼叫了initHandlerMethods()方法,這方法上面註釋上面也寫得相當明白:掃描應用上下文,檢測並註冊處理方法。也就是說就是掃描所有的bean,並使用isHandler()提取出所有@Controller或者@RequestMapping的類。isHandler()是由子類去實現的,如下:

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements MatchableHandlerMapping, EmbeddedValueResolverAware {    
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }   
}

​ 繼續分析第一段原始碼,將上面提取出來的類使用detectHandlerMethods()方法進行處理,看著這個方法的最後五行,也就是迴圈那部分。這裡面有兩個關鍵的變數,一是mapping,二是invocableMethod。前者是@RequestMapping對應的URL地址,包括這個URL是GET還是POST方法等;後者是URL對應的方法,包括入參返回值等等。舉個列子:

@RequestMapping(value = "stu/{id}",method = RequestMethod.GET)
@ResponseBody
public Object student(@PathVariable("id") Integer id){
     return new Student(id,"小王");
}

上面例子,我們除錯可知道這兩個變數裡面的值為:

mapping:{[/stu/{id}],methods=[GET]}

invocableMethod:public java.lang.Object com.eejron.controller.HelloController.student(java.lang.Integer)

​ 對於獲取到的這兩個變數,SpringMVC將其註冊起來便於後續的訪問。繼續看registerHandlerMethod()這個註冊方法,下面的程式碼還是前文中第一段程式碼同一個類的內容,但是排版上選擇將其拆開。會發現在內部類MappingRegistry中,有5個Map,它們都有各自的自責。

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {

    private final MappingRegistry mappingRegistry = new MappingRegistry();

    protected void registerHandlerMethod(Object handler, Method method, T mapping) {
            this.mappingRegistry.register(mapping, handler, method);
        }   

    private static class MappingRegistration<T> {//內部靜態類

        private final T mapping;

        private final HandlerMethod handlerMethod;

        private final List<String> directUrls;

        private final String mappingName;

    }

    class MappingRegistry {//內部類

        private final Map<T, MappingRegistration<T>> registry = new HashMap<T, MappingRegistration<T>>();

        private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();

        private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();

        private final Map<String, List<HandlerMethod>> nameLookup =
                new ConcurrentHashMap<String, List<HandlerMethod>>();

        private final Map<HandlerMethod, CorsConfiguration> corsLookup =
                new ConcurrentHashMap<HandlerMethod, CorsConfiguration>();

        public void register(T mapping, Object handler, Method method) {
            this.readWriteLock.writeLock().lock();
            try {
                HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                assertUniqueMethodMapping(handlerMethod, mapping);

                if (logger.isInfoEnabled()) {
                    logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
                }
                this.mappingLookup.put(mapping, handlerMethod);

                List<String> directUrls = getDirectUrls(mapping);
                for (String url : directUrls) {
                    this.urlLookup.add(url, mapping);
                }

                String name = null;
                if (getNamingStrategy() != null) {
                    name = getNamingStrategy().getName(handlerMethod, mapping);
                    addMappingName(name, handlerMethod);
                }

                CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
                if (corsConfig != null) {
                    this.corsLookup.put(handlerMethod, corsConfig);
                }

                this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
            }
            finally {
                this.readWriteLock.writeLock().unlock();
            }
        }

        private List<String> getDirectUrls(T mapping) {
            List<String> urls = new ArrayList<String>(1);
            for (String path : getMappingPathPatterns(mapping)) {
                if (!getPathMatcher().isPattern(path)) {
                    urls.add(path);
                }
            }
            return urls;
        }

        private void addMappingName(String name, HandlerMethod handlerMethod) {
            List<HandlerMethod> oldList = this.nameLookup.get(name);
            if (oldList == null) {
                oldList = Collections.<HandlerMethod>emptyList();
            }

            for (HandlerMethod current : oldList) {
                if (handlerMethod.equals(current)) {
                    return;
                }
            }

            if (logger.isTraceEnabled()) {
                logger.trace("Mapping name '" + name + "'");
            }

            List<HandlerMethod> newList = new ArrayList<HandlerMethod>(oldList.size() + 1);
            newList.addAll(oldList);
            newList.add(handlerMethod);
            this.nameLookup.put(name, newList);

            if (newList.size() > 1) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Mapping name clash for handlerMethods " + newList +
                            ". Consider assigning explicit names.");
                }
            }
        }

    }


}

​ 我們主要來看下register()方法裡面涉及到Map的操作:

    this.mappingLookup.put(mapping, handlerMethod);

​ 這個很明瞭,就是前文分析獲取到的mapping跟invocableMethod的關聯,只不過是handlerMethod又把invocableMethod封裝一次而已

    List<String> directUrls = getDirectUrls(mapping);
    for (String url : directUrls) {
        this.urlLookup.add(url, mapping);
    }

​ 上面這個實現了同一個@RequestMapping對映多個請求,也就說說stu/{id}和student/{id}都能對映到下面同一個方法上,使用程式碼示例如下:

    @RequestMapping(value = {"stu/{id}","student/{id}"},method = RequestMethod.GET)
    @ResponseBody
    public Object student(@PathVariable("id") Integer id){
         return new Student(id,"小王");
    }

還有個this.nameLookup.put(name, newList),但是這個被我吃掉了,因為沒看懂啥作用-,-

    this.corsLookup.put(handlerMethod, corsConfig);

​ 上面是跨域處理,使用@CrossOrigin註解才會進行處理

    this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));

​ 上面這個就是對上面分析的的一個封裝。

​ 整個SpringMVC啟動時註冊相應的URL至此就完成了,後面的就是請求進來後提取註冊的資訊並呼叫相應方法執行