spring mvc 札记

spring mvc 笔记
最近练习了一下spring mvc,计划以后多用,所以做做笔记。

● 第一个头疼的问题
  模仿官方的例子搭建一个工程,居然死在不能访问首页上,折腾了两天才发现问题所在。
  在web.xml里配置:
 
<servlet-mapping>
     <servlet-name>appServlet</servlet-name>
     <url-pattern>/</url-pattern>
  </servlet-mapping>

  spring-mvc配置文件里:
 
<mvc:view-controller path="/" view-name="index"/>

  然后访问首页,一直打不开,访问其它页面完全没问题。再做一个测试,加上一行配置:
 
<mvc:view-controller path="/a" view-name="index"/>

  然后访问http://localhost:8080/a,很正常。但http://localhost:8080/就是不行。不管怎么google都找不到答案,郁闷了两天后突然发觉了问题:在web的根目录有一个index.html文件,删掉了就没事!

● 简化RequestMapping
  如何去掉每个方法头顶的RequestMapping(value="xxx")中的xxxx,因为一般都会与方法名相同,再写就多余了。像struts2的convention插件那样就挺好。Google了很久,别人也找不到答案

●事务问题
  在Controller中调用service层的方法访问数据库时,出现当前线程没有session的问题,很诡异,之前用struts2时很正常。在老外的论坛中找到一个解决方法:在DispatcherServlet的配置文件中添加<tx:annotation-driven/>。问题是解决了,但这个方法很奇怪,因为已经在applicationContext.xml中配置了tx。还不明白是怎么回事。
  后来找到解决方案。造成事务失效的原因是,mvc的配置文件先被处理,Service类此时还没有进行事务增强处理,所以不具备事务的能力,而轮到applicationContext.xml被处理时,已经被处理过的组件将被忽略。解决方法是,mvc的配置文件中不进行Service类的扫描:
<context:component-scan base-pagckage="...">
  <context:exclude-filter type="annotation" expression="ogr.springframework.stereotype.Service"/>
</context:component-scan>


后来发现,其实更好的方法是,既然同时用了spring和spring MVC,没必要搞出一个applicationContext.xml和一个spring-mvc.xml,应该统一,不要重复配置<context:component-scan/>

●中文乱码问题
  直接用@ResponseBody输出字符串会有中文乱码出现,原因可Google到,其中一个解决方法是:在mvc的配置文件中添加以下配置(注意,必须添加在<mvc:annotation-driven />之前。并且,只添加StringHttpMessageConverter是不行的,那样输出json时会报错,所以只好也添加了MappingJacksonHttpMessageConverter,如果用到其它的Converter估计同样要添加。这不是一个好的解决办法。)
	<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
		<property name="messageConverters">
	         <list>
	             <bean class = "org.springframework.http.converter.StringHttpMessageConverter">
	                <property name = "supportedMediaTypes">
	                	<list>
	                		<value>text/plain;charset=UTF-8</value>
	                     </list>
	                </property>
	             </bean>
	             <bean class = "org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
	                <property name = "supportedMediaTypes">
	                	<list>
	                		<value>application/json;charset=UTF-8</value>
	                     </list>
	                </property>
	             </bean>
	         </list>
		</property>
	</bean>

  目前比较好的方法可能就是修改源码并重新编译了,将StringHttpMessageConverter类中的Charset.forName("ISO-8859-1");修改为Charset.forName("utf-8");这样最保险,不会引起其它错误,只是编译源码有点麻烦。

● 配置异常处理
 
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="defaultErrorView" value="error/unexpected"/>
    <property name="exceptionMappings">
      <props>
        <prop key="org.sninwo.test.AppException">error/expected</prop>
        <prop key="java.lang.Throwable">error/unexpected</prop></props>
    </property>
  </bean>


● 页面传入集合参数
//: test/params?xxIds=2,3,4 或 test/param2?xxIds=2&xxIds=3&xxIds=4
@RequestMapping("/test/params")
public @ResponseBody String params(int[] xxIds){
	for (int id: xxIds){
		//....
	}
	return "xxx";
}


● 页面传入bean
//:test/bean?s1=abc&s2=ddd&i=45&bool=1
@RequestMapping("/test/bean")
public @ResponseBody String bean(Bean bean){
	return bean != null ? bean.toString() : "null";
}


● 页面传入List和Map
/* 
List和Map不能直接传,必须绑定到某个对象上,而且,在url中指定参数时,格式有点怪异:List必须指定下标,格式为listName[i].prop=xx,Map的键也必须用中括号[],不能直接用点,格式为mapName['keyName']=value 
*/


● 向页面传出json
记得添加相应的jackson依赖包,此外无需额外配置。
//:test/json
@RequestMapping("/test/json")
public @ResponseBody Bean json(){
	Bean bean = new Bean();
	bean.setS1("abc");
	bean.setS2("ddd");
	return bean;
}


●hibernate代理对象的json转换
    用session.get(id)来取出非代理对象,而不要用session.load(id)来取出代理对象。

●让Controller接受日期参数
在Controller类中添加如下方法:
@InitBinder
protected void initBinder(WebDataBinder binder){
  binder.registerCustomerEditor(Date.class, new PropertyEdiotrSupport(){
    public void setAsText(String value){
      setValue(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(value));
    }
    public void getAsText(){
      return (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date)getValue()));
    }
  });
}