Spring5源码14-SpringMVC-HandlerMapping

Spring5源码14-SpringMVC-DispatcherServlet处理请求核心逻辑 正常情况下,如果没有URL匹配的Handler,我们可以通过设置默认的Handler 来解决这一问题,不过如果没有设置默认的Handler。客户端第二次请求此URL时2,客户端会向服务器发送请求头 “If-Modified-Since”,询问服务器该时间之后当前请求是否有被修改过,如果服务端内容没有变化,则会自动返回 304 状态码(只要响应头,内容为空,这样就节省了带宽)。// 遍历每一个 handleMapping,解析 request,直到碰到一个解析成功的,将解析后的 Handler拦截链路返回。 阅读详情

在 Spring MVC 的分析过程中,我们知道了 Spring mvc 默认会引入DispatcherServlet.properties 文件中的类(该文件和 DispatcherServlet 在同级目录) 的一些类。DispatcherServlet.properties 配置文件如下。

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

下面我们来看看 HandlerMapping 不同实现类的区别。

1. HandlerMapping

从 DispatcherServlet.properties 文件中得知。HandlerMapping 默认引入三个类BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、RouterFunctionMapping

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

我们来看看他们的区别:

  • BeanNameUrlHandlerMapping :以beanName 作为key值

  • RequestMappingHandlerMapping :完成@Controller和@RequestMapping 的解析,并将解析保存。请求发送时与请求路径进行匹配对应找到合适的Handler。RequestMappingHandlerMapping 实现了 InitializingBean 接口,会在afterPropertiesSet 方法中。

    • 调用时机: 解析@Controller和@RequestMapping注解是在 afterPropertiesSet方法中进行的。匹配调用则是在 DispatcherServlet doDispatch方法中的getHandler中调用了HandlerMapper中的getHandler中的getHandlerInternal方法。
  • RouterFunctionMapping : RouterFunctionMapping是检索RouterFunction应用程序上下文中创建的所有bean 的类。

在 Spring mvc 加载 HandlerMapping 的代码分析中,我们知道,Spring 默认会加载 DispatcherServlet.properties 中的所有的 HandlerMapping。同时我们可以通过 DispatcherServlet.detectAllHandlerMappings 属性来控制是否加载所有的HandlerMapping,还是只加载唯一的HandlerMapping。

为了方便,先禁用寻找所有的HandlerMapping。

@Bean
public DispatcherServlet dispatcherServlet(){
    DispatcherServlet dispatcherServlet = new DispatcherServlet();
    // 设置false,则只寻找beanName 为 handlerMapping 的类,不加载所有HandlerMapping类
    dispatcherServlet.setDetectAllHandlerMappings(false);
    return dispatcherServlet;
}

1.1. BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping 顾名思义,就是根据beanName 来 进行映射。

我们创建一个Controller如下,这里有两点需要注意:

  • 映射的Controller 需要实现 org.springframework.web.servlet.mvc.Controller 接口。
  • beanName 需要以 ‘/’ 开头。
@org.springframework.stereotype.Controller("/helloSimple")
public class HelloSimpleController implements Controller {
   @Override
   public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
      return null;
   }
}

这里解释分析一下为什么要 beanName 以 ‘/’ 开头?

2.1.1 isHandler(beanType)

