Spring MVC 3.x annotated controller的几点心得体会

[b]最近拿Spring MVC 3.x做项目,用了最新的系列相关Annotation来做Controller,有几点心得体会值得分享。 [/b]


转载请注明 :IT进行时(zhengxianquan AT hotmail.com) from
[url]http://itstarting.iteye.com/[/url]


[b]一、编写一个AbstractController.java,所有的Controller必须扩展 [/b]

除了获得Template Design Pattern的好处之外,还能一点,那就是放置全局的@InitBinder:


public class AbstractController {
...
@InitBinder
protected void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class,new CustomDateEditor(dateFormat, false));
}
...
}

[b]二、每个域都应该有一个Controller,且做好URI规划[/b]

大家都知道Spring MVC 3.x是完全支持Restful的,我们把URI做好规划,对于诸如ACL的实现会有很大的帮助。建议的URI规划如下:{Domain}[/{SubDomain}]/{BusinessAction}/{ID}。比如:

hotels/bookings/cancel/{id} ——表示此URI匹配hotels域的bookings子域,将要进行的是取消某项booking的操作。代码如下:


@Controller
@RequestMapping(value = "/hotels")
public class HotelsController extends AbstractController {
...
@RequestMapping(value = "/bookings/cancel/{id}", method = RequestMethod.POST)
public String deleteBooking(@PathVariable long id) {
bookingService.cancelBooking(id);
//use prefix 'redirect' to avoid duplicate submission
return "redirect:../../search";
}
...
}

[b]另外还有几个重要原因:[/b]


1、由于Spring的DefaultAnnotationHandlerMapping.java在做Mapping的时候,先做是否有类匹配,再找方法,把基本的mapping放在类上面,可以加速匹配效率;

2、后续可以通过更细腻的支持Ant path style的AntPathMatcher来规划URI Template资源,做ACL控制(请参考后面的心得体会)。


[b]三、JSON/XML等ajax的支持很cool,可以尝试 [/b]

JSON/XML/RSS等均可支持,当然有些denpendency,比如JSON的默认支持,需要jackson jar出现在lib中,POM的artifact如下:


<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.5.3</version>
</dependency>


这样,我们其实根本就不需要进行额外的JSON转换了,Spring MVC 3会根据请求的格式自行转换:

@ResponseBody
@RequestMapping(value = "/ajax", method = RequestMethod.POST)
public JsonDataWrapper<Hotel> ajax(WebRequest request, Hotel hotel, Model model)
throws Exception {
JsonDataWrapper<Hotel> jsonDataWrapper = this.getPaginatedGridData(request, hotel, hotelService);
return jsonDataWrapper;
}

[b]注 :[/b]我上面的JsonDataWrapper只是我自己做的一个简单的wrapper,目的是为jQuery Flexigrid plugin做数据准备的。还是贴出来吧:

/**
* A wrapper class for jQuery Flexigrid plugin component.
*
* The format must be like this:
* <code>
* {"total":2,"page":1,"rows":[
* {"personTitle":"Mr","partyName":"Test8325","personDateOfBirth":"1970-07-12"},
* {"personTitle":"Ms","partyName":"Ms Susan Jones","personDateOfBirth":"1955-11-27"}
* ]}
* </code>
*
* @author bright_zheng
*
* @param <T>: the generic type of the specific domain
*/
public class JsonDataWrapper<T> implements Serializable {
private static final long serialVersionUID = -538629307783721872L;

public JsonDataWrapper(int total, int page, List<T> rows){
this.total = total;
this.page = page;
this.rows = rows;
}
private int total;
private int page;
private List<T> rows;

public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public List<T> getRows() {
return rows;
}
public void setRows(List<T> rows) {
this.rows = rows;
}
}


[b]四、Controller的单元测试变得很可为且简单 [/b]

以前的项目从来不做controller层的测试,用了Spring MVC 3这一点就不再难为情,做吧:


public class HotelsControllerTest {

private static HandlerMapping handlerMapping;
private static HandlerAdapter handlerAdapter;

private static MockServletContext msc;

@BeforeClass
public static void setUp() {
String[] configs = {
"file:src/main/resources/context-*.xml",
"file:src/main/webapp/WEB-INF/webapp-servlet.xml" };
XmlWebApplicationContext context = new XmlWebApplicationContext();
context.setConfigLocations(configs);
msc = new MockServletContext();
context.setServletContext(msc);
context.refresh();
msc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
ApplicationContextManager manager = new ApplicationContextManager();
manager.setApplicationContext(context);
handlerMapping = (HandlerMapping) ApplicationContextManager.getContext().getBean(DefaultAnnotationHandlerMapping.class);
handlerAdapter = (HandlerAdapter) ApplicationContextManager.getContext().getBean(ApplicationContextManager.getContext().getBeanNamesForType(AnnotationMethodHandlerAdapter.class)[0]);
}

@Test
public void list() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();

request.setRequestURI("/hotels");
request.addParameter("booking.id", "1002");
request.addParameter("hotel.name", "");
request.setMethod("POST");

//HandlerMapping
HandlerExecutionChain chain = handlerMapping.getHandler(request);
Assert.assertEquals(true, chain.getHandler() instanceof HotelsController);

//HandlerAdapter
final ModelAndView mav = handlerAdapter.handle(request, response, chain.getHandler());

//Assert logic
Assert.assertEquals("hotels/search", mav.getViewName());
}
}


