最近一段时间Spring MVC吵得挺火的,项目中也有用到,于是趁这个机会研究了下MultiActionController.
MultiActionController的用途:
在使用Spring提供的控制器时,AbstractController和SimpleFormController是应用得最多的。AbstractController是最基本的Controller,可以给予用户最大的灵活性。
SimpleFormController则用于典型的表单编辑和提交。在一个需要增,删,改,查的需求中,增加和修改扩展SimpleFormController完成,删除和查询则扩展AbstractController完成。
但是像上面那样完成某一业务对象的增,删,改,查,都属于一类相关的业务。把一类相关的操作分布到不同的类去完成,违返“高内聚”的设计原则。这样四个业务操作需要四个类来完成,造成太多的类文件,难以维护和配置。
所以Spring借鉴Struts的DispatchAction提供了类似功能的MultiActionController。可以实现不同的请求路径对应MultiActionController中的不同方法,这样就可以把相关的操作都在一个类的相关方法中完成。这样使得这个类具有“高内聚”,也利于系统的维护,还避免了重复代码。增加和修改操作的数据验证逻辑是很相似的,使用MultiActionController后就可以让增加和修改操作共用一段数据验证逻辑代码。
MultiActioncontroller的结构:
public class MultiActionController extends AbstractController implements LastModified {
本文查看的spring3.1.0的源代码,由上述声明可知,MultiActionController类扩展至AbstractController对象,主要业务逻辑在重写方法:
/**
* Template method. Subclasses must implement this.
* The contract is the same as for <code>handleRequest</code>.
* @see #handleRequest
*/
protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception;
MultiActionController中对上述方面的具体实现如下:
/**
* Determine a handler method and invoke it.
* @see MethodNameResolver#getHandlerMethodName
* @see #invokeNamedMethod
* @see #handleNoSuchRequestHandlingMethod
*/
@Override
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);
}
}
由上述代码可知,具体的业务逻辑是:
String methodName = this.methodNameResolver.getHandlerMethodName(request);
1) 通过配置的methodNameResolver确定要引用的方法名。
return invokeNamedMethod(methodName, request, response);
2) 根据确定的方法名调用响应的方法,执行业务逻辑。
1。1) methodNameResolver的确定,
methodNameResolver变量类型为接口 MethodNameResolver, 实现MethodNameResolver接口的常用的有以下三个:
A) InternalPathMethodNameResolver 该类型为默认实现,即若没有配置methodNameResolver属性时使用。 它的主要用法如下:
Maps the resource name after the last slash, ignoring an extension.
E.g. "/foo/bar/baz.html" to "baz", assuming a "/foo/bar/baz.html" controller mapping to the corresponding MultiActionController handler. method.
中文意思就是: 以最后一个斜杠为分界线,取得最后的部分,去掉后缀名就是你的映射方法名。
B) ParameterMethodNameResolver 这个用的很普遍, 默认的paramName为action。 具体用法如下:
配置该类的paramName为你想要用来指定方法属性的名字,比如什么action、method、command都行,什么都行,不配置的话,就是默认的action。
名称定位器就会获取该属性(名称为paramName配置的值)的值作为方法名。
C) PropertiesMethodNameResolver 这个可以单独配置properties文件来指定url--methodName的映射。每个都需要配置,重用性不好。具体用法:
The most flexible out-of-the-box implementation of the MethodNameResolver interface.
Uses java.util.Properties to define the mapping between the URL of incoming requests and the corresponding method name.
Such properties can be held in an XML document.
Properties format is /welcome.html=displayGenresPage Note that method overloading isn't allowed, so there's no need to specify arguments.
Supports direct matches, e.g. a registered "/test" matches "/test", and a various Ant-style pattern matches,
e.g. a registered "/t*" matches both "/test" and "/team". For details, see the AntPathMatcher javadoc.
2)invokeNamedMethod(methodName, request, response)的详细流程:
/**
* Invokes the named method.
* <p>Uses a custom exception handler if possible; otherwise, throw an
* unchecked exception; wrap a checked exception or Throwable.
*/
protected final ModelAndView invokeNamedMethod(
String methodName, HttpServletRequest request, HttpServletResponse response) throws Exception {
Method method = this.handlerMethodMap.get(methodName);
if (method == null) {
throw new NoSuchRequestHandlingMethodException(methodName, getClass());
}
try {
Class[] paramTypes = method.getParameterTypes();
List<Object> params = new ArrayList<Object>(4);
params.add(request);
params.add(response);
if (paramTypes.length >= 3 && paramTypes[2].equals(HttpSession.class)) {
HttpSession session = request.getSession(false);
if (session == null) {
throw new HttpSessionRequiredException(
"Pre-existing session required for handler method '" + methodName + "'");
}
params.add(session);
}
// If last parameter isn't of HttpSession type, it's a command.
if (paramTypes.length >= 3 &&
!paramTypes[paramTypes.length - 1].equals(HttpSession.class)) {
Object command = newCommandObject(paramTypes[paramTypes.length - 1]);
params.add(command);
bind(request, command);