SpringMVC处理器映射器HandlerMapping详解

目录

一、前言        

二、initHandlerMappings

三、处理器映射器架构

策略接口

请求链

模版类

四、RequestMappingHandlerMapping的初始化

HandlerMethod映射器模版类的初始化

AbstractHandlerMethodMapping.MappingRegistry:内部类注册中心

五、RequestMappingHandlerMapping映射器模版类的调用

RequestMappingHandlerMapping调用


一、前言        

昨天在进行spring的安全升级时,遇到一个奇葩问题:原始版本为4.1.9,升级至5.3.34,由于是几年前的项目,请求路径还是带有.action的老式写法(如login.action),而有的不带(如:index),升级完后,就发现了问题,login.action能访问到,请求index时,日志爆了 no mapping for GET。

        我们来看看是怎么回事。

        在SpringMVC中会有很多请求,每个请求都需要一个HandlerAdapter处理,具体接收到一个请求之后使用哪个HandlerAdapter进行处理呢,他们的过程是什么。本文将对此问题进行讨论

二、initHandlerMappings

DispatcherServlet在初始化中,会调用其initHandlerMappings方法注册HandlerMapping对象并放到其缓存池中,其过程如下:先查询容器中是否有处理器映射器,如果有就注册到其缓存池中,如果没有就安装默认到规则创建处理器映射器,并注册到其缓存池中。

private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;//detectAllHandlerMappings默认为true//true标志检测所有handlerMapping,false只获取“handlerMapping”bean。if (this.detectAllHandlerMappings) {// 在ApplicationContext中查找所有HandlerMappings,包括祖先上下文。Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());//排序AnnotationAwareOrderComparator.sort(this.handlerMappings);}}else {try {//只获取“handlerMapping”beanHandlerMapping 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.}}//通过注册,确保我们至少有一个HandlerMapping//如果找不到其他映射,则为默认HandlerMapping。if (this.handlerMappings == null) {//从spring-webmvc下的DispatcherServlet.properties读取默认配置this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}

Spring默认HandlerMapping有BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、RouterFunctionMapping

三、处理器映射器架构

策略接口

处理器映射器使用了策略模式

HandlerMapping是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事

HandlerMapping:负责映射用户的URL和对应的处理类Handler,HandlerMapping并没有规定这个URL与应用的处理类如何映射。所以在HandlerMapping接口中仅仅定义了根据一个URL必须返回一个由HandlerExecutionChain代表的处理链,我们可以在这个处理链中添加任意的HandlerAdapter实例来处理这个URL对应的请求(这样保证了最大的灵活性映射关系)。

请求链

模版类

处理器映射器都是实现AbstractHandlerMapping,该抽象类完成了所有的Handler以及handler里面所有的HandlerMethod的模版操作,但是怎么获取Handler,这些逻辑都是交给子类自己去实现,所以这层抽象可谓也是非常的灵活,并没有把Handler的实现方式定死。 

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupportimplements HandlerMapping, Ordered, BeanNameAware {//默认的Handler,这边使用的Obejct,子类实现的时候,使用HandlerMethod,HandlerExecutionChain等@Nullableprivate Object defaultHandler;// url路径计算的辅助类、工具类private UrlPathHelper urlPathHelper = new UrlPathHelper();// Ant风格的Path匹配模式~  解决如/books/{id}场景private PathMatcher pathMatcher = new AntPathMatcher();// 保存着拦截器们~~~private final List<Object> interceptors = new ArrayList<>();// 从interceptors中解析得到,直接添加给全部handlerprivate final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();// 跨域相关的配置~private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();private CorsProcessor corsProcessor = new DefaultCorsProcessor();// 最低的顺序(default: same as non-Ordered)private int order = Ordered.LOWEST_PRECEDENCE;@Nullableprivate String beanName;/*** Initializes the interceptors.* @see #extendInterceptors(java.util.List)* @see #initInterceptors()*/@Overrideprotected void initApplicationContext() throws BeansException {// 给子类扩展:增加拦截器,默认为空实现.RequestMappingHandlerMapping也没有重写这个方法extendInterceptors(this.interceptors);// 找到所有MappedInterceptor(截器是)类型的bean添加到adaptedInterceptors中detectMappedInterceptors(this.adaptedInterceptors);// 将interceptors中的拦截器取出放入adaptedInterceptors// 如果是WebRequestInterceptor类型的拦截器  需要用WebRequestHandlerInterceptorAdapter进行包装适配initInterceptors();}@Override@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {//根据请求获取对应的处理器,子类实现Object handler = getHandlerInternal(request);if (handler == null) {//如果获取不到,到默认到处理器中handler = getDefaultHandler();}//如果还没有处理器,返回nullif (handler == null) {return null;}// 意思是如果当前传入的handler是个String类型,那就根据其名字去容器内找这个Bean,当作一个Handler~if (handler instanceof String) {String handlerName = (String) handler;//到容器中找handler = obtainApplicationContext().getBean(handlerName);}//根据handler和request构造一个请求处理链~~HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);// 4.2版本提供了对CORS跨域资源共享的支持  此处暂时略过~if (hasCorsConfigurationSource(handler)) {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;}@Nullableprotected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
}

