`

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关系不大的代码,你一般是这么写么?还是有什么更好的写法?

相关推荐

    SpringMVC文档.zip_spring mvc

    5. **Spring MVC 3.x annotated controller的几点心得体会** - 作者可能分享了在实际开发中使用注解控制器的经验和技巧,包括错误处理、数据验证、模型绑定等方面。 6. **SpringMVC数据绑定** - 数据绑定是Spring...

    Manning.Spring.in.Action.4th.Edition.2014.11.epub

    7.1. Alternate Spring MVC configuration 7.1.1. Customizing DispatcherServlet configuration 7.1.2. Adding additional servlets and filters 7.1.3. Declaring DispatcherServlet in web.xml 7.2. Processing ...

    springframework.5.0.12.RELEASE

    Exporting a lazily initialized bean (which implements SelfNaming and is annotated with ManagedResource annotation) gives IllegalStateException [SPR-17592] #22124 MockHttpServletRequest changes Accept-...

    the.annotated.turing

    书中的内容主要围绕以下几个关键知识点展开: 1. **图灵机模型**:图灵机是计算理论的基石,它是一个抽象的计算设备,能够模拟任何算法的逻辑。书中详细介绍了图灵机的工作原理,包括状态、带子、读写头和规则,...

    自动驾驶Apollo源码注释.(annotated Apollo 1.0 source code).zip

    在学习Apollo 1.0源码时,可以关注以下几个关键点: - 数据流处理:了解如何从不同传感器获取原始数据,并将其转化为可供其他模块使用的格式。 - 算法实现:深入研究感知模块中的目标检测算法,如YOLO、Faster R-...

    Margaret A. Ellis, Bjarne Stroustrup-The Annotated C++ Reference Manual

    Margaret A. Ellis, Bjarne Stroustrup-The Annotated C++ Reference Manual

    Addison.Wesley.dot.NET.Framework.Standard.Library.Annotated.Reference.Volume.1.Mar.2004.FIXED [CHM]

    Edited by a Lead Program Manager on Microsoft's .NET Framework team, .NET Framework Standard Library Annotated Reference, Volume 1, is the definitive reference for the .NET Framework base class ...

    spring-mvc序列化json动态擦除属性[参考].pdf

    在Spring MVC中,当需要将Java对象转换为JSON格式发送给客户端时,有时会遇到返回对象包含大量冗余属性的情况,这可能导致不必要的网络流量消耗。为了解决这个问题,我们可以实现一个动态擦除属性的机制,使得在序列...

    Learning Spring Application Development

    Following this, you will explore how to implement the request handling layer using Spring annotated controllers. Other highlights include learning how to build the Java DAO implementation layer by ...

    Learning Spring Application Development(PACKT,2015)

    Following this, you will explore how to implement the request handling layer using Spring annotated controllers. Other highlights include learning how to build the Java DAO implementation layer by ...

    redis-3.0-annotated-unstable.zip

    redis-3.0-annotated-unstable.zipredis-3.0-annotated-unstable.zipredis-3.0-annotated-unstable.zipredis-3.0-annotated-unstable.zip

    Spring+SpringMVC+JPA+Hibernate搭建

    13. 配置控制器(Controller)以处理Web层的请求,并将视图解析器配置到Spring MVC配置文件中,以便Spring MVC框架知道如何渲染视图。 通过上述步骤,可以将Spring、SpringMVC、JPA和Hibernate集成到一起,构建一个...

    spring2.5 jar

    2. **注解驱动开发(Annotated Controllers)**:在Spring 2.5中,可以使用`@Controller`、`@RequestMapping`等注解来声明控制器类和处理HTTP请求的方法,大大简化了Web应用程序的开发。 3. **AOP增强**:Spring ...

    The Annotated C++ Reference Manual.part2

    The Annotated C++ Reference Manual.part2

    人脸数据库 Annotated Database

    在"Annotated Database"中,这些关键点已预先标注,可以用于训练和优化关键点检测算法,如Dlib的HOG(Histogram of Oriented Gradients)或基于深度学习的MTCNN(Multi-task Cascaded Convolutional Networks)。...

    The Annotated C++ Reference Manual.zip.001

    The Annotated C++ Reference Manual

    The Thinking in Java Annotated Solution Guide

    而《The Thinking in Java Annotated Solution Guide》则是对原书习题的详尽解答,帮助读者巩固所学知识并解决实际编程问题。 这个压缩包包含的是书中所有代码示例,对于学习Java编程非常有帮助。以下是这些代码...

    The Annotated C++ Reference Manual.7z.008

    The Annotated C++ Reference Manual (共11部分) 传说中的原版巨作,不差页

Global site tag (gtag.js) - Google Analytics