`

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

阅读更多

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

 

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

 

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

    除了获得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));
	}
...
}


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

    大家都知道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";
	}
...
}
 

 

    另外还有几个重要原因:

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

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


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

    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;
	}

   :我上面的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;
	}
}


四、Controller的单元测试变得很可为且简单

    以前的项目从来不做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());
	}
}

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

    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);
	}
}
 

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

    由于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。

 

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

    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效果,欢迎拍砖。

  ==Before


  ==After


 

 

补充说明:后面陆续补充的心得,就不在这里出现了,而是以跟帖的形式存在,便于讨论吧

 

  • 大小: 4 KB
  • 大小: 8.2 KB
分享到:
评论
60 楼 muqingren 2011-03-17  
牛逼,感谢楼主
59 楼 °Clour 2011-03-16  
不错、收藏。嘎嘎、
58 楼 itstarting 2011-02-27  
junnyfan 写道
请问楼主有没有用过列表对象的绑定?
假如:ObjectA a,有一属性是Set<ObjectB> b,而ObjectB又有自己的属性b1,b2这种情况下controller取不到b.也就是绑定不成功,知道是什么原因吗?
我的jsp可能是这样的。
b1:<input type="text" name="b.b1" />
b2:<input type="text" name="b.b2" />
b1:<input type="text" name="b.b1" />
b2:<input type="text" name="b.b2" />
理论上如果b.b1和b.b1输入框name相同,传过去应是数组,这种情况Spring不能正常绑定。

而如果我把ObjectA类的结构改成含有属性ObjectB b时。客户端jsp为:
b1:<input type="text" name="b.b1" />
b2:<input type="text" name="b.b2" />
时,controller就可以正常获取.

各路大侠都帮忙看看,谢啦!

看不懂你的问题

你不用spring 的tag怎么能绑定?

另外,能用EL的表达式应该就能完成绑定,比如:
<form:input path="a.b.b1"/>
57 楼 junnyfan 2011-02-27  
请问楼主有没有用过列表对象的绑定?
假如:ObjectA a,有一属性是Set<ObjectB> b,而ObjectB又有自己的属性b1,b2这种情况下controller取不到b.也就是绑定不成功,知道是什么原因吗?
我的jsp可能是这样的。
b1:<input type="text" name="b.b1" />
b2:<input type="text" name="b.b2" />
b1:<input type="text" name="b.b1" />
b2:<input type="text" name="b.b2" />
理论上如果b.b1和b.b1输入框name相同,传过去应是数组,这种情况Spring不能正常绑定。

而如果我把ObjectA类的结构改成含有属性ObjectB b时。客户端jsp为:
b1:<input type="text" name="b.b1" />
b2:<input type="text" name="b.b2" />
时,controller就可以正常获取.

各路大侠都帮忙看看,谢啦!
56 楼 matychen 2011-02-12  
itstarting 写道
matychen 写道
问楼主一个问题啊,
......



这位兄台的....

对于第一种,不怎么感冒,
对于第二种,可以采用注入MessageSourceAware,不必实现MessageSourceAware接口,要相对较好点。
@Autowired
	private MessageSource messageSource;

....
public void testParam(HttpServletRequest request,Locale locale,
			HttpServletResponse response)
			throws IOException {
		if (StringUtils.isBlank(securityCode)) {
			this.setResult(messageSource.getMessage("common.securityCode.IsBlank", null, locale), response);
			return;
		}

还是采用第二种方式吧。

不知道后面的兄弟有好点的办法没?
55 楼 itstarting 2011-02-01  
今天碰到一个问题,解决了,也分享下,是关于interceptor的问题。

最精简版的可以这样配置:
<mvc:interceptors>   
  <bean class="xxx.AInterceptor" /> 
</mvc:interceptors>

表示全部都要经AInterceptor


还可以这样配置:
<mvc:interceptors>   
  <mvc:interceptor>   
    <mvc:mapping path="/pathA/*" />   
    <bean class="xxx.AInterceptor" />   
  </mvc:interceptor>   
  <mvc:interceptor>   
    <mvc:mapping path="/pathB/*" />   
    <bean class="xxx.BInterceptor" />   
  </mvc:interceptor>   
</mvc:interceptors>

表示按路径匹配Interceptor

可是问题来了,我的AclInterceptor不能用于login的controller,但没法excluding掉
我尝试这样:
<mvc:interceptors>   
  <mvc:interceptor>   
    <mvc:mapping path="/login" />   
    <bean class="xxx.IgnoreInterceptor" />   
  </mvc:interceptor>   
  <mvc:interceptor>   
    <mvc:mapping path="/*" />   
    <bean class="xxx.BInterceptor" />   
  </mvc:interceptor>   
</mvc:interceptors>

即:我开发了一个啥事不干的interceptor,适配/login的路径
可还是不行,为啥,因为/login的path还能匹配/*,所以两个interceptor都要跑,这并非我所想

于是重新调整了AclInterceptor,加了一个属性叫ignoreControllers。
<mvc:interceptors>
    	<bean class="xxx.admin.acl.impl.AclInterceptor" >
    		<!-- ignore list of controller simple class name -->
	    	<property name="ignoreControllers">
				<list>
					<value>LoginController</value>
				</list>
			</property>
    	</bean>
	</mvc:interceptors>


Spring MVC其实可以直接提供ignore path的我想,但既然他干不了,就只能自己干了
54 楼 itstarting 2011-01-31  
matychen 写道
问楼主一个问题啊,
spring3 mvc的方法级别的错误信息国际化,每次必须在方法参数里面传入 Locale locale
public String interpolate(String messageTemplate, Context context, Locale locale) {
    try {
        return messageSource.getMessage(messageTemplate, new Object[]{}, locale);
    } catch (NoSuchMessageException e) {
        return super.interpolate(messageTemplate, context, locale);
    }
}

有没有像struts2这样的
if (StringUtils.isBlank(this.securityCode)) {
			this.setResult(getText("securityCodeIsNull"));
			return;
		}

不用关心locale的方法



这位兄台的问题我还真碰到过,因为奇怪于Spring对于validation的BindingResult和service layer如何获取i18n消息的设计,经过初步的实践,我的心得如下:

1、Errors类型的实例,可自动生成于controller层,一般的validation,只需要reject和rejectValue即可,用法举例:
errors.reject("form.global.error", "Errors found by form validation");
errors.rejectValue("hotel.name", "hotel.query.keyword.required");

前者表示通用性消息,后者表示FormField相关的消息,可惜的是,在前端就拿不到是不是FormField的依据了(据我说知吧)
put进去的是key就行了,自己会搞,至于采用了那种i18n的结果,有很多的策略,参考reference即可,我这里就不赘述了

2、要想在service layer拿到i18n,我碰过鼻子,一般意义上认为i18n是web层的东西,所以想当然的放到了webmvc-config.xml之类的context file里,结果呢一直在service layer拿不到。这一点必须要理解Spring context的层次性,只要放到root context即可——对于web app就是配置在web.xml的contextConfigLocation里:
<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath:context-*.xml
		</param-value>
	</context-param>

具体配置参考:
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
		<property name="basenames">
			<list>
				<value>/WEB-INF/messages/messages</value>
				<value>/WEB-INF/messages/validation</value>
			</list>	
		</property>
		<!-- 
			Default is "-1", indicating to cache forever.
			A value of "0" will check the last-modified timestamp of the file on every message access. 
			Do not set to "0" in a production environment! 
		-->
		<property name="cacheSeconds" value="0" />
	</bean>

然后在需要用到消息的类中实现MessageSourceAware接口,即可通过类似这样的方法获得:
public String format(String key, Object[] value){
  return this.getMessageSource().getMessage(key, value, key, Locale.getDefault());
}

我这里采用的是Locale.getDefault(),没深究,Spring应该还有更好的解决方案,获取当前thread的Locale比如——这点留待后面的兄弟解答
53 楼 matychen 2011-01-30  
问楼主一个问题啊,
spring3 mvc的方法级别的错误信息国际化,每次必须在方法参数里面传入 Locale locale
public String interpolate(String messageTemplate, Context context, Locale locale) {
    try {
        return messageSource.getMessage(messageTemplate, new Object[]{}, locale);
    } catch (NoSuchMessageException e) {
        return super.interpolate(messageTemplate, context, locale);
    }
}

有没有像struts2这样的
if (StringUtils.isBlank(this.securityCode)) {
			this.setResult(getText("securityCodeIsNull"));
			return;
		}

不用关心locale的方法
52 楼 itstarting 2011-01-28  
gtssgtss 写道
spring的验证一直没搞明白,只好自己弄个验证。

目前我是使用ajax验证,谁敢绕过ajax验证,提交非法数据,直接给他跳到错误页面,他对咱不友好,咱也没必要对他友好,只是这样工作量大些。

很想学习下spring的验证,可以让写代码轻松很多么?谁能搞个实际点的demo学习下?

我专门写了个ajaxFormValidator的jquery插件,再写了另一个errorBinder插件,这样就能完成验证->UI元素绑定&Tooltip的效果了。

只是spring的错误消息设计有点缺陷,在于在前端无法区分FieldError和GlobalError,所以只能另外想了其他的办法,通过设定一些convention(比如输入元素的id命名规则)来做
51 楼 gtssgtss 2011-01-28  
spring的验证一直没搞明白,只好自己弄个验证。

目前我是使用ajax验证,谁敢绕过ajax验证,提交非法数据,直接给他跳到错误页面,他对咱不友好,咱也没必要对他友好,只是这样工作量大些。

很想学习下spring的验证,可以让写代码轻松很多么?谁能搞个实际点的demo学习下?
50 楼 daquan198163 2011-01-28  
其实我觉得原来的SpringMVC在validation这一块已经做得很不错了:
一般的输入合法性验证靠集成Commons-Validator框架,规则写在配置文件里,而且可以实现服务端客户端双重验证,灵活、集中、便于维护;
对于复杂的纯服务端验证,比如校验email是否已经存在,可以用SpringMVC自己的Validator框架,验证逻辑封装在Validator然后注入SimpleFormController,验证逻辑不会和业务逻辑混在一起,也很优雅。
而且上述两种验证可以同时使用,而且可以做到验证失败后,表单回填和错误信息的显示,集成的天衣无缝。
49 楼 mib168 2011-01-28  
不知spring mvc今后是否会取代struts 呵呵
48 楼 itstarting 2011-01-28  
kongruxi 写道


项目中也用上JSR-303,做验证挺方便的,给个简单例子吧

<!-- 开启MVC注解 -->
<mvc:annotation-driven />


Bean代码:
public class User {

	private int id;

	@NotBlank(message = "用户名不能为空")
	@Size(min = 4, max = 16, message = "用户名长度必须为{min} ~ {max} 位")
	private String username;

	@NotBlank(message = "密码不能为空")
	@Size(min = 6, max = 20, message = "密码度必须为{min} ~ {max} 位")
	private String password;

	// 省略setter,getter
}



Controller代码:
@Controller
@RequestMapping("/user/*")
public class UserControler {

	@Resource
	private UserManager userManager;

	@RequestMapping(value = "save.do", method = RequestMethod.POST)
	public String save(@Valid User user, BindingResult result) {
		if (result.hasErrors()) {
			// 验证到错误时
			return "user/input";
		}
		userManager.save(user);
		return "user/success";
	}
}


当然,页面上也要对应有,不过也可以用上form标签来绑定
<input type="text" name="username" />
<input type="password" name="password" />





我是担心验证的地方太多,逻辑看起来不方便(总是感觉不太完整),毕竟除了简单的表单验证之外,还有更为复杂的业务逻辑校验,所以我们为了统一,全部废掉JSR-303的做法,反而用了最原始的做法,即编写一套简单的验证工具,基于此工具编码验证。
47 楼 kongruxi 2011-01-27  
itstarting 写道
fq_jeid 写道
请问搂主..
这个spring的验证说是要jsr-303规范..
就是不在spring的范畴里面..
我怎么打不出@valid注解.myeclipse没有提示..
我换了1.6的jdk还是编译错误.



这个需要更多的依赖,一个是api另一个是实现,目前比较推荐的实现来自于hibernate。
比如:
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.0.0.GA</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>4.0.2.GA</version>
		</dependency>


项目中也用上JSR-303,做验证挺方便的,给个简单例子吧

<!-- 开启MVC注解 -->
<mvc:annotation-driven />


Bean代码:
public class User {

	private int id;

	@NotBlank(message = "用户名不能为空")
	@Size(min = 4, max = 16, message = "用户名长度必须为{min} ~ {max} 位")
	private String username;

	@NotBlank(message = "密码不能为空")
	@Size(min = 6, max = 20, message = "密码度必须为{min} ~ {max} 位")
	private String password;

	// 省略setter,getter
}



Controller代码:
@Controller
@RequestMapping("/user/*")
public class UserControler {

	@Resource
	private UserManager userManager;

	@RequestMapping(value = "save.do", method = RequestMethod.POST)
	public String save(@Valid User user, BindingResult result) {
		if (result.hasErrors()) {
			// 验证到错误时
			return "user/input";
		}
		userManager.save(user);
		return "user/success";
	}
}


当然,页面上也要对应有,不过也可以用上form标签来绑定
<input type="text" name="username" />
<input type="password" name="password" />


46 楼 itstarting 2011-01-26  
fq_jeid 写道
请问搂主..
这个spring的验证说是要jsr-303规范..
就是不在spring的范畴里面..
我怎么打不出@valid注解.myeclipse没有提示..
我换了1.6的jdk还是编译错误.



这个需要更多的依赖,一个是api另一个是实现,目前比较推荐的实现来自于hibernate。
比如:
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.0.0.GA</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>4.0.2.GA</version>
		</dependency>
45 楼 fq_jeid 2011-01-26  
请问搂主..
这个spring的验证说是要jsr-303规范..
就是不在spring的范畴里面..
我怎么打不出@valid注解.myeclipse没有提示..
我换了1.6的jdk还是编译错误.
44 楼 tuhaitao 2011-01-19  
体验很不错,但是唯有测试有点牵强:

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]);        
    }  
...
}


这段代码没必要,分析原因:
1.WebApplicationContext本身继承自ApplicationContext, 所以,使用@ContextConfiguration(locations={"spring-*.xml"}完全可以用ApplicationContext加载所有类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-dao.xml", "classpath:spring-service.xml", "classpath:spring-web.xml"}, inheritLocations = true)
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class UserControllerTest {
	
    private static HandlerMapping handlerMapping;  
    private static HandlerAdapter handlerAdapter;
	
    @Before
    public void setUp() {
        handlerMapping  =   ApplicationContextHook.applicationContext.getBean(DefaultAnnotationHandlerMapping.class);
        handlerAdapter  = ApplicationContextHook.applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
	}
...
}


hook spring的application即可
43 楼 unsid 2010-12-25  
最近在考虑一个问题,就是话说springMVC支持rest风格的URI,这样非常方便
你是怎么规划URI的呢?我觉得web应用的页面无非三种列表页面,详细信息页面和录入信息窗口页面,那么如果是以书为例,rest风格API怎么规划合理:
1、图书列表 /project/booktype/3/booklist
2、图书详细信息/project/booktype/3/booklist/4
3、录入页面怎么描述?/project/newbook?
   如果录入页面分很多栏信息?/project/newbook/1?
   如果这些新西兰嵌入到一个父frame里怎么表示frame?/project/newbookframe?
4、试想如果一个网站能在“最新图书列表”,“图书分类检索”里都能查到同一本书,那么一本图书对应两个URI?

/project/newbook或者project/newbookframe这种风格的URI不觉得很不REST么?
42 楼 itstarting 2010-12-20  
unsid 写道
itstarting 写道
unsid 写道

参数里增加了@ModelAttribute("customerCommand") CustomerCommand customerCommand 就正常显示了,我很纳闷我hello这个方法根本用不到CustomerCommand customerCommand难道必须要在这声明?


其实你的hello方法的执行终点是渲染hello.jsp,此时hello.jsp会试图在request获取modelAttribute,但如果你按原来的写法,显然没有名字为customerCommand的modelAttribute,所以会有绑定异常,而你加上就可以了。

另外,你也可以直接new一个然后put到request里去,也是OK的,此时方法上可以不要这个参数(@ModelAttribute("customerCommand") CustomerCommand customerCommand)了:
model.addAttribute("customerCommand",new CustomerCommand ());



BTW:也许显式的写@ModelAttribute也是一种好习惯,但我一般懒得写,放在方法上即可,此时Spring能处理好,即使你没put到request去:
	@RequestMapping(value="/hello",method = RequestMethod.GET)
	public String hello(CustomerCommand customerCommand,Model model) {
		model.addAttribute("hello","helloworld!");
		return "hello";
	}


奥,你要怎么说我就理解modelAttribute的出处了,原来是从request里取CustomerCommand对象
但是每次打开一个带form的页面时候,都要从上一个controller里写一个model.addAttribute("customerCommand",new CustomerCommand ());么?这多不优雅啊,至少controller中加入了和这个controller关系不大的代码,你一般是这么写么?还是有什么更好的写法?

new一个我是不会干的,多烦啊

我认为有两种办法可以值得一试:
1、在方法上简单加上这个参数;
public String hello(CustomerCommand customerCommand,Model model) {
...
}

2、写一个专门的方法来初始化并就绪一个新的bean,这个方法需要加上@ModelAttribute

我一般用第一种。
第二种不是很好——所有方法都要执行,代码虽然“优雅”点但效率不高


试想一下,你要导航到一个form了,一般而言重要初始化一些东西,那么直接拿到这个从方法参数来的东西加工就是了
41 楼 unsid 2010-12-20  
itstarting 写道
unsid 写道

参数里增加了@ModelAttribute("customerCommand") CustomerCommand customerCommand 就正常显示了,我很纳闷我hello这个方法根本用不到CustomerCommand customerCommand难道必须要在这声明?


其实你的hello方法的执行终点是渲染hello.jsp,此时hello.jsp会试图在request获取modelAttribute,但如果你按原来的写法,显然没有名字为customerCommand的modelAttribute,所以会有绑定异常,而你加上就可以了。

另外,你也可以直接new一个然后put到request里去,也是OK的,此时方法上可以不要这个参数(@ModelAttribute("customerCommand") CustomerCommand customerCommand)了:
model.addAttribute("customerCommand",new CustomerCommand ());



BTW:也许显式的写@ModelAttribute也是一种好习惯,但我一般懒得写,放在方法上即可,此时Spring能处理好,即使你没put到request去:
	@RequestMapping(value="/hello",method = RequestMethod.GET)
	public String hello(CustomerCommand customerCommand,Model model) {
		model.addAttribute("hello","helloworld!");
		return "hello";
	}


奥,你要怎么说我就理解modelAttribute的出处了,原来是从request里取CustomerCommand对象
但是每次打开一个带form的页面时候,都要从上一个controller里写一个model.addAttribute("customerCommand",new CustomerCommand ());么?这多不优雅啊,至少controller中加入了和这个controller关系不大的代码,你一般是这么写么?还是有什么更好的写法?

相关推荐

Global site tag (gtag.js) - Google Analytics