02-SpringMVC初始化HandlerMappings过程

2020-02-13  本文已影响0人  AcientFish

要了解DispatcherServlet,就必须先了解几个特殊Bean,否则在理解源码的过程中会很吃力。
[图片上传失败...(image-11609-1581567742949)]
上图出自Spring的官方文档。从图中可以看出HandlerMapping占的篇幅很大,也是整个DispatcherServlet映射寻址的关键。我们今天就来分析在初始化过程中是如何初始化HandlerMapping的。
在正式开始前,我们先来了解一个SpringMVC包中的配置文件


DispatcherServletProcess.png

这个配置文件是Spring初始化时的默认配置,如果用户没有进行自定义配置,那么会根据上面这个配置中的地址去获取默认处理类。
上篇文章中我们已经介绍了DispatcherServlet#onRefresh时每个方法的作用,现在我们直入主题,来分析分析initHandlerMappings这个方法都做了什么。

/**
     * 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;
        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null){
            // 获取默认HandlerMapping策略
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        }
    }
    
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        // 获取需要的策略名称
        String key = strategyInterface.getName();
        // 从默认策略中得到HandlerMapping的策略,在上面的图中可以看到HandlerMapping有两个默认策略,BeanNameUrlHandlerMapping和RequestMappingHandlerMapping。
        // 此处的defaultStrategies在上篇文章中提到过,在静态代码块中做了初始化操作。
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List<T> strategies = new ArrayList<>(classNames.length);
            // 依次处理每个策略
            for (String className : classNames) {
                try {
                    // 通过反射将配置的类加载到jvm中
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    // 初始化策略对象,此时会通过beanFactory来createBean,这些方法在前面分析Spring容器启动时已经讲过了。重点在初始化结束后调用initializeBean执行Aware回调
                    Object strategy = createDefaultStrategy(context, clazz);
                    strategies.add((T) strategy);
                }
                catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException(
                            "Could not find DispatcherServlet's default strategy class [" + className +
                            "] for interface [" + key + "]", ex);
                }
                catch (LinkageError err) {
                    throw new BeanInitializationException(
                            "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                            className + "] for interface [" + key + "]", err);
                }
            }
            return strategies;
        }
        else {
            return new LinkedList<>();
        }
    }

我们已经知道HandlerMapping默认配置了两个实现(实际还有其他实现),对于BeanNameUrlHandlerMapping来说相对简单,IOC容器生成对象之后基本也就结束了初始化操作,我们来重点关注一下RequestMappingHandlerMapping。首先我们来看看RequestMappingHandlerMapping的类关系图


RequestMappingHandlerMapping.png

通过关系图我们可以看到,RequestMappingHandlerMapping的父类AbstractHandlerMethodMapping实现了InitializingBean接口,这意味着IOC容器在创建结束后会执行initializeBean方法,调用afterPropertiesSet方法。同时RequestMappingHandlerMapping重写了afterPropertiesSet,我们来看看这个方法里面发生了什么

@Override
    public void afterPropertiesSet() {
        this.config = new RequestMappingInfo.BuilderConfiguration();
        this.config.setUrlPathHelper(getUrlPathHelper());
        this.config.setPathMatcher(getPathMatcher());
        this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
        this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
        this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
        this.config.setContentNegotiationManager(getContentNegotiationManager());
        // 前面只是创建了一个配置对象,初始化了一些配置项
        // 调用父类afterPropertiesSet方法
        super.afterPropertiesSet();
    }

    /**
     * Detects handler methods at initialization.
     * @see #initHandlerMethods
     */
    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }

    /**
     * Scan beans in the ApplicationContext, detect and register handler methods.
     * @see #getCandidateBeanNames()
     * @see #processCandidateBean
     * @see #handlerMethodsInitialized
     */
    protected void initHandlerMethods() {
        // 这里的getCandidateBeanNames会独去所有被容器管理的对象
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
    processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
    
    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            beanType = obtainApplicationContext().getType(beanName);
        }
        catch (Throwable ex) {
            // An unresolvable bean type, probably from a lazy bean - let's ignore it.
            if (logger.isTraceEnabled()) {
                logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
            }
        }
        // isHandler方法会判断当前类是否存在@Controller或@RequestMapping注解,从而过滤非Controller对象
        if (beanType != null && isHandler(beanType)) {
            // 从此处开始真正处理每个Controller
            detectHandlerMethods(beanName);
        }
    }

请注意,此时才是HandlerMapping初始化的核心部分。在AbstractHandlerMethodMapping#detectHandlerMethods方法中会对每个Controller中的所有方法进行判断,得到带有@RequestMapping注解的方法并将其封装成RequestMappingInfo。最后会将Method封装成HandlerMethod,同时依次在mappingLookup、urlLookup、nameLookup、corsLookup和registry中注册。下面我们来分析detectHandlerMethods方法。

    /**
     * Look for handler methods in the specified handler bean.
     * @param handler either a bean name or an actual handler instance
     * @see #getMappingForMethod
     */
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            // 查找@RequestMapping方法并封装成RequestMappingInfo
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            // 将Method封装成HandlerMethod并依次注册
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }
    
    public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
        final Map<Method, T> methodMap = new LinkedHashMap<>();
        Set<Class<?>> handlerTypes = new LinkedHashSet<>();
        Class<?> specificHandlerType = null;
        for (Class<?> currentHandlerType : handlerTypes) {
            final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
            // 判断方法是否是桥接方法(Bridge)、组合方法(Synthetic)或者是Object中的方法,如果不是则封装RequestMappingInfo
            ReflectionUtils.doWithMethods(currentHandlerType, method -> {
                Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
                T result = metadataLookup.inspect(specificMethod);
                if (result != null) {
                    Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                    if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
                        methodMap.put(specificMethod, result);
                    }
                }
            }, ReflectionUtils.USER_DECLARED_METHODS);
        }
        return methodMap;
    }

最终会调用到RequestMappingHandlerMapping#getMappingForMethod方法,在这个方法中会判断是否存在@RequestMapping注解,不存在会跳过。同时会将方法上的@RequestMapping和类上的@RequestMapping进行合并(如果存在),合并操作会将类上的url与方法中配置的url进行拼接。最终返回合并后的RequestMappingInfo。
当类中所有的方法全部处理完之后会遍历得到的Map,逐一将Method封装成HandlerMethod并调用registry依次注册到mappingLookup、urlLookup、nameLookup、corsLookup和registry中去,关键代码如下

public void register(T mapping, Object handler, Method method) {
    this.readWriteLock.writeLock().lock();
    try {
        // 封装HanderMethod
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        assertUniqueMethodMapping(handlerMethod, mapping);
        // 以RequestMappingHandlerMapping为key,HandlerMethod为值注册在mappingLookup中
        this.mappingLookup.put(mapping, handlerMethod);
        // 从mapping中获取配置的patterns解析配置的Url,并以url为key,mapping为value注册到urlLookup中
        List<String> directUrls = getDirectUrls(mapping);
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }
        // 取类名中的大写字母用#和方法名连接在一起作为key,handlerMethod集合作为value注册到nameLookup中
        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }
        // 初始化跨域访问配置,判断类或者方法上是否存在CrossOrigin注解,如果没有则忽略
        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }
        // 注册到registry中
        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}

至此整个HandlerMapping就算初始化结束了,下篇文章我们继续分析初始化HandlerAdapter的过程,这两个过程在后续的Spring MVC接收请求,封装请求参数,调用Controller中的方法中起到了至关重要的作用。我们会着重分析这两块的初始化过程,之后再来分析接收到请求后如何封装参数,分发请求。

上一篇下一篇

猜你喜欢

热点阅读