getHandlerInternal模板方法的实现类: 

接下来最重要的就是以getHandlerInternal()方法为主线,看看其子类们的实现。它主要分为两大主线: AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping。

本文是以AbstractHandlerMethodMapping的子类RequestMappingHandlerMapping为主线

四、RequestMappingHandlerMapping的初始化

HandlerMethod映射器都是处理器映射器类型的映射器。这种类型的映射器有一个模版类AbstractHandlerMethodMapping, 所有的HandlerMethod映射器都是他的实现。

AbstractHandlerMethodMapping包括其初始化和调用过程。为了好讲解,在这里就将其初始化和调用过程代码分开说

HandlerMethod映射器模版类的初始化

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();static {ALLOW_CORS_CONFIG.addAllowedOrigin("*");ALLOW_CORS_CONFIG.addAllowedMethod("*");ALLOW_CORS_CONFIG.addAllowedHeader("*");ALLOW_CORS_CONFIG.setAllowCredentials(true);}private boolean detectHandlerMethodsInAncestorContexts = false;@Nullableprivate HandlerMethodMappingNamingStrategy<T> namingStrategy;//注册表,HandlerMapping在容器启动过程中初始化,把扫描到的handler放到注册表中private final MappingRegistry mappingRegistry = new MappingRegistry();@Overridepublic void afterPropertiesSet() {initHandlerMethods();}protected void initHandlerMethods() {//循环所有的beanfor (String beanName : getCandidateBeanNames()) {//如果bean名字不是以scopedTarget.开头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) {if (logger.isTraceEnabled()) {logger.trace("Could not resolve type for bean '" + beanName + "'", ex);}}//因为这里我们是研究RequestMappingHandlerMapping,所以这局代码内容如下//如果beanType不为null,且类上标注@Controller注解或者@RequestMapping注解if (beanType != null && isHandler(beanType)) {detectHandlerMethods(beanName);}}protected void detectHandlerMethods(Object handler) {Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {Class<?> userType = ClassUtils.getUserClass(handlerType);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.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);//底层使用了MappingRegistry的register方法registerHandlerMethod(handler, invocableMethod, mapping);});}}//忽略处理器映射器查询Handler部分代码.....
}

1、循环所有的bean,如果bean名字不是以scopedTarget.开头,那么就判断他们是否是Handler(类上标注@Controller注解或者@RequestMapping注解)

2、如果是Handler,获取这个类上所有标注@RequestMapping的方法信息,以RequestMappingInfo形式

3、把他们储存到MappingRegistry中

AbstractHandlerMethodMapping.MappingRegistry:内部类注册中心

维护几个Map(键值对),用来存储映射的信息, 还有一个MappingRegistration专门保存注册信息 这个注册中心,核心是保存了多个Map映射关系,相当于缓存下来。在请求过来时需要查找的时候,可以迅速定位到处理器