这个方法在 AbstractHandlerMethodMapping 并没有具体实现,是供子类实现,目的是判断当前 Bean是否是 Handler (满足Handler 的条件)。我们这里来看看 RequestMappingHandlerMapping 的实现。

  • 我们来看 BeanNameUrlHandlerMapping 的源码如下, 可以很清楚的看到这里有对 / 的判断。寻找到以 / 开头的beanName 并作为url返回。
    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<>();
    		// 判断 beanName 是否是 / 开头
    		if (beanName.startsWith("/")) {
    			urls.add(beanName);
    		}
    		// 判断 beanName 的别名是否是 / 开头哦
    		String[] aliases = obtainApplicationContext().getAliases(beanName);
    		for (String alias : aliases) {
    			if (alias.startsWith("/")) {
    				urls.add(alias);
    			}
    		}
    		// 将是 / 的beanname 转化成数组并返回。
    		return StringUtils.toStringArray(urls);
    	}
    
    }

    返回之后,在AbstractDetectingUrlHandlerMapping#detectHandlers 方法中完成了Handler 的注册

    protected void detectHandlers() throws BeansException {
            ApplicationContext applicationContext = obtainApplicationContext();
            String[] beanNames = (this.detectHandlersInAncestorContexts ?
                            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
                            applicationContext.getBeanNamesForType(Object.class));
    
            // Take any bean name that we can determine URLs for.
            // 遍历所有的BeanName
            for (String beanName : beanNames) {
                    // 获取到符合规则的Url
                    String[] urls = determineUrlsForHandler(beanName);
                    if (!ObjectUtils.isEmpty(urls)) {
                            // URL paths found: Let's consider it a handler.
                            // 注册成handler 
                            registerHandler(urls, beanName);
                    }
            }
            ...
    }

    我们来进一步看看Handler 注册的流程。注册流程其实很简单,其实就是利用AbstractUrlHandlerMapping#handlerMap 中保存了url 和 Handler。key为 urlPath,value为handler实例。

    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);
            }
    }
    
    ...
    
    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.
            // 如果不是懒加载 && handler 是 String类型。
            if (!this.lazyInitHandlers && handler instanceof String) {
                    String handlerName = (String) handler;
                    ApplicationContext applicationContext = obtainApplicationContext();
                    // 如果是handler是单例,则从上下文中根据 beanName 获取handler 对象实例
                    if (applicationContext.isSingleton(handlerName)) {
                            resolvedHandler = applicationContext.getBean(handlerName);
                    }
            }
            // 判断 handlerMap 中已经保存了该 url
            Object mappedHandler = this.handlerMap.get(urlPath);
            if (mappedHandler != null) {
                    // 如果该url 对应的两个不同的 handler,则抛出异常
                    if (mappedHandler != resolvedHandler) {
                            throw new IllegalStateException(
                                            "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                                            "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
                    }
            }
            else {
                    // 如果是  urlPath 是 / 开头。则设置为 根处理器
                    if (urlPath.equals("/")) {
                            if (logger.isTraceEnabled()) {
                                    logger.trace("Root mapping to " + getHandlerDescription(handler));
                            }
                            setRootHandler(resolvedHandler);
                    }
                    // 如果是  urlPath 是 / 开头。则设置为 默认处理器
                    else if (urlPath.equals("/*")) {
                            if (logger.isTraceEnabled()) {
                                    logger.trace("Default mapping to " + getHandlerDescription(handler));
                            }
                            setDefaultHandler(resolvedHandler);
                    }
                    else {
                            // 否则的话,保存到 handlerMap 中
                            this.handlerMap.put(urlPath, resolvedHandler);
                            if (logger.isTraceEnabled()) {
                                    logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
                            }
                    }
            }
    }

    1.2 RequestMappingHandlerMapping

    RequestMappingHandlerMapping,即我们平常最常用的一种形式,通过 @ResquestMapping 注解的方式来执行路径,由于下面有关于RequestMappingHandlerMapping 注册流程的分析,这里就不再赘述 。

    1.3 RouterFunctionMapping

    RouterFunctionMapping没用过 。

    2. RequestMappingHandlerMapping

    下面的解析以 RequestMappingHandlerMapping 为例,因为 RequestMappingHandlerMapping 也是我们最常使用的 HandlerMapping。

    2.1 Handler的注册

    我们首先来看一下 RequestMappingHandlerMapping 的结构图

    我们看到其实现了其实 RequestMappingHandlerMapping 实现了 InitializingBean 接口(实际上是 AbstractHandlerMethodMapping 实现的。看了这么久的源码,我们可以大胆的猜测,对 Handler 的解析应该就在 InitializingBean#afterPropertiesSet 方法中。

    我们来看一下 RequestMappingHandlerMapping#afterPropertiesSet 的实现如下。


     

    @Override
    @SuppressWarnings("deprecation")
    public void afterPropertiesSet() {
            // 进行 RequestMapping 的配置
            this.config = new RequestMappingInfo.BuilderConfiguration();
            this.config.setUrlPathHelper(getUrlPathHelper());
            this.config.setPathMatcher(getPathMatcher());
            this.config.setSuffixPatternMatch(useSuffixPatternMatch());
            this.config.setTrailingSlashMatch(useTrailingSlashMatch());
            this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
            this.config.setContentNegotiationManager(getContentNegotiationManager());
            // 调用父类的  afterPropertiesSet 方法。
            super.afterPropertiesSet();
    }

     上面我们看到,其主要内容还是在父类的 afterPropertiesSet 方法中。因此下面我们来看AbstractHandlerMethodMapping#afterPropertiesSet:

    private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
    /**
     * 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() : 从 Spring 容器中获取所有候选的beanName。
            for (String beanName : getCandidateBeanNames()) {
                    // 如果beanName 不是以 SCOPED_TARGET_NAME_PREFIX 开头,则进行处理
                    if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                            // 1. 初始 候选的beanName
                            processCandidateBean(beanName);
                    }
            }
            // 这里就是打印了一下log日志
            handlerMethodsInitialized(getHandlerMethods());
    }

    我们根据 initHandlerMethods 的注释就可以知道 initHandlerMethods 的作用是扫描 ApplicationContext,检测并注册 Handler方法。我们可以看到其中的关键方法就是 processCandidateBean(处理候选的Bean。这里实际完成了对 HandlerMethod 的解析和注册)。

    在processCandidateBean 方法中,完成了对 HandlerMethod 的解析和注册 。而processCandidateBean的具体实现在 AbstractHandlerMethodMapping#processCandidateBean中。

    下面我们来看看具体实现

    protected void processCandidateBean(String beanName) {
            Class<?> beanType = null;
            try {
                    // 获取beanType
                    beanType = obtainApplicationContext().getType(beanName);
            }
            catch (Throwable ex) {
                    ...
            }
            // 如果 beanType != null  && beanType 是 handler。在 RequestMappingHandlerMapping  中的判断是 bean被 @Controller 或者 @RequestMapping 注解修饰。则会进行bean的handler方法筛选
            if (beanType != null && isHandler(beanType)) {
                    // 筛选出合适的 HandlerMethod 注册
                    detectHandlerMethods(beanName);
            }
    }

    可以看到其逻辑并不复杂,我们有两个方法需要关注:

  • isHandler(beanType) : 判断当前bean是否满足 Handler 条件
  • detectHandlerMethods(beanName) : 解析 handler 里面的方法并注册 下面我们来看一看逐一来看一看:

2.1.1 isHandler(beanType)

这个方法在 AbstractHandlerMethodMapping 并没有具体实现,是供子类实现,目的是判断当前 Bean是否是 Handler (满足Handler 的条件)。我们这里来看看 RequestMappingHandlerMapping 的实现。

// RequestMappingHandlerMapping 中 isHandler 的实现
@Override
protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                        AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

可以看到,在 RequestMappingHandlerMapping 中 这里判断当前的bean 是否可以作为handler 的条件是 : bean被 @Controller 或者 @RequestMapping 注解修饰。则会进行bean的handler方法筛选。如果满足,则会通过 AbstractHandlerMethodMapping#detectHandlerMethods方法来进行下一步的筛选Handler的方法筛选。

2.1.2. detectHandlerMethods(beanName)

detectHandlerMethods 的作用是筛选出合适的 Handler Method 方法,并进行注册。接下来来看看 AbstractHandlerMethodMapping#detectHandlerMethods方法的具体实现。

// 检测 handler 方法并注册
protected void detectHandlerMethods(Object handler) {
        // 获取 handlerType。这里传入的 handler如果是  String 就是 beanName,从上下文中获取type,否则就直接认为是 Handler
        Class<?> handlerType = (handler instanceof String ?
                        obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
                // 返回给定类的用户定义类:通常只是给定的类,但对于CGLIB生成的子类,则返回原始类
                Class<?> userType = ClassUtils.getUserClass(handlerType);
                // 遍历当前bean的所有方法,筛选出合适的 handler 方法 以及 注解信息
                // 这里需要注意的是   MethodIntrospector.selectMethods 底层将  getMappingForMethod 返回为null 的值给过滤掉了。
                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);
                                        }
                                });
                if (logger.isTraceEnabled()) {
                        logger.trace(formatMappings(userType, methods));
                }
                // 遍历所有的methods(这里的methods经过上面的筛选后,都是被 @RequestMapping 注解修饰的方法)
                methods.forEach((method, mapping) -> {
                        // 这里是针对 cglib 代理特殊处理。
                        Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                        //注册 HandlerMethod 。
                        registerHandlerMethod(handler, invocableMethod, mapping);
                });
        }
}

在上述代码中通过 MethodIntrospector#selectMethods(...) 方法遍历了当前bean的所有方法,并调用 getMappingForMethod 方法进行处理。getMappingForMethod 方法在 AbstractHandlerMethodMapping 中并未有具体实现,这里我们看看 RequestMappingHandlerMapping 中的实现。

// RequestMappingHandlerMapping 中的实现
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        // 转换成 RequestMappingInfo ,如果方法没有被  @RequestMapping 注解修饰,则会返回null
        // 解析出来方法上  @RequestMapping  注解的各种信息
        RequestMappingInfo info = createRequestMappingInfo(method);
        if (info != null) {
                // 解析出来 bean 上  @RequestMapping  注解的各种信息
                RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
                if (typeInfo != null) {
                        // 合并类和方法的 @RequestMapping 注解信息
                        info = typeInfo.combine(info);
                }
                // 获取前缀,拼接前缀
                String prefix = getPathPrefix(handlerType);
                if (prefix != null) {
                        info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
                }
        }
        // 返回信息
        return info;
}	

getMappingForMethod 方法的作用是解析 方法上的@RequestMapping注解的信息并和类上 @RequestMapping 注解 的信息相结合。

@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        // 获取当前方法上的  @RequestMapping 注解
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        // 获取自定义的方法条件
        RequestCondition<?> condition = (element instanceof Class ?
                        getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        // 这里可以看到 如果 requestMapping  = null,则会直接返回null,否则会封装成一个 RequestMappingInfo (包含 @RequestMapping 注解的各种参数) 返回。
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

2.1.3. registerHandlerMethod(handler, invocableMethod, mapping)

registerHandlerMethod(handler, invocableMethod, mapping); 看方法名字就知道 注册 HandlerMethod。下面我们看看具体实现

//  RequestMappingHandlerMapping 中
@Override
public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) {
        super.registerMapping(mapping, handler, method);
        updateConsumesCondition(mapping, method);
}

@Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
        super.registerHandlerMethod(handler, method, mapping);
        updateConsumesCondition(mapping, method);
}

...

// 对 @RequestBody 注解进行了进一步解析
private void updateConsumesCondition(RequestMappingInfo info, Method method) {
        ConsumesRequestCondition condition = info.getConsumesCondition();
        if (!condition.isEmpty()) {
                for (Parameter parameter : method.getParameters()) {
                        MergedAnnotation<RequestBody> annot = MergedAnnotations.from(parameter).get(RequestBody.class);
                        if (annot.isPresent()) {
                                condition.setBodyRequired(annot.getBoolean("required"));
                                break;
                        }
                }
        }
}

其中 super.registerMapping(mapping, handler, method); 调用的是 AbstractHandlerMethodMapping#registerMapping 。如下

// RequestMappingInfo 和 MappingRegistration 的映射关系
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
//  key是 RequestMappingInfo,   value 是 HandlerMethod
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
// url 和  RequestMappingInfo 映射起来。
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
// 类名#方法名  和 HandlerMethod 的映射关系
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
// HandlerMethod 和 CorsConfiguration 的映射关系
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

private final MappingRegistry mappingRegistry = new MappingRegistry();

// super.registerMapping(mapping, handler, method);  调用的是 AbstractHandlerMethodMapping#registerMapping 。如下
public void registerMapping(T mapping, Object handler, Method method) {
        if (logger.isTraceEnabled()) {
                logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
        }
        // 完成了handler method 的注册
        this.mappingRegistry.register(mapping, handler, method);
}

下面我们来看看 this.mappingRegistry.register(mapping, handler, method); 的具体实现 (AbstractHandlerMethodMapping.MappingRegistry#register) 如下

public void register(T mapping, Object handler, Method method) {
        // Assert that the handler method is not a suspending one.
        if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
                        throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
                }
        }
        // 加写锁
        this.readWriteLock.writeLock().lock();
        try {
                // 将 handler 和 method 封装成一个  HandlerMethod  实例
                HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                // 校验实例的合法性。即唯一性
                validateMethodMapping(handlerMethod, mapping);
                // 保存映射关心, key是 RequestMappingInfo,   value 是 HandlerMethod 
                this.mappingLookup.put(mapping, handlerMethod);
                // 获取url映射,如果是 restful 请求则获取不到。建立 url 和Mapping 的映射。
                // 一个mapping 可以对应多个url
                List<String> directUrls = getDirectUrls(mapping);
                // 这里将url 和  mapping映射起来
                // 在进行匹配的时候,就是先根据url找到合适的mapping,然后根据找到的mapping再去找到HandlerMethod
                for (String url : directUrls) {
                        this.urlLookup.add(url, mapping);
                }
                // 保存 类名#方法名 : HandlerMethod 的映射关系
                String name = null;
                if (getNamingStrategy() != null) {
                        // 这里解析出来的name 并不是完整的类名,而是类名的首字母组合。比如方法名是 DemoController.say() ,解析出来的name即为 DC#say。如果是 SayController.say()。解析出来则为 name = SC#say
                        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();
        }
}

2.1.4. 总结

经过上面的分析我们可以得知:

  1. Spring启动时加载 RequestMappingHandlerMapping。RequestMappingHandlerMapping在初始化的时候会从 Spring容器中获取bean
  2. 遍历每个bean,判断是否满足成为Handler的条件(bean被 @Controller 或者 @RequestMapping 注解修饰)。满足条件的作为 Handler进行下一步处理
  3. 通过反射获取每个 Handler 里面的方法,去判断是否被@RequestMapping 注解修饰,如果被修饰,则获取@RequestMapping 注解的信息与类上面的 @RequestMapping 注解信息合并后注册HandlerMethod。

2.2 Handler 的筛选

当一个请求过来的的时候,会交由 DispatcherServlet 来进行请求分发。根据请求的 request 查找到对应的 handler。这里来分析一下这个查找过程。

DispatcherServlet 的查找方法是 DispatcherServlet#getHandler。如下

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
                // 这里的 HandlerMapping  如果没有特殊操作,则默认是从配置文件中读取并加载的 HandlerMapping
                for (HandlerMapping mapping : this.handlerMappings) {
                        // 调用HandlerMapping 的getHandler 方法。
                        HandlerExecutionChain handler = mapping.getHandler(request);
                        if (handler != null) {
                                return handler;
                        }
                }
        }
        return null;
}

mapping.getHandler(request); 调用的是 AbstractHandlerMapping#getHandler 方法。其实现如下:

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 1. 调用 getHandlerInternal 获取 handler 。该方法供子类实现
        Object handler = getHandlerInternal(request);
        // 如果没有获取到 handler。则获取默认的handler
        if (handler == null) {
                handler = getDefaultHandler();
        }
        if (handler == null) {
                return null;
        }
        // Bean name or resolved handler?
        // 如果 handler  是 String类型,则会认为是 beanName。从Spring容器中获取 bean实例
        if (handler instanceof String) {
                String handlerName = (String) handler;
                handler = obtainApplicationContext().getBean(handlerName);
        }
        // 2. 生成拦截器链路
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

        if (logger.isTraceEnabled()) {
                logger.trace("Mapped to " + handler);
        }
        else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
                logger.debug("Mapped to " + executionChain.getHandler());
        }

        if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
                CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
                CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
                config = (config != null ? config.combine(handlerConfig) : handlerConfig);
                executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }

        return executionChain;
}

2.2.1. getHandlerInternal(request)

该方法在 AbstractHandlerMethodMapping#getHandlerInternal 中没有具体实现,是供不同的 HandlerMapping 子类自己实现的。这里我们直接看 RequestMappingInfoHandlerMapping#getHandlerInternal

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        try {
                return super.getHandlerInternal(request);
        }
        finally {
                ProducesRequestCondition.clearMediaTypesAttribute(request);
        }
}

super.getHandlerInternal(request); 调用的是 AbstractHandlerMethodMapping#getHandlerInternal。如下

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        // 从 request 中解析出 请求路径
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        request.setAttribute(LOOKUP_PATH, lookupPath);
        this.mappingRegistry.acquireReadLock();
        try {
                // 查找 HandlerMethod
                HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
                return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
                this.mappingRegistry.releaseReadLock();
        }
}

...

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<>();
        // 通过 url 获取匹配路径
        // springMVC会在初始化的时候建立URL和相应RequestMappingInfo的映射,这点在上面的解析中我们可以知道。如果不是restful接口,这里就可以直接获取到。
        // 这里是通过 urlLookup 获取的 RequestMappingInfo 
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
                // 将匹配的 Mapping 保存到 matches 中。
                addMatchingMappings(directPathMatches, matches, request);
        }
        // 如果上面没有获取到匹配的路径,则只能遍历所有的 mapping。
        // 由于会遍历所有的 RequestMapping。所以性能会随着 RequestMapping数量的增加降低
        if (matches.isEmpty()) {
                // No choice but to go through all mappings...
                addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
        // 如果 matches 不为空,则说明有匹配的 Mapping
        if (!matches.isEmpty()) {
                Match bestMatch = matches.get(0);
                // 如果合适的 Mapping 不止一个,则筛选出最合适的
                if (matches.size() > 1) {
                        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
                        matches.sort(comparator);
                        bestMatch = matches.get(0);
                        if (logger.isTraceEnabled()) {
                                logger.trace(matches.size() + " matching mappings: " + matches);
                        }
                        if (CorsUtils.isPreFlightRequest(request)) {
                                return PREFLIGHT_AMBIGUOUS_MATCH;
                        }
                        Match secondBestMatch = matches.get(1);
                        if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                                Method m1 = bestMatch.handlerMethod.getMethod();
                                Method m2 = secondBestMatch.handlerMethod.getMethod();
                                String uri = request.getRequestURI();
                                throw new IllegalStateException(
                                                "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                        }
                }
                request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
                handleMatch(bestMatch.mapping, lookupPath, request);
                return bestMatch.handlerMethod;
        }
        else {
                return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
}

...

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
        for (T mapping : mappings) {
                T match = getMatchingMapping(mapping, request);
                if (match != null) {
                        matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
                }
        }
}

...

//  this.mappingRegistry.getMappingsByUrl(lookupPath);  
// 也就是 AbstractHandlerMethodMapping.MappingRegistry#getMappingsByUrl
@Nullable
public List<T> getMappingsByUrl(String urlPath) {
        return this.urlLookup.get(urlPath);
}

2.2.2 getHandlerExecutionChain(handler, request)

AbstractHandlerMapping#getHandlerExecutionChain。主要目的是将配置中对应的拦截器加入到执行链中,以保证这些拦截器可以有效的作用于目标对象

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        // 构建一个执行链
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                        (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
        // 解析出来 请求路径
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
        // 遍历所有的拦截器,如果拦截器匹配符则加入到执行链中。adaptedInterceptors 是在 Mapping 初始化的时候加载的
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
                if (interceptor instanceof MappedInterceptor) {
                        MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                        if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                                chain.addInterceptor(mappedInterceptor.getInterceptor());
                        }
                }
                else {
                        chain.addInterceptor(interceptor);
                }
        }
        return chain;
}

关于 adaptedInterceptors、interceptors 两个拦截器集合:adaptedInterceptors 是 AbstractHandlerMapping 在初始化的时候实现了 ApplicationContextAware 接口,在 ApplicationObjectSupport#setApplicationContext 方法中调用 initApplicationContext 方法,进行了 adaptedInterceptors 的初始化。而 interceptors 则可以通过 set 方法进行注入。

// AbstractHandlerMapping 
@Override
protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        detectMappedInterceptors(this.adaptedInterceptors);
        initInterceptors();
}
...
// 从容器中 获取所有MappedInterceptor类型的拦截器
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
        mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
                        obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}

2.2.3 总结

总结部分:

  1. List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); 这个方法是非常直观的根据URL来获取,springMVC会在初始化的时候建立URL和相应RequestMappingInfo的映射。如果不是restful接口,这里就可以直接获取到了。

  2. 如果1中已经获取到,则调用方法addMatchingMappings(directPathMatches, matches, request)进行匹配校验。

  3. 如果1中未获取到匹配方法信息,则调用方法addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); 进行全局(all mappings)扫描匹配(this.mappingRegistry.getMappings().keySet())。且会把所有的RequestMappingInfo都遍历完才会停止,也就是说项目中的@RequestMapping方法越多,这个匹配的效率就越低,性能越差。

  4. 在遍历过程中,SpringMVC首先会根据@RequestMapping中的headers, params, produces, consumes, methods与实际的HttpServletRequest中的信息对比,剔除掉一些明显不合格的RequestMapping。 如果以上信息都能够匹配上,那么SpringMVC会对RequestMapping中的path进行正则匹配,剔除不合格的。接下来会对所有留下来的候选@RequestMapping进行评分并排序。最后选择分数最高的那个作为结果。

22-Spring MVC源码跟踪 上面的方法中,第2步其实就是从第一个步骤中的Map中取得Controller,然后经过拦截器的预处理方法,到最核心的部分–第5步调用controller的方法处理请求。在第2步中我们可以知道处理request的Controller,第5步就是要根据url确定Controller中处理请求的方法,然后通过反射获取该方法上的注解和参数,解析方法和参数上的注解,最后反射调用方法获取ModelAndView结果视图。到这里,方法的参数值列表也获取到了,就可以直接进行方法的调用了。 阅读详情

相关推荐

【SpringBoot 4.x 第61节】Spring MVC 请求处理链路源码解析:DispatcherServlet、HandlerMapping 与 HandlerAdapter!

🏆 本文收录于 《滚雪球学 Spring Boot 4.x》 专栏。本专栏面向 有一定 Java 基础,但尚未系统学习 Spring Boot 的读者,采用“滚雪球式学习法”:先跑通、再理解、再重构、再上线,带你从第一个 Hello API 开始,逐步完成一个可部署、可监控、可扩展的后端项目。

**My Coding Family** 327

Spring 源码分析衍生篇十一 :HandlerMapping

一、前言 在 Spring MVC 的分析过程中,我们知道了 Spring mvc 默认会引入DispatcherServlet.properties 文件中的类(该文件和 DispatcherServlet 在同级目录) 的一些类。 DispatcherServlet.properties 配置文件如下。 # Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when

猫吻鱼的博客 1525

Vivado实战:UltraScale/UltraScale+器件eFUSE寄存器配置与安全编程全解析

本文详细解析了Xilinx UltraScale/UltraScale+器件中eFUSE寄存器的配置与安全编程方法。从芯片唯一身份标识到加密密钥烧录,再到用户寄存器的灵活应用和安全控制寄存器的配置技巧,全面介绍了如何利用eFUSE功能提升硬件安全性。文章还提供了JTAG接口安全管理和NKZ文件备份方案,帮助开发者避免常见陷阱,确保安全编程的可靠性。

weixin_42535703的博客 428

springMVC HandlerMapping组件介绍

1:简单介绍 该组件作用是根据请求信息获取对应的处理请求的handler,职责很单一,只干这一件事。该组件对应的接口是org.springframework.web.servlet.HandlerMapping,只有唯一的一个用来根据请求获取handler的方法getHandler(req),源码如下: public interface HandlerMapping { String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getNam

wang0907的博客 1701

Unity URP热扭曲效果实战:如何让半透明物体也‘动’起来(附完整Shader代码)

本文深入解析Unity URP管线中热扭曲效果无法影响半透明物体的核心原因,并提供一套完整的实战解决方案。通过编写自定义Shader,并巧妙利用URP的Render Objects渲染器功能,成功让指定半透明物体(如粒子特效)也能被扭曲效果影响,从而制作出火焰、热浪等更具视觉冲击力的特效。文末附有完整的Shader代码与进阶优化技巧。

pz890123的博客 501

Gephi实战指南——从数据导入到可视化输出

本文详细介绍了Gephi数据可视化工具的使用方法,从数据导入到可视化输出的全流程实战指南。涵盖Gephi三大工作区功能、数据导入技巧、图形编辑进阶操作以及专业级可视化输出方案,帮助用户高效完成社交网络分析等复杂任务。

weixin_28731223的博客 481

BeanNameUrlHandlerMapping类介绍

1:介绍 该类用于维护比如实现了org.springframework.web.servlet.mvc.Controller接口的类型的handler。关于维护使用如@RequestMapping定义的handler的Mapping可以参考[这里],(https://blog.csdn.net/wang0907/article/details/108960496)。另外从其名称中也能看出来,这是一个使用BeanName作为url的mapping。 2:初始化 2.1:入口 先看下BeanNameUrlHan

wang0907的博客 2008

如何实现MCGS与S7-200SMART PLC以太网多台通信控制?

第九步:(设备工具箱—设备管理—鼠标依次点击通用TCP/IP父设备—西门子_ Smart200到设备窗口里面去)数据类型:第0位(指PLC地址变量小数点后面的位数,例如我地址是M0.0,那么在数据类型应填写通道的第00位)第十步:(鼠标双击打开通用TCPIP父设备0--[通用TCP/IP父设备]进入通信配置)通道地址:0(指小数点前面起始字节的地址,例如我地址是M0.0,那么应在通道地填写0)。第七步:(选择通用TCP/IP父设备,点击新增,右侧选定设备就会自动添加进去。

u010670857的博客 6718

java.lang.ClassNotFoundException: org.springframework.web.servlet.HandlerMapping解决方法

【现象】 Caused by: java.lang.ClassNotFoundException: org.springframework.web.servlet.HandlerMapping at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_181] at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1....

一火的专栏 5920

RTK_ROS_导航(4):接入move_base

现在我们要将之前的数据接入到Move_base中,使得机器人能够根据Map上的终点进行导航,并且实现避障效果如下图。

旋风小短腿 1947

stable diffusioncontrolNet 预处理器/模型的下载及安装使用教程

首先说明,controlNet 的模型 不同于stable diffusion 模型,这一点一定要区分清楚。下面提供多个ControlNet 预处理器+ 专用模型的百度网盘下载链接,都是由群友整理贡献,但各自包含的版本不同,大家可自行选择。推荐下载紫色链接内的模型,fp16版文件更小,稍微新一些的controlnet插件还添加了对control-lora的支持(注意,要求controlnet插件的版本比12月16号新(1.1.423以上)才能正常识别),推荐下载;

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 3385

Spring MVC处理器映射器(HandlerMapping)详解

Spring MVC提供的处理器映射器(HandlerMapping)主要有BeanNameUrlHandlerMapping和SimpleUrlHandlerMapping,我们需根据使用的场景来进行配置。

陈瑾 421

23种设计模式-中介者(Mediator)设计模式

软考中级-软件设计师 中介者设计模式

片言可决讼,半纸能缚龙 1864

SpringMVC的源码解析

springmvc、springboot、源码解析

蓝影铁哥的博客 1191

DISM进行系统修复,进度条卡在63.2%的解决方法

一时用不了软件,安装修复的时候也出现问题 DISM一直卡在63.2%,等了好久时间,直接强制退出寻找最终的解决方案

码农研究僧的博客 1万+

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

源码看SpringMVC工作流程

Spring MVC源码深入剖析通过源码看SpringMVC工作流程1.1 gradle搭建源码调试环境1.2 MVC工作原理和继承关系1.3 Spring MVC源码深入剖析1.3.1 MVC启动阶段1.3.2 MVC请求阶段getHandlergetHandlerAdapter调用业务Controller(核心)org.springframework.web.servlet.DispatcherServlet#processDispatchResult(视图解析和渲染)总结 通过源码看SpringMVC

m0_46690280的博客 375

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

不是吧不是吧!都2020年了你还没有看过SpringMVC源码?

前言: Spring MVC是Spring框架中用于Web应用快速开发的一个模块。Spring MVC的MVC 是Model-View-Contoller的缩写。它是一个广泛应用于图形化用户交互开发中的设计模式,不仅常见于 Web开发,也广泛应用于如Swing和JavaFX 等桌面开发。作为当今业界最主流的Web开发框架,Spring MVC(有时也称Spring Web MVC)的开发技能相当热门。 在大型网站和复杂系统的开发中,Java无疑具有很大的优势,而在Java的Web框架中Spring MV

Java圈全能架构师的博客 360

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Java八股文面试之框架篇(Spring、SpringMVC、Mybatis、Spring Security)

Java八股文面试之框架篇(Spring、SpringMVC、Mybatis)

y516369的博客 1304

面试官:你分析过SpringMVC的源码吗?

1. MVC使用 在研究源码之前,先来回顾以下springmvc是如何配置的,这将能使我们更容易理解源码。 1.1 web.xml 1 <servlet> 2 <servlet-name>mvc-dispatcher</servlet-name> 3 <servlet-class>org.springframewo...

weixin_43770982的博客 430

SpringMvc 源码解析

图1 首先加入 spring——webmvc依赖。 图2 首先我们要明白一个请求的过程。一个request 请求 首先经过一个 servlet 。然后同反射获取类名和方法名,调用controller类的对应方法。 图3 图4 setLonadOnStartup(1) 的作用是回执行DispatcherServlet的init()方法。 图5 ...

江南竹子 326

SpringMVC执行流程

gpt是的,Spring MVC中的请求处理流程是由DispatcherServlet作为中央控制器,根据请求的URL分发给相应的HandlerMapping进行处理。它是springmvc最为核心的组件 起到中央控制的作用。谁去处理我们的cotrol去处理例如咱们都在control包下放了userControl,员工Control 这都叫控制器 映射器 映射一个请求的路径 前端过来的请求他其实里面包含一个具体的圆也就是url 那么url到底要更哪个控制器control进行映射。

lpblog的博客 1063

面试有没有看过spring源码_面试官:你分析过SpringMVC的源码吗?

1. MVC使用在研究源码之前,先来回顾以下springmvc是如何配置的,这将能使我们更容易理解源码。1.1 web.xml1 2 mvc-dispatcher3 org.springframework.web.servlet.DispatcherServlet4 8 9 contextConfigLocation10 class...

weixin_39598308的博客 107

SpringMVC工作原理

1、Spring mvc介绍 SpringMVC框架是以请求为驱动,围绕Servlet设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是DispatcherServlet,它是一个Servlet,顶层是实现的Servlet接口。 2、SpringMVC使用 需要在web.xml中配置DispatcherServlet。并且需要配置spring监听...

dingjian0009的博客 674
上一篇: java基础学习:java中的反射
下一篇: Spring 源码阅读:基于 XML 配置初始化 Spring 上下文过程总结(10+详细流程图)
java_lujj
博客等级 码龄4年 18粉丝 152原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值