设计应用MultiActionController心得

设计使用MultiActionController心得
今天总结一下在一个Spring的项目中使用MultiActionController的应用小结。
在项目开发过程中,面对一个需要对一个域对象进行查询明细,查询列表和查询该域对象的关联对象的案例,这里不包括对域对象进行更新的工作,这样如果采用实现Controller接口的话,每个实现类执行handleRequest返回ModelAndView来达到目的,很自然的将会产生3个实现类,这样项目的类文件数量增多,而且在以后维护工作中,虽然在一个用例中,仍然不得不从配置文件下手,一个一个根据映射找出对应的相应的Controller,这添加了维护的难度,所以这里我们很自然的采用了MultiActionController做实现。代码基本类似如下:
public class BookMultiController extends MultiActionController{
	
	//(1)
	public ModelAndView getBook(HttpServletRequest request,
			HttpServletResponse response) {
		ModelAndView mv = new ModelAndView("book/book_detail");
		Book book = bookManager.getBook(request.getParameter("bookId"));
		mv.addObject("book", book);
		return mv;
	}
	
	//(2)
	public ModelAndView getBookList(HttpServletRequest request,
			HttpServletResponse response) {
		ModelAndView mv = new ModelAndView("book/book_list");
		Book book = new Book();
		BeanUtils.populate(book, request.getParameterMap());
		List list = bookManager.getBooks(book);
		mv.addObject("bookList", list);
		return mv;
	}
	
	//(3)
	public ModelAndView getAuthorByBook(HttpServletRequest request,
			HttpServletResponse response) {
		ModelAndView mv = new ModelAndView("author/author_detail");
		Author author = authorManager.getAuthorByBookId(request.getParameter("bookId"));
		mv.addObject("author", author);
		return mv;
	}
}

在Url配置映射方面,我们使用了org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver,所以上述代码
的访问是:
book.do?method=getBook&bookId=123;
book.do?method=getBookList&bookName=JavaDesign&price=100;
book.do?method=getAuthorByBook&bookId=123;
这样实现下来似乎很不错了。但是在开发过程中,这样情况还会出现很多,当我们碰到一个综合查询的时候,往往会有一颗树,点击树的每一个节点,通过传递一些参数,我们需要看到想要的结果显示在正确的页面。如果采用实现Controller的方法,那么每一个节点的点击将具体的对应到一个相应,通过处理返回ModelAndView。如果考虑这颗树的节点很多,那么是否应该随着节点的不断增加,controller也不断的增加,配置文件的不断增加呢?如果不这样,是否还是考虑使用MultiActionController呢?在这里我们的选择还是使用MultiActionController来实现,不过带来的后果就是有多少个节点,就意味着MultiActionController有多少个对应的方法执行来返回ModelAndView。在这里已经假设节点有很多的情况,那么就意味着这个Controller随着执行方法的增加,代码也会会很冗长。如果有30个节点的话,上面的方法将会有30个,代码一多,这时仔细看看,其实他们都很薄,也都很相似。这里薄和相似是他们
都没有像SimpleFormController那样需要绑定对象,对对象进行验证,保存和更新的动作,他们都是从用户请求那里拿到参数,然后返回给用户希望得到的东西,这就足够了。所以我们在这个基础上加了一些东西,让这些事情做起来更容易一些,在MultiActionController注入了一个IRequestHandler,由它来完成上述的需求,这样30个方法的查询,实现起来至多也就是2个。拿一开始的用例来举例,代码:
	public ModelAndView query(HttpServletRequest request,
			HttpServletResponse response) {
		requestHandler.dynamicInvocate(request);
		Solution solution = requestHandler.getSolution();
		return new ModelAndView(solution.getPage(),solution.getModelName(),solution.getModel());
	}
	
	public ModelAndView queryBook(HttpServletRequest request,
			HttpServletResponse response) {
		Book book = new Book();
		BeanUtils.populate(book, request.getParameterMap());
		Map<String,Object> map = new HashMap<String,Object>();
		map.put("book", book);
		requestHandler.dynamicInvocate(request, map);
		Solution solution = requestHandler.getSolution();
		return new ModelAndView(solution.getPage(),solution.getModelName(),solution.getModel());
	}

对应的访问变成:
query.do?method=query&purpose=getBook&bookId=123;
query.do?method=queryBook&purpose=getBookList&bookName=JavaDesign&price=100;
query.do?method=query&purpose=getAuthorByBook&bookId=123;
这里IRequestHandler有两个版本的dynamicInvocate,第一个最为简单,就是从请求控制域获取参数然后执行调用,并最终返回一个Solution对象,这里面封装了模型和视图。而第二个版本,则可以实现更多的参数传递的调用。如一开始的代码清单(2)中,是没有办法直接从请求中获取Book对象的,这样构建一个Map传递给调用,也能成功的得到模型和视图。完成了这些,回过头来再次考虑刚才那个拥有30个节点的树,如果足够幸运,所有的查询都是从request,或者session中获取的参数,那么只有一个MultiActionController类文件,并且只有一个方法。值得一提的是这个requestHandler我们是使用方法注入的,也就是他始终是以原型的形式出现,不会造成一些负面的影响,在我们系统测试运行期间,他运作的很好,效果还算满意。
大家对这样的设计有何看法,欢迎大家讨论。
1 楼 mazzystar 2007-04-23  
看了半天没太明白,
requestHandler算是业务层吗?
这么做是把controller里的多个方法提取成requestHandler里的一个方法吗??
这样的好处是减少了配置文件的代码量吗?
2 楼 lsy 2007-04-23  
应该说requestHandler是直接处理业务请求的,他和handleRequest完成的工作一样,就是获取模型和准备视图,其他的业务逻辑代码还是在serivce
层完成,只不过requestHandler需要从我们自行定义的一个xml开始工作。
刚才可能没有说清楚,我上传一个类图。这里我们主要使用xml,当然也可以是其他载体。上面的三个代码实际上是被写到了xml文件中,而requestHandler
通过反射和WebApplicationContextUtils来完成工作。对应上述的三个方法,xml写成:
	<item name="getBook">
		<operation action="bookManager" method="getBook">
			<parameter match="request">bookId</parameter>	
		</operation>
		<solution page="book/book_detail" modelName="book" />
	</item>	
	<item name="getBookList">
		<operation action="bookManager" method="getBooks">
			<parameter>bookName</parameter>	
			<parameter>price</parameter>
			<parameter match="custom" type="com.test.model.Book">book</parameter>			
		</operation>
		<solution page="book/book_list" modelName="bookList" />
	</item>
	<item name="getAuthorByBook">
		<operation action="authorManager" method="getAuthorByBookId">
			<parameter match="request" type="java.lang.String" >bookId</parameter>	
		</operation>
		<solution page="author/author_detail" modelName="author" />
	</item>

在类图中,XmlParser在容器启动的时候,首先将xml解析成一组<String,Item>静态的映射,然后等待请求的到来。在这一组映射中的每一个Item,都由
Operation和Solution合成,Solution封装了model,view。而Operation封装了执行业务逻辑方法的manager,参数,方法。RequestHelper则根据xml中定
义的东东从request,session或者自定义的map中获取方法执行的参数,并WebApplicationContextUtils获取项目上下文,然后通过getBean获取manager,
那么这样一切准备就绪,requestHandler就可以开始工作了。这样相对一开始给出的那段(1),(2),(3)的代码清单,就变成了这样三段xml片断,以后的增
量不再写实际代码,而是写一段xml代码片断替代,维护工作就是从这一个xml开始,这样我们觉得还是值得的。