class MappingRegistry {//对于RequestMappingHandlerMapping来说//保存着RequestMappingInfo和MappingRegistration的对应关系~private final Map<T, MappingRegistration<T>> registry = new HashMap<>();// 对于保存着mapping和HandlerMethod的对应关系~//对于RequestMappingHandlerMapping来说//保存着RequestMappingInfo和HandlerMethod的对应关系~private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();// 这里的Map不是普通的Map,而是MultiValueMap,它是个多值Map。其实它的value是一个list类型的值// 至于为何是多值?有这么一种情况  URL都是/api/v1/hello  但是有的是get post delete等方法   所以有可能是会匹配到多个MappingInfo的//对于RequestMappingHandlerMapping来说,保存着URL和RequestMappingInfo的关系~private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();//对于RequestMappingHandlerMapping来说,保存着URL和HandlerMethod的关系~private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>()// 这两个就不用解释了private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();// 读写锁~~~ 读写分离  提高启动效率private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();//5.1版本其子类只有一个RequestMappingHandlerMapping,T就是RequestMappingInfo//handler一般情况下是处理器方法从属bean的名字//method是处理器方法public void register(T mapping, Object handler, Method method) {if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {throw new IllegalStateException("Unsupported suspending handler method detected: " + method);}this.readWriteLock.writeLock().lock();try {HandlerMethod handlerMethod = createHandlerMethod(handler, method);//断言提供的映射是唯一的。			validateMethodMapping(handlerMethod, mapping);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);}//初始化跨域配置//使用的是AbstractHandlerMethodMapping的initCorsConfiguration方法,子类实现CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {this.corsLookup.put(handlerMethod, corsConfig);}this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));}finally {this.readWriteLock.writeLock().unlock();}}

这个注册中心,核心是保存了多个Map映射关系,相当于缓存下来。在请求过来时需要查找的时候,可以迅速定位到处理器

在其初始化过程中,其主要模版化的2个方法

protected CorsConfiguration initCorsConfiguration(Object handler, Method method, T mapping) {return null;}
protected abstract boolean isHandler(Class<?> beanType);

五、RequestMappingHandlerMapping映射器模版类的调用

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();static {ALLOW_CORS_CONFIG.addAllowedOrigin("*");ALLOW_CORS_CONFIG.addAllowedMethod("*");ALLOW_CORS_CONFIG.addAllowedHeader("*");ALLOW_CORS_CONFIG.setAllowCredentials(true);}private boolean detectHandlerMethodsInAncestorContexts = false;@Nullableprivate HandlerMethodMappingNamingStrategy<T> namingStrategy;//注册表,HandlerMapping在容器启动过程中初始化,把扫描到的handler放到注册表中private final MappingRegistry mappingRegistry = new MappingRegistry();//忽略初始化部分代码.....protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {//获取请求路径String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);//放到请求属性中request.setAttribute(LOOKUP_PATH, lookupPath);this.mappingRegistry.acquireReadLock();try {//根据请求和路径获取对应的处理方法,注册表中取HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);}finally {this.mappingRegistry.releaseReadLock();}}protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {// Match是一个private class,内部就两个属性:T mapping和HandlerMethod handlerMethodList<Match> matches = new ArrayList<>();// 根据lookupPath去注册中心里查找RequestMappingInfo,因为一个具体的url可能匹配上多个RequestMappingInfo// 至于为何是多值?有这么一种情况  URL都是/api/v1/hello  但是有的是get post delete等方法  等不一样,都算多个的  所以有可能是会匹配到多个MappingInfo的// 所有这个里可以匹配出多个出来。比如/hello 匹配出GET、POST、PUT都成,所以size可以为3List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {// 依赖于子类实现的抽象方法:getMatchingMapping()  看看到底匹不匹配,而不仅仅是URL匹配就行// 比如还有method、headers、consumes等等这些不同都代表着不同的MappingInfo的// 最终匹配上的,会new Match()放进matches里面去addMatchingMappings(directPathMatches, matches, request);}// 当还没有匹配上的时候,别无选择,只能浏览所有映射// 这里为何要浏览所有的mappings呢?而不是报错404呢?// 增加路径匹配对范围,如:/rest 匹配 /rest.ssssif (matches.isEmpty()) {addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}// 只要找到了一个匹配的  就进来这里了~~~// 请注意:因为到这里   匹配上的可能还不止一个  所以才需要继续处理~~if (!matches.isEmpty()) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));matches.sort(comparator);//如果匹配到多个,就取第一个Match bestMatch = matches.get(0);if (matches.size() > 1) {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(" ");}}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);}}	
}
protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
}
@Nullable
protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)throws Exception {
return null;
}