[b] 需要说明一下[/b] :由于当前最想版本的Spring(Test) 3.0.5还不支持@ContextConfiguration的注解式context file注入,所以还需要写个setUp处理下,否则类似于Tiles的加载过程会有错误,因为没有ServletContext。3.1的版本应该有更好的解决方案,参见:[url]https://jira.springsource.org/browse/SPR-5243[/url] 。

Service等其他layer就没有这类问题,测试的写法将变得更加优雅,贴个出来:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/resources/context-*.xml" })
public class DefaultServiceImplTest {
/** @Autowired works if we put @ContextConfiguration at junit type */
@Autowired
@Qualifier("hotelService")
private HotelService<Hotel> hotelService;

@Test
public void insert() {
Hotel hotel = new Hotel();
hotel.setAddress("addr");
hotel.setCity("Singapore");
hotel.setCountry("Singapore");
hotel.setName("Great Hotel");
hotel.setPrice(new BigDecimal(200));
hotel.setState("Harbarfront");
hotel.setZip("010024");
hotelService.insert(hotel);
}
}

[b]五、ACL可以写一个HandlerInterceptorAdapter,在“事前”拦截。[/b]

由于Spring是Restful friendly的framework,做ACL就不要用过滤器了,用interceptor吧:

public class AclInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = Logger.getLogger(AclInterceptor.class);

/** default servlet prefix */
private static final String DEFAULT_SERVLET_PREFIX = "/servlet";

/** will be injected from context configuration file */
private AclService service;

@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String currentUri = request.getRequestURI();
boolean isAccessible = true;
//only intercept for annotated business controllers
Controller c = AnnotationUtils.findAnnotation(handler.getClass(), Controller.class);
if(c!=null){
String[] grantedResource = getGrantedResource(request);
if(grantedResource==null || grantedResource.length==0){
throw new AccessDeniedException("No resource granted");
}
isAccessible = service.isAccessible(grantedResource, currentUri);
if(logger.isDebugEnabled()){
logger.debug("ACL interceptor excueted. Accessible for Uri[" + currentUri +"] = " + isAccessible);
}
//if isAccessible==true, throw custom AccessDeniedException
if(!isAccessible) throw new AccessDeniedException();
}
return isAccessible;
}

/**
* transfer the original Uri to resource Uri
* e.g.:
* original Uri: /servlet/hotels/ajax
* target Uri : /hotels/ajax
* @param originalUri
* @return
*/
protected String getUri(String originalUri){
return originalUri.substring(DEFAULT_SERVLET_PREFIX.length());
}

/**
* Get the granted resource from session
* @param request
* @return
*/
protected String[] getGrantedResource(HttpServletRequest request){
//get the resources from current session
//String[] uriResourcePattern = (String[]) request.getSession().getAttribute("uriResourcePattern");
//TODO: mock data here
String[] uriResourcePattern = new String[]{"/**"};

return uriResourcePattern;
}

public void setService(AclService service) {
this.service = service;
}
}

注 :上面还有TODO部分,很简单,登录后把当然用户可访问的Ant path style的URI Resources放到session中,作为一个String[],这里拿来出匹配即可,建议匹配就用org.springframework.util.AntPathMatcher。



[b]六、对于Ajax Form Validation,正在寻求更佳的解禁方案。[/b]

Spring 3已经支持@Valid的注解式validation,又一个JSR,但通过我研究代码发现,默认的org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.java实现还是非常封闭,并不能很好的实现binding后执行业务代码前处理,所以目前的实现办法还不算优雅。

已经实现,大致的思路是:

1)发出ajax form post请求(用jQuery);

2)正常mapping到controller的方法中处理;

3)做form validation;

4)不管怎样,forward到一个解析错误消息的JSP,叫ajaxFormValidatorResult.jsp;

5)再写一个jquery plugin,叫errorBinder.js,解析错误并自定binding为Tooltip,如果没错误就正常submit

贴个简单的demo效果,欢迎拍砖。

[b] ==Before [/b]

[img]http://dl.iteye.com/upload/attachment/357731/6bba3541-d9f9-3410-b6f6-b478411b766b.png[/img]


[b] ==After[/b]

[img]http://dl.iteye.com/upload/attachment/357733/d4c693d4-5534-342f-af87-19ea0a6b09aa.png[/img]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值