问题一:指定扫描包的位置
应该将所有控制器类都放在基本包下,并且指定该扫描包,避免Spring MVC扫描了无关的包。比如所有控制器类全部放在com.dodonew.controller包下面,扫描配置如下所示:
<!-- 指定包的扫描位置 -->
<context:component-scan base-package="com.dodonew.controller"/>
而不应该配置为com.dodonew,因为扫描了无关的包,会影响应用程序的效率。
@RequestMapping注解中有这样两个属性:consumes属性和produces属性,类型为String[],consumes属性指定处理请求的提交内容类型(Content-Type)。例如application/json、text/html等。produces属性指定返回的内容类型,返回的内容类型必须是request请求头(Accept)中所包含的类型,如下图所示:
先看下2标注的地方,request请求头中Accept接收的类型包括text/html,application/xhtml+xml,application/xml等,在1标注的地方,我们看到返回的Content-Type的值为text/html;charset=UTF-8,这也验证了返回的内容类型必须是request请求头(Accept)中所包含的类型。
要理解Content-Type,我们得先了解下HTTP的基础知识。HTTP通信过程包括从客户端发往服务端的请求以及从服务器端返回客户端的响应。用于HTTP协议交互的信息被称为HTTP报文,请求端(客户端)的HTTP报文叫做请求报文,响应端(服务器端)的叫做响应报文,HTTP报文本身是由多行(用CR+LF作换行符)数据构成的字符串文本。HTTP报文大致可分为报文首部和报文主体两块,通常,并不一定要有报文主体,如下图所示:
annotation 1-2.png
请求报文和响应报文的结构如下图所示:
annotation 1-3.png
从图中可以看出,通用首部字段、实体首部字段在请求报文和响应报文中都存在的,而请求首部字段、响应首部字段分别存在于请求报文和响应报文中的。一般有4种首部,分别是:通用首部、请求首部、响应首部和实体首部。而这个Content-Type字段就是存在于实体首部字段的,Content-Type字段说明了实体主体内对象的媒体类型,和Accept字段一样,字段值用type/subtype形式赋值,一般是指网页中存在的Content-Type,如果没有指定Content-Type,默认为text/html。下面是几个常见的Content-Type:
- text/html
- text/plain
- text/css
- text/javascript
- application/x-www-form-urlencoded
- multipart/form-data
- application/json
- application/xml
其中后面四个是post的发包方式,也就是说在发送post请求的时候指定的Content-Type就是这些值。下面通过一幅图来更加形象的理解下Content-Type,如下图所示:
annotation 1-4.png
content-type的主要作用就是约定客户端与服务器端主体内对象的类型,也就是传输数据指定的类型。
问题三:解决中文乱码的问题
为了解决中文乱码的问题,我们在Spring MVC中经常会做如下配置:
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
但是这个配置只对post请求是有效的,对get请求是没有效果的,也就是说发送post请求中包含有中文的话,该配置起作用,不会出现中文的问题,但是发送get请求中包含中文的话,该配置就不起作用,一样会出现中文乱码的情况。为了解决get请求中文乱码的问题,需要对get请求参数进行urlEncode编码,一般情况下get请求尽量不要带中文参数,如果使用建议使用两次urlEncode编码。
问题四:深入理解WEB-INF
WEB-INF里面存放的东西只对服务器端开放,对客户端是不可见的。所以在使用的时候就需要注意以下几点:
- 页面资源文件只能放在webapp目录下(也就是web应用的根目录),如css、js、image等,不能放在WEB-INF下,因为WEB-INF是对客户端隐藏的,所以放在WEB-INF下会造成页面的布局等文件引用不到的情况。比如在引入jquery和json2文件时,不能放在WEB-INF里面,否则会出现找不到引用文件的问题。
- 页面文件一般放在WEB-INF目录下面,这样可以限制访问,提高安全性,如jsp、html文件,放在WEB-INF目录下就可以避免客户端直接在地址栏上输入路径进行访问了。基于不同的功能,把jsp放置在WEB-INF下的不同的目录中。
- 只能用转向方式来访问WEB-INF目录下的jsp,不能采用重定向的方式请求该目录里面的任何资源。比如使用spring mvc中的dispatcherServlet进行转发。
- 在WEB-INF下面存放的jsp文件,访问css、js等资源文件,忽略WEB-INF目录即可。也就是说在WEB-INF里面的文件访问不在WEB-INF里面的文件,忽略WEB-INF目录即可。但是WEB-INF外面的文件访问WEB-INF里面的文件,必须要通过转向的方式才能实现。
- 比如用a标签,location:ref标签,来访问WEB-INF里面的东西是访问不到的,因为这两个标签相当于是客户端发送的请求链接,而WEB-INF对客户端是隐藏的。
问题五:域对象为什么要序列化?
先看下什么是序列化?对象序列化机制是Java语言内建的一种对象持久化方式,可以很容易的在JVM中的活动对象和字节数组流之间进行转换。除了可以很简单的实现持久化之外,序列化机制的另外一个重要用途是在远程方法调用中,用来对开发人员屏蔽底层实现细节。对于一个存在Java虚拟机中的对象来说,其内部的状态只保持在内存中。JVM停止之后,这些状态就丢失了。在很多情况下,对象的内部状态是需要被持久化下来的,提到持久化,一种方式就是存储到数据库,一种就是进行序列化。所以对域对象进行序列化后,可以保存对象的内部状态,更重要的是让远程调用整个过程透明化。
注意:在序列化对象的时候,我们需要声明一个全局唯一标识符serialVersionUID,为什么要声明这个呢?因为把一个Java对象序列化之后,所得到的字节数组一般会保存在磁盘或数据库之中。在保存完成之后,有可能原来的Java类有了更新,比如添加了额外的域。这个时候从兼容性的角度出发,要求仍然能够读取旧版本的序列化数据。在读取的过程中,当ObjectInputStream发现一个对象的定义的时候,会尝试在当前JVM中查找其Java类定义。这个查找过程不能仅根据Java类的全名来判断,因为当前JVM中可能存在名称相同,但是含义完全不同的Java 类。这个对应关系是通过一个全局惟一标识符serialVersionUID来实现的。通过在实现了Serializable接口的类中定义该域,就声明了该Java类的一个惟一的序列化版本号。JVM会比对从字节数组中得出的类的版本号,与JVM中查找到的类的版本号是否一致,来决定两个类是否是兼容的。对于开发人员来说,需要记得的就是在实现了Serializable接口的类中定义这样的一个域,并在版本更新过程中保持该值不变。当然,如果不希望维持这种向后兼容性,换一个版本号即可。该域的值一般是综合Java类的各个特性而计算出来的一个哈希值,可以通过Java提供的serialver命令来生成。在Eclipse中,如果Java类实现了Serializable接口,Eclipse会提示并帮你生成这个serialVersionUID。在IntelliJ IDEA开发工具中,需要进行下设置,打开偏好设置,如下图所示:
annotation 1-5.png
这样就设置好了,在使用的时候把鼠标放在类名上,比如定义了一个User类,就放在User类上面,在mac上,按住option+enter键就会出现对应的提示了。在windows上,按住alter+enter,效果如下图所示:
annotation 1-6.png
问题六:IntelliJ IDEA提供的热更新功能
打开tomcat设置,如下图所示:
annotation 1-7.png
在红色标注的地方,都选择了Update classes and resouces,选择了这两个配置的作用就是当你更改了一个类的时候,不需要重新启动tomcat。但是这两个配置只在Debug模式起作用,在Run模式下是不起作用的,这点一定要注意。
问题七:Spring MVC整合fastjson组件
HttpMessageConvert是Spring3.0之后新增的一个重要接口,它负责将请求信息转换为一个对象(类型为T),并将对象(类型T)绑定到请求方法的参数中或输出为响应信息。在Spring中的dispatcherServlet默认已经装配了RequestMappingHandlerAdapter作为HandleAdapter组件的实现类,也就是说RequestMappingHandlerAdapter默认已经使用了HttpMessageConvert,会将请求信息转换为对象,或将对象转换为响应信息。Spring为HttpMessageConvert<T>提供了多个实现类,如下:
- StringHttpMessageConverter
- FormHttpMessageConverter
- XmlAwareFormHttpMessageConverter
- ResourceHttpMessageConverter
- BufferedImageHttpMessageConverter
- MappingJackson2HttpMessageConverter等等。
注意:如果在Spring Web容器中显式定义了一个RequestMappingHandlerAdapter,则Spring MVC的RequestMappingHandlerAdapter默认装配的HttpMessageConverter将不再起作用。
在项目开发中使用json数据越来越成为一种趋势了,而Spring默认使用Jackson处理json数据。如果要使用Jackson处理json数据的话,就需要引入依赖的Jackson包,如果没有依赖Jackson包,当客户端发送application/json格式的数据时,服务端就会报application/json not supported contentType错误。使用默认的Jackson处理json数据,只需要引入依赖的jar包即可,不需要做额外的配置,否则的话,就需要添加额外的配置,比如使用业界比较流行的fastjson组件,添加的额外配置如下:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<!-- 配置fastjson中实现HttpMessageConvert接口的转换器 -->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!-- 这里顺序不能反,一定先写text/html,不然IE下会出现下载提示 -->
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
<property name="features">
<array>
<!-- 是否输出值为null的字段,默认为false -->
<value>WriteMapNullValue</value>
<value>WriteNullStringAsEmpty</value>
</array>
</property>
<property name="charset">
<value>UTF-8</value>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
- QuoteFieldNames :输出key时是否使用双引号,默认为true
- WriteMapNullValue :否输出值为null的字段,默认为false
- WriteNullListAsEmpty :List字段若为null,输出[],而非null
- WriteNullNumberAsZero :数值字段若为null,输出0,而非null
- WriteNullStringAsEmpty :字符类型字段若为null,输出”“,而非null
- WriteNullBooleanAsFalse :Boolean字段若为null,输出false,而非null
通过这样的配置,就可以使用Fastjson对json数据进行处理了,不再使用Jackson组件。
问题八:Spring MVC标签库有哪些优势?
Spring从2.0版本开始,提供了一组功能强大的标签用来在JSP和Spring Web MVC中处理其他元素,相比其他的标签库,Spring的标签库集成在Spring Web MVC中,因此这里的标签库可以访问控制器(Controller)处理命令对象和绑定数据,这样会使JSP页面开发更加容易。要使用Spring MVC的标签库,需要在JSP页面的开头处声明一下taglib指令,如下所示:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
问题九:Spring MVC国际化要注意的问题
Spring MVC的国际化实现方式有3种,分别为AcceptHeaderLocaleResolver国际化,SessionLocaleResolver国际化,CookieLocaleResolver国际化,具体的实现可以搜下文章看下,这里主要讲下自己遇到的一些问题。
先看下基于AcceptHeaderLocaleResolver国际化,它是默认的,也是最容易使用的语言区域解析器,使用它Spring MVC会读取浏览器的accept-language标题,来确定使用哪个语言区域。AcceptHeaderLocalResolver可以不用显式配置,也可以显式配置,配置如下所示:
<!-- 国际化 -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>message</value>
</list>
</property>
</bean>
<!-- 国际化操作拦截器如果采用基于(Session/Cookie)则必须配置 -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
</mvc:interceptors>
<!--AcceptHeaderLocalResolver配置,因为AcceptHeaderLocalResolver是默认语言区域解析器,不配置也可以 -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"/>
<!-- SessionLocaleResolver配置 -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
<!-- CookieLocaleResolver配置 -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>
特别要注意的是id="messageSource"和id="localeResolver",一定要是这个值,不能做任何改动,否则就会报错说找不到对应的国际化属性文件,这个稍后会做解释的。另外,需要注意的是美式英语和英语两个是不同的,美式英语对应的属性文件名后缀为en_US,英语对应的属性文件名为en,所以在对属性文件命名时要特别注意。最后,在更改或添加浏览器支持的语言时,也要注意选择和属性文件名相对应的语言,如下图所示:
annotation 1-8.png
从图中也可以看到美式英语和英语是不一样的,所以在进行国际化适配的时候,这点是需要注意的。
SessionLocaleResolver需要对其进行显式配置,会从HttpSession作用域中获取用户设置的语言区域,来确定使用哪个语言区域。CookieLocaleResolver也需要对其进行显式配置,会从Cookie中获取用户设置的语言区域,来确定使用哪个语言区域。它们的配置如上所示,也都需要注意id="localeResolver"的值是不能做更改的,下面来解释下为什么不能做更改。
This constructs a bean with the name sessionLocaleResolver however the DispatcherServlet looks for a bean with the name localeResolver. If this isn‘t detected it will use the default, which is a AcceptHeaderLocaleResovler.
也就是说dispatcherServlet会根据localeResolver名称找到对应的bean,如果名称发生了改变,就找不到对应的bean,所以这个时候基于SessionLocaleResolver的国际化就会失败,不起作用。这个时候,系统就会改用默认的AcceptHeaderLocaleResolver来实现国际化。同样id="messageSource"也不能做更改,否则就会出现找不到对应属性文件的问题。