RequestMappingHandlerMapping调用

public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {private static final Method HTTP_OPTIONS_HANDLE_METHOD;/*** Expose URI template variables, matrix variables, and producible media types in the request.* @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE* @see HandlerMapping#MATRIX_VARIABLES_ATTRIBUTE* @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE*/@Overrideprotected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {super.handleMatch(info, lookupPath, request);String bestPattern;Map<String, String> uriVariables;Set<String> patterns = info.getPatternsCondition().getPatterns();if (patterns.isEmpty()) {bestPattern = lookupPath;uriVariables = Collections.emptyMap();}else {bestPattern = patterns.iterator().next();uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);}request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);if (isMatrixVariableContentAvailable()) {Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);}Map<String, String> decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);}}private boolean isMatrixVariableContentAvailable() {return !getUrlPathHelper().shouldRemoveSemicolonContent();}private Map<String, MultiValueMap<String, String>> extractMatrixVariables(HttpServletRequest request, Map<String, String> uriVariables) {Map<String, MultiValueMap<String, String>> result = new LinkedHashMap<>();uriVariables.forEach((uriVarKey, uriVarValue) -> {int equalsIndex = uriVarValue.indexOf('=');if (equalsIndex == -1) {return;}int semicolonIndex = uriVarValue.indexOf(';');if (semicolonIndex != -1 && semicolonIndex != 0) {uriVariables.put(uriVarKey, uriVarValue.substring(0, semicolonIndex));}String matrixVariables;if (semicolonIndex == -1 || semicolonIndex == 0 || equalsIndex < semicolonIndex) {matrixVariables = uriVarValue;}else {matrixVariables = uriVarValue.substring(semicolonIndex + 1);}MultiValueMap<String, String> vars = WebUtils.parseMatrixVariables(matrixVariables);result.put(uriVarKey, getUrlPathHelper().decodeMatrixVariables(request, vars));});return result;}/*** Iterate all RequestMappingInfo's once again, look if any match by URL at* least and raise exceptions according to what doesn't match.* @throws HttpRequestMethodNotSupportedException if there are matches by URL* but not by HTTP method* @throws HttpMediaTypeNotAcceptableException if there are matches by URL* but not by consumable/producible media types*/@Overrideprotected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath, HttpServletRequest request) throws ServletException {PartialMatchHelper helper = new PartialMatchHelper(infos, request);if (helper.isEmpty()) {return null;}if (helper.hasMethodsMismatch()) {Set<String> methods = helper.getAllowedMethods();if (HttpMethod.OPTIONS.matches(request.getMethod())) {HttpOptionsHandler handler = new HttpOptionsHandler(methods);return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD);}throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods);}if (helper.hasConsumesMismatch()) {Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();MediaType contentType = null;if (StringUtils.hasLength(request.getContentType())) {try {contentType = MediaType.parseMediaType(request.getContentType());}catch (InvalidMediaTypeException ex) {throw new HttpMediaTypeNotSupportedException(ex.getMessage());}}throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes));}if (helper.hasProducesMismatch()) {Set<MediaType> mediaTypes = helper.getProducibleMediaTypes();throw new HttpMediaTypeNotAcceptableException(new ArrayList<>(mediaTypes));}if (helper.hasParamsMismatch()) {List<String[]> conditions = helper.getParamConditions();throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap());}return null;}
}

RequestMappingHandlerMapping根据请求获取对应的handlerMethod过程是:

1、获取请求路径

2、根据路径到注册表中查询对应路径的RequestMappingInfo

3、如果匹配到多个,就取第一个。

4、如果匹配不到,就到注册表中查询所有RequestMappingInfo,匹配规则我们可以自定义。

Spring MVC请求URL带后缀匹配的情况,如/hello.json也能匹配/hello

RequestMappingInfoHandlerMapping 在处理http请求的时候, 如果 请求url 有后缀,如果找不到精确匹配的那个@RequestMapping方法。 那么,就把后缀去掉,然后.*去匹配,这样,一般都可以匹配,默认这个行为是被开启的。4.3以后是关闭的

比如有一个@RequestMapping("/rest"), 那么精确匹配的情况下, 只会匹配/rest请求。 但如果我前端发来一个 /rest.abcdef 这样的请求, 又没有配置 @RequestMapping("/rest.abcdef") 这样映射的情况下, 那么@RequestMapping("/rest") 就会生效。

这样会带来什么问题呢?绝大多数情况下是没有问题的,但是如果你是一个对权限要求非常严格的系统,强烈关闭此项功能,否则你会有意想不到的"收获"。本文的一开始夜抛出了这个问题

究其原因咱们可以接着上面的分析,其实就到了PatternsRequestCondition这个类上,具体实现是它的匹配逻辑来决定的。

public final class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {...@Override@Nullablepublic PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {// patterns表示此MappingInfo可以匹配的值们。一般对应@RequestMapping注解上的patters数组的值if (this.patterns.isEmpty()) {return this;}// 拿到待匹配的值,比如此处为"/hello.json"String lookupPath = this.pathHelper.getLookupPathForRequest(request);// 最主要就是这个方法了,它拿着这个lookupPath匹配~~~~List<String> matches = getMatchingPatterns(lookupPath);// 此处如果为empty,就返回null了~~~~return (!matches.isEmpty() ? new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions) : null);}public List<String> getMatchingPatterns(String lookupPath) {List<String> matches = new ArrayList<>();for (String pattern : this.patterns) {// 最最最重点就是在getMatchingPattern()这个方法里~~~ 拿着lookupPath和pattern看它俩合拍不~String match = getMatchingPattern(pattern, lookupPath);if (match != null) {matches.add(match);}}// 解释一下为何匹配的可能是多个。因为url匹配上了,但是还有可能@RequestMapping的其余属性匹配不上啊,所以此处需要注意  是可能匹配上多个的  最终是唯一匹配就成~if (matches.size() > 1) {matches.sort(this.pathMatcher.getPatternComparator(lookupPath));}return matches;}// // ===============url的真正匹配规则  非常重要~~~===============// 注意这个方法的取名,上面是负数,这里是单数~~~~命名规范也是有艺术感的@Nullableprivate String getMatchingPattern(String pattern, String lookupPath) {// 完全相等,那就不继续聊了~~~if (pattern.equals(lookupPath)) {return pattern;}// 注意了:useSuffixPatternMatch 这个属性就是我们最终要关闭后缀匹配的关键// 这个值默外部给传的true(其实内部默认值是boolean类型为false)if (this.useSuffixPatternMatch) {// 这个意思是若useSuffixPatternMatch=true我们支持后缀匹配。我们还可以配置fileExtensions让只支持我们自定义的指定的后缀匹配,而不是下面最终的.*全部支持if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {for (String extension : this.fileExtensions) {if (this.pathMatcher.match(pattern + extension, lookupPath)) {return pattern + extension;}}}// 若你没有配置指定后缀匹配,并且你的handler也没有.*这样匹配的,那就默认你的pattern就给你添加上后缀".*",表示匹配所有请求的url的后缀~~~else {boolean hasSuffix = pattern.indexOf('.') != -1;if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {return pattern + ".*";}}}// 若匹配上了 直接返回此patterif (this.pathMatcher.match(pattern, lookupPath)) {return pattern;}// 这又是它支持的匹配规则。默认useTrailingSlashMatch它也是true// 这就是为何我们的/hello/也能匹配上/hello的原因  // 从这可以看出,Spring MVC的宽容度是很高的,容错处理做得是非常不错的~~~~~~~if (this.useTrailingSlashMatch) {if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {return pattern + "/";}}return null;}
}

