問題一:指定掃描包的位置
應該將所有控制器類都放在基本包下,並且指定該掃描包,避免Spring MVC掃描了無關的包。比如所有控制器類全部放在com.dodonew.controller包下面,掃描配置如下所示:
<!-- 指定包的掃描位置 -->
<context:component-scan base-package="com.dodonew.controller"/>
而不應該配置為com.dodonew,因為掃描了無關的包,會影響應用程序的效率。
先看下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報文大致可分為報文首部和報文主體兩塊,通常,並不一定要有報文主體,如下圖所示:

請求報文和響應報文的結構如下圖所示:

從圖中可以看出,通用首部字段、實體首部字段在請求報文和響應報文中都存在的,而請求首部字段、響應首部字段分別存在於請求報文和響應報文中的。一般有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,如下圖所示:

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開發工具中,需要進行下設置,打開偏好設置,如下圖所示:

這樣就設置好了,在使用的時候把鼠標放在類名上,比如定義了一個User類,就放在User類上面,在mac上,按住option+enter鍵就會出現對應的提示了。在windows上,按住alter+enter,效果如下圖所示:

問題六:IntelliJ IDEA提供的熱更新功能
打開tomcat設置,如下圖所示:

在紅色標注的地方,都選擇了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,所以在對屬性文件命名時要特別注意。最后,在更改或添加瀏覽器支持的語言時,也要注意選擇和屬性文件名相對應的語言,如下圖所示:

從圖中也可以看到美式英語和英語是不一樣的,所以在進行國際化適配的時候,這點是需要注意的。
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"也不能做更改,否則就會出現找不到對應屬性文件的問題。