Spring之SpringMVC的MethodNameResolver(源码)分析
前言
在介绍SpringMVC 的Controller的具体实现中,我们讲到了MultiActionController。在获取处理请求对于的方法的时候我们用到了下面的代码,来自于MultiActionController的handleRequestInternal的方法:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { try { String methodName = this.methodNameResolver.getHandlerMethodName(request); return invokeNamedMethod(methodName, request, response); } catch (NoSuchRequestHandlingMethodException ex) { return handleNoSuchRequestHandlingMethod(ex, request, response); } }
MultiActionController通过methodNameResolver的getHandlerMethodName()方法获取处理该请求的具体方法名字。来看下methodNameResolver具体实现。
1.MethodNameResolver介绍
MethodNameResolver是个接口,来看看定义
public interface MethodNameResolver { String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException; }
这个接口用于MultiActionController方法中获取具体的参数,它是通过策略模式来实现的。它可以通过具体的请求映射到处理的方法名上,他们的改变也不会影响到其他应用程序。看看下面其他的具体实现。MethodNameResolver的实现类包括ParameterMethodNameResolver、AbstractUrlMethodNameResolver、InternalPathMethodNameResolver、PropertiesMethodNameResolver。
2.ParameterMethodNameResolver
ParameterMethodNameResolver实现了MethodNameResolver接口,它支持通过参数值来映射到方法名上。通过注释知道可以通过下面的过程来获取具体的方法:
1.根据请求的参数名解析功能方法名;
2.根据请求参数名的值解析功能方法名,默认的参数名是 action,即请求的参数中含有“action=query” ,则功能处理方法名为 query;
3.逻辑功能方法名到真实功能方法名映射;
4.默认的方法名,当以上策略失败时默认调用的方法名。
来具体获取处理器方法名过程:
{ String methodName = null; // 检查参数名是否存在 if (this.methodParamNames != null) { for (String candidate : this.methodParamNames) { if (WebUtils.hasSubmitParameter(request, candidate)) { methodName = candidate; if (logger.isDebugEnabled()) { logger.debug("Determined handler method '" + methodName + "' based on existence of explicit request parameter of same name"); } break; } } } // 检查参数名的值是否存在 if (methodName == null && this.paramName != null) { methodName = request.getParameter(this.paramName); if (methodName != null) { if (logger.isDebugEnabled()) { logger.debug("Determined handler method '" + methodName + "' based on value of request parameter '" + this.paramName + "'"); } } } if (methodName != null && this.logicalMappings != null) { // Resolve logical name into real method name, if appropriate. String originalName = methodName; methodName = this.logicalMappings.getProperty(methodName, methodName); if (logger.isDebugEnabled()) { logger.debug("Resolved method name '" + originalName + "' to handler method '" + methodName + "'"); } } if (methodName != null && !StringUtils.hasText(methodName)) { if (logger.isDebugEnabled()) { logger.debug("Method name '" + methodName + "' is empty: treating it as no method name found"); } methodName = null; } if (methodName == null) { if (this.defaultMethodName != null) { // No specific method resolved: use default method. methodName = this.defaultMethodName; if (logger.isDebugEnabled()) { logger.debug("Falling back to default handler method '" + this.defaultMethodName + "'"); } } else { // If resolution failed completely, throw an exception. throw new NoSuchRequestHandlingMethodException(request); } } return methodName; }
3、PropertiesMethodNameResolver
PropertiesMethodNameResolver和InternalPathMethodNameResolver继承了抽象类AbstractUrlMethodNameResolver。而AbstractUrlMethodNameResolver提供了方法名的获取的方法。
public final String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException { String urlPath = this.urlPathHelper.getLookupPathForRequest(request); String name = getHandlerMethodNameForUrlPath(urlPath); if (name == null) { throw new NoSuchRequestHandlingMethodException(urlPath, request.getMethod(), request.getParameterMap()); } if (logger.isDebugEnabled()) { logger.debug("Returning handler method name '" + name + "' for lookup path: " + urlPath); } return name; }
由上面的代码可知,子类的差异在于 String name = getHandlerMethodNameForUrlPath(urlPath);这段代码getHandlerMethodNameForUrlPath是抽象方法,具体的实现见子类。首先看下PropertiesMethodNameResolver的getHandlerMethodNameForUrlPath的实现:
PropertiesMethodNameResolver提供自定义的从请求 URL 解析功能方法的方法名,使用一组用户自定义的模式到功能方法名的映射,映射使用 Properties 对象存放,
protected String getHandlerMethodNameForUrlPath(String urlPath) { String methodName = this.mappings.getProperty(urlPath); if (methodName != null) { return methodName; } Enumeration propNames = this.mappings.propertyNames(); while (propNames.hasMoreElements()) { String registeredPath = (String) propNames.nextElement(); if (this.pathMatcher.match(registeredPath, urlPath)) { return (String) this.mappings.get(registeredPath); } } return null; }
用法如下:
<bean id="propertiesMethodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver"> <property name="mappings"> <props> <prop key="/create">create</prop> <prop key="/update">update</prop> <prop key="/delete">delete</prop> <prop key="/list">list</prop> <!-- 默认的行为 --> <prop key="/**">list</prop> </props> </property> </bean>
4.InternalPathMethodNameResolver
InternalPathMethodNameResolver是MethodNameResolver的默认实现,在MultiActionController中的MethodNameResolver的初始化可知,
private MethodNameResolver methodNameResolver = new InternalPathMethodNameResolver()
这个解析器提供从请求 URL 路径解析处理器方法的方法名,从请求的最后一个路径(/)开始,并忽略扩展名;如请求 URL 是“/user/list.html” ,则解析的功能处
理方法名为“list” ,即调用 list 方法。该解析器还可以指定前缀和后缀,通过 prefix 和 suffix 属性来判断。下面就是详细的getHandlerMethodName方法的实现过程。
public final String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException { String urlPath = this.urlPathHelper.getLookupPathForRequest(request); String name = getHandlerMethodNameForUrlPath(urlPath); if (name == null) { throw new NoSuchRequestHandlingMethodException(urlPath, request.getMethod(), request.getParameterMap()); } if (logger.isDebugEnabled()) { logger.debug("Returning handler method name '" + name + "' for lookup path: " + urlPath); } return name; }
5.总结说明
MethodNameResolver 的实现是通过策略模式来的,通过这个也可以是发现,Spring的实现中大量采用了设计模式的相关知识,如果Controller的实现中采用了模板设计模式一样。如果自己能够灵活应用这些设计模式,并且有个很好的思想,我想写出这样优秀的代码应该也不是问题,加油,共勉。