 分析了URL的匹配原因,现在肯定知道为何默认情况下"/hello.aaaa"或者"/hello.aaaa/“或者”"/hello/""能匹配上我们/hello的原因了吧~~~

Spring和SpringBoot中如何关闭此项功能呢?

为何要关闭的理由,上面其实已经说了。当我们涉及到严格的权限校验(强权限控制)的时候。特备是一些银行系统、资产系统等等,关闭后缀匹配事非常有必要的。

高版本直接默认就是关闭的

可以看到这两个属性值都直接冒泡到RequestMappingHandlerMapping这个实现类上来了,所以我们直接通过配置来改变它的默认行为就成。

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {// 开启后缀名匹配,开启最后一个/匹配@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.setUseSuffixPatternMatch(true);configurer.setUseTrailingSlashMatch(true);}
}

或者通过xml方式开启

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/35816.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电子名片小程序源码系统 前后端分离 带完整的安装代码包以及搭建教程

系统概述 电子名片小程序源码系统是一款基于前后端分离架构的综合性平台&#xff0c;旨在为用户提供一个集销售名片和企业商城于一体的解决方案。该系统采用先进的技术手段&#xff0c;实现了个性化名片设计、便捷的销售功能、企业商城模块等一系列实用功能。同时&#xff0c;…

72V转12V非隔离DC/DC电源原理图+PCB源文件

资料下载地址&#xff1a;72V转12V非隔离DCDC电源原理图PCB源文件 电动车所用的非隔离DC/DC电源&#xff0c;采用BUCK电路&#xff0c;运行稳定&#xff0c;为已经在产品中使用的电路 1、原理图 2、PCB

Python爬取中国天气网天气数据.

一、主题式网络爬虫设计方案 1.主题式网络爬虫名称 名称&#xff1a;Python爬取中国天气网天气数据 2.主题式网络爬虫爬取的内容与数据特征分析 本次爬虫主要爬取中国天气网天气数据 3.主题式网络爬虫设计方案概述&#xff08;包括实现思路与技术难点&#xff09; reques…

ET实现游戏中邮件系统逻辑思路(服务端)

ET是一个游戏框架&#xff0c;用的编程语言是C#&#xff0c;游戏引擎是Unity&#xff0c;框架作者&#xff1a;熊猫 ET社区 在游戏中我们通常都会看到有邮件系统&#xff0c;邮件系统的作用有给玩家通知、发放奖励等 下面小编使用ET框架带大家看一下邮件系统的一种实现方…

「C系列」C 经典练习实例

文章目录 1. 基本输入输出2. 字符串操作3. 数组与循环4. 函数与递归5. 逻辑与条件6. 数学问题7. 数字与数学8. 数组与字符串9. 逻辑与条件10. 结构体和联合体11. 指针12. 文件操作13. 动态内存分配相关链接 C语言经典练习实例及详细代码可以涵盖多个方面&#xff0c;从基础输入…

中国近10年AGV共部署了351700台……

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 根据网络上公开数据获得如下信息。仅供参考。 如下为中国近十年AGV部署的公开数据&#xff1a; 2014-2018年&#xff0c;中国AGV机器人累计销量达…

没有思考过 Embedding,不足以谈 AI

在当今的人工智能&#xff08;AI&#xff09;领域&#xff0c;Embedding 是一个不可或缺的概念。如果你没有深入理解过 Embedding&#xff0c;那么就无法真正掌握 AI 的精髓。接下来&#xff0c;我们将深入探讨 Embedding 的基本概念。 1. Embedding的基本概念 1.1 什么是 Emb…

【数据采集】亮数据浏览器、亮网络解锁器实战指南

前言 继上次我们写了数据采集与AI分析&#xff0c;亮数据通义千问助力跨境电商前行的文章之后&#xff0c;好多小伙伴来后台留言&#xff0c;表示对亮数据的数据采集非常感兴趣&#xff0c;并且感觉用起来非常顺手&#xff0c;大大减少了小白用户获取数据的成本。 在这儿&…

【python爬虫实战】爬取书店网站的 书名价格(注释详解)

思路来源&#xff1a;b站视频【【Python爬虫】爆肝两个月&#xff01;拜托三连了&#xff01;这绝对是全B站最用心&#xff08;没有之一&#xff09;的Python爬虫公开课程&#xff0c;从入门到&#xff08;不&#xff09;入狱 &#xff01;-哔哩哔哩】 https://b23.tv/M79rxMd …

docker 学习之路

文章目录 1、官方文档2、常用命令挂载Docker容器内运行的脚本或命令常用 3、介绍4、Dockerfile5、问题6、链接 ​ 1、官方文档 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Linux…

基于 Paimon 的袋鼠云实时湖仓入湖实战剖析

在当今数据驱动的时代&#xff0c;企业对数据的实施性能力提出了前所未有的高要求。为了应对这一挑战&#xff0c;构建高效、灵活且可扩展的实时湖仓成为数字化转型的关键。本文将深入探讨袋鼠云数栈如何通过三大核心实践——ChunJun 融合 Flink CDC、MySQL 一键入湖至 Paimon …

我用低代码平台自己搭建了一套MES应用系统,1天搞定!

MES系统是什么 MES系统是一套面向制造企业车间执行层的生产信息化管理系统。它能够为操作人员和管理人员提供计划的执行、跟踪以及所有资源&#xff08;包括人、设备、物料、客户需求等&#xff09;的当前状态。通过MES系统可以对从订单下达到产品完成的整个生产过程进行优化管…

Intellij Idea显示回退和前进按钮的方法

方法1 使用快捷键&#xff1a; 回到上一步 ctrl alt <-&#xff08;左方向键&#xff09;回到下一步 ctrl alt ->&#xff08;右方向键&#xff09; 方法2&#xff1a; Preferences -> Appearance & Behavior -> Menus and Toolbars -> Navigation B…

生信技能50 - 本地构建Clinvar数据库VCF变异位点快速搜索功能

1. Clinvar数据库文件下载 参考本人文章: 生信技能40 - Clinvar数据库VCF文件下载和关键信息提取 # 下载GRCh37 vcf wget -c -b https://ftp.ncbi.nlm.nih.gov/pub/clinvar/vcf_GRCh37/clinvar_20240624.vcf.gz wget https://ftp.ncbi.nlm.nih.gov/pub/clinvar/vcf_GRCh37/…

森林草原火险因子综合监测系统解决方案

一、概述 根据应急管理部和国家统计局发布的数据&#xff0c;2023 年全国共接报火灾 74.5 万起&#xff0c;其中森林火灾 328 起&#xff0c;共发生草原火灾 15 起。为应对这一严峻挑战我国正面临着森林草原火险的严重威胁。森林火灾不仅对生态文明建设构成严重威胁&#xff0c…

顶顶通呼叫中心中间件-透传uuid并且导入对端变量到本端(mod_cti基于Freeswitch)

一、配置拨号方案 win-ccadmin配置方法 点击拨号方案 -> 点击进入排队 -> 根据图中配置。如果不是排队转人工是机器人转人工那么就是在机器人那个拨号方案配置&#xff0c;并且需要配置在"cti_robot"之前即可 action"set" data"sip_h_X_tas…

第2章.现场设备的物联网模式--设备网关

第2章.现场设备的物联网模式 本章列出了与现场设备或事物相关的关键模式。阅读本章后&#xff0c;您将能够识别物联网架构中这些模式的存在。它提供了有关模式适合或适用的场景的详细信息&#xff0c;以及需要考虑的约束。这将帮助您相对轻松地理解现有的物联网架构。 本章涵盖…

【论文阅读】Answering Label-Constrained Reachability Queries via Reduction Techniques

Cai Y, Zheng W. Answering Label-Constrained Reachability Queries via Reduction Techniques[C]//International Conference on Database Systems for Advanced Applications. Cham: Springer Nature Switzerland, 2023: 114-131. Abstract 许多真实世界的图都包含边缘标签…

腾讯云TI平台的大模型精调解决方案

腾讯云TI平台的大模型精调解决方案 随着人工智能和大数据技术的快速发展&#xff0c;大模型在各行各业的应用日益广泛。然而&#xff0c;大规模模型的训练和部署面临着诸多挑战&#xff0c;包括训练资源的高效利用、模型训练的稳定性和国产化适配需求。腾讯云TI平台凭借其强大…

从@Param注解开始,深入了解 MyBatis 参数映射的原理

系列文章目录 MyBatis缓存原理 Mybatis plugin 的使用及原理 MyBatisSpringboot 启动到SQL执行全流程 数据库操作不再困难&#xff0c;MyBatis动态Sql标签解析 Mybatis的CachingExecutor与二级缓存 使用MybatisPlus还是MyBaits &#xff0c;开发者应该如何选择&#xff1f; 巧…