本篇文章主要介紹自己在學(xué)習(xí)Spring MVC常用注解柳畔、標(biāo)簽庫(kù)馍管、國(guó)際化遇到的一些問題荸镊,分享給大家咽斧,希望對(duì)你有所幫助坎匿。
問題一:指定掃描包的位置
應(yīng)該將所有控制器類都放在基本包下盾剩,并且指定該掃描包雷激,避免Spring MVC掃描了無(wú)關(guān)的包。比如所有控制器類全部放在com.dodonew.controller包下面告私,掃描配置如下所示:
<!-- 指定包的掃描位置 -->
<context:component-scan base-package="com.dodonew.controller"/>
而不應(yīng)該配置為com.dodonew屎暇,因?yàn)閽呙枇藷o(wú)關(guān)的包,會(huì)影響應(yīng)用程序的效率驻粟。
問題二:Content-Type的理解
@RequestMapping注解中有這樣兩個(gè)屬性:consumes屬性和produces屬性根悼,類型為String[],consumes屬性指定處理請(qǐng)求的提交內(nèi)容類型(Content-Type)蜀撑。例如application/json挤巡、text/html等。produces屬性指定返回的內(nèi)容類型酷麦,返回的內(nèi)容類型必須是request請(qǐng)求頭(Accept)中所包含的類型矿卑,如下圖所示:
先看下2標(biāo)注的地方,request請(qǐng)求頭中Accept接收的類型包括text/html,application/xhtml+xml,application/xml等沃饶,在1標(biāo)注的地方母廷,我們看到返回的Content-Type的值為text/html;charset=UTF-8,這也驗(yàn)證了返回的內(nèi)容類型必須是request請(qǐng)求頭(Accept)中所包含的類型糊肤。
要理解Content-Type徘意,我們得先了解下HTTP的基礎(chǔ)知識(shí)。HTTP通信過程包括從客戶端發(fā)往服務(wù)端的請(qǐng)求以及從服務(wù)器端返回客戶端的響應(yīng)轩褐。用于HTTP協(xié)議交互的信息被稱為HTTP報(bào)文椎咧,請(qǐng)求端(客戶端)的HTTP報(bào)文叫做請(qǐng)求報(bào)文,響應(yīng)端(服務(wù)器端)的叫做響應(yīng)報(bào)文把介,HTTP報(bào)文本身是由多行(用CR+LF作換行符)數(shù)據(jù)構(gòu)成的字符串文本勤讽。HTTP報(bào)文大致可分為報(bào)文首部和報(bào)文主體兩塊,通常拗踢,并不一定要有報(bào)文主體脚牍,如下圖所示:
請(qǐng)求報(bào)文和響應(yīng)報(bào)文的結(jié)構(gòu)如下圖所示:
從圖中可以看出,通用首部字段巢墅、實(shí)體首部字段在請(qǐng)求報(bào)文和響應(yīng)報(bào)文中都存在的诸狭,而請(qǐng)求首部字段、響應(yīng)首部字段分別存在于請(qǐng)求報(bào)文和響應(yīng)報(bào)文中的君纫。一般有4種首部驯遇,分別是:通用首部、請(qǐng)求首部蓄髓、響應(yīng)首部和實(shí)體首部叉庐。而這個(gè)Content-Type字段就是存在于實(shí)體首部字段的,Content-Type字段說明了實(shí)體主體內(nèi)對(duì)象的媒體類型会喝,和Accept字段一樣陡叠,字段值用type/subtype形式賦值玩郊,一般是指網(wǎng)頁(yè)中存在的Content-Type,如果沒有指定Content-Type枉阵,默認(rèn)為text/html译红。下面是幾個(gè)常見的Content-Type:
- text/html
- text/plain
- text/css
- text/javascript
- application/x-www-form-urlencoded
- multipart/form-data
- application/json
- application/xml
其中后面四個(gè)是post的發(fā)包方式,也就是說在發(fā)送post請(qǐng)求的時(shí)候指定的Content-Type就是這些值兴溜。下面通過一幅圖來(lái)更加形象的理解下Content-Type临庇,如下圖所示:
content-type的主要作用就是約定客戶端與服務(wù)器端主體內(nèi)對(duì)象的類型,也就是傳輸數(shù)據(jù)指定的類型昵慌。
問題三:解決中文亂碼的問題
為了解決中文亂碼的問題假夺,我們?cè)赟pring MVC中經(jīng)常會(huì)做如下配置:
<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>
但是這個(gè)配置只對(duì)post請(qǐng)求是有效的,對(duì)get請(qǐng)求是沒有效果的斋攀,也就是說發(fā)送post請(qǐng)求中包含有中文的話已卷,該配置起作用,不會(huì)出現(xiàn)中文的問題淳蔼,但是發(fā)送get請(qǐng)求中包含中文的話侧蘸,該配置就不起作用,一樣會(huì)出現(xiàn)中文亂碼的情況鹉梨。為了解決get請(qǐng)求中文亂碼的問題讳癌,需要對(duì)get請(qǐng)求參數(shù)進(jìn)行urlEncode編碼,一般情況下get請(qǐng)求盡量不要帶中文參數(shù)存皂,如果使用建議使用兩次urlEncode編碼晌坤。
問題四:深入理解WEB-INF
WEB-INF里面存放的東西只對(duì)服務(wù)器端開放,對(duì)客戶端是不可見的旦袋。所以在使用的時(shí)候就需要注意以下幾點(diǎn):
- 頁(yè)面資源文件只能放在webapp目錄下(也就是web應(yīng)用的根目錄)骤菠,如css、js疤孕、image等商乎,不能放在WEB-INF下,因?yàn)閃EB-INF是對(duì)客戶端隱藏的祭阀,所以放在WEB-INF下會(huì)造成頁(yè)面的布局等文件引用不到的情況鹉戚。比如在引入jquery和json2文件時(shí),不能放在WEB-INF里面专控,否則會(huì)出現(xiàn)找不到引用文件的問題抹凳。
- 頁(yè)面文件一般放在WEB-INF目錄下面,這樣可以限制訪問踩官,提高安全性却桶,如jsp辩越、html文件,放在WEB-INF目錄下就可以避免客戶端直接在地址欄上輸入路徑進(jìn)行訪問了〔桓担基于不同的功能崖疤,把jsp放置在WEB-INF下的不同的目錄中权烧。
- 只能用轉(zhuǎn)向方式來(lái)訪問WEB-INF目錄下的jsp谈火,不能采用重定向的方式請(qǐng)求該目錄里面的任何資源温技。比如使用spring mvc中的dispatcherServlet進(jìn)行轉(zhuǎn)發(fā)。
- 在WEB-INF下面存放的jsp文件,訪問css、js等資源文件,忽略WEB-INF目錄即可。也就是說在WEB-INF里面的文件訪問不在WEB-INF里面的文件,忽略WEB-INF目錄即可凛虽。但是WEB-INF外面的文件訪問WEB-INF里面的文件钠署,必須要通過轉(zhuǎn)向的方式才能實(shí)現(xiàn)狸棍。
- 比如用a標(biāo)簽,location:ref標(biāo)簽,來(lái)訪問WEB-INF里面的東西是訪問不到的袜腥,因?yàn)檫@兩個(gè)標(biāo)簽相當(dāng)于是客戶端發(fā)送的請(qǐng)求鏈接见擦,而WEB-INF對(duì)客戶端是隱藏的鲤屡。
問題五:域?qū)ο鬄槭裁匆蛄谢?/h3>
先看下什么是序列化?對(duì)象序列化機(jī)制是Java語(yǔ)言內(nèi)建的一種對(duì)象持久化方式滴铅,可以很容易的在JVM中的活動(dòng)對(duì)象和字節(jié)數(shù)組流之間進(jìn)行轉(zhuǎn)換邦投。除了可以很簡(jiǎn)單的實(shí)現(xiàn)持久化之外伤锚,序列化機(jī)制的另外一個(gè)重要用途是在遠(yuǎn)程方法調(diào)用中,用來(lái)對(duì)開發(fā)人員屏蔽底層實(shí)現(xiàn)細(xì)節(jié)蠢涝。對(duì)于一個(gè)存在Java虛擬機(jī)中的對(duì)象來(lái)說玄呛,其內(nèi)部的狀態(tài)只保持在內(nèi)存中。JVM停止之后和二,這些狀態(tài)就丟失了徘铝。在很多情況下,對(duì)象的內(nèi)部狀態(tài)是需要被持久化下來(lái)的,提到持久化惕它,一種方式就是存儲(chǔ)到數(shù)據(jù)庫(kù)怕午,一種就是進(jìn)行序列化。所以對(duì)域?qū)ο筮M(jìn)行序列化后淹魄,可以保存對(duì)象的內(nèi)部狀態(tài)郁惜,更重要的是讓遠(yuǎn)程調(diào)用整個(gè)過程透明化。
注意:在序列化對(duì)象的時(shí)候甲锡,我們需要聲明一個(gè)全局唯一標(biāo)識(shí)符serialVersionUID兆蕉,為什么要聲明這個(gè)呢?因?yàn)榘岩粋€(gè)Java對(duì)象序列化之后缤沦,所得到的字節(jié)數(shù)組一般會(huì)保存在磁盤或數(shù)據(jù)庫(kù)之中虎韵。在保存完成之后,有可能原來(lái)的Java類有了更新缸废,比如添加了額外的域包蓝。這個(gè)時(shí)候從兼容性的角度出發(fā),要求仍然能夠讀取舊版本的序列化數(shù)據(jù)企量。在讀取的過程中测萎,當(dāng)ObjectInputStream發(fā)現(xiàn)一個(gè)對(duì)象的定義的時(shí)候,會(huì)嘗試在當(dāng)前JVM中查找其Java類定義届巩。這個(gè)查找過程不能僅根據(jù)Java類的全名來(lái)判斷硅瞧,因?yàn)楫?dāng)前JVM中可能存在名稱相同,但是含義完全不同的Java 類姆泻。這個(gè)對(duì)應(yīng)關(guān)系是通過一個(gè)全局惟一標(biāo)識(shí)符serialVersionUID來(lái)實(shí)現(xiàn)的零酪。通過在實(shí)現(xiàn)了Serializable接口的類中定義該域,就聲明了該Java類的一個(gè)惟一的序列化版本號(hào)拇勃。JVM會(huì)比對(duì)從字節(jié)數(shù)組中得出的類的版本號(hào)四苇,與JVM中查找到的類的版本號(hào)是否一致,來(lái)決定兩個(gè)類是否是兼容的方咆。對(duì)于開發(fā)人員來(lái)說月腋,需要記得的就是在實(shí)現(xiàn)了Serializable接口的類中定義這樣的一個(gè)域,并在版本更新過程中保持該值不變瓣赂。當(dāng)然榆骚,如果不希望維持這種向后兼容性,換一個(gè)版本號(hào)即可煌集。該域的值一般是綜合Java類的各個(gè)特性而計(jì)算出來(lái)的一個(gè)哈希值妓肢,可以通過Java提供的serialver命令來(lái)生成。在Eclipse中苫纤,如果Java類實(shí)現(xiàn)了Serializable接口碉钠,Eclipse會(huì)提示并幫你生成這個(gè)serialVersionUID纲缓。在IntelliJ IDEA開發(fā)工具中,需要進(jìn)行下設(shè)置喊废,打開偏好設(shè)置祝高,如下圖所示:
這樣就設(shè)置好了,在使用的時(shí)候把鼠標(biāo)放在類名上污筷,比如定義了一個(gè)User類工闺,就放在User類上面,在mac上瓣蛀,按住option+enter鍵就會(huì)出現(xiàn)對(duì)應(yīng)的提示了陆蟆。在windows上,按住alter+enter揪惦,效果如下圖所示:
問題六:IntelliJ IDEA提供的熱更新功能
打開tomcat設(shè)置涧窒,如下圖所示:
在紅色標(biāo)注的地方奶段,都選擇了Update classes and resouces丹禀,選擇了這兩個(gè)配置的作用就是當(dāng)你更改了一個(gè)類的時(shí)候挑豌,不需要重新啟動(dòng)tomcat骚腥。但是這兩個(gè)配置只在Debug模式起作用隧哮,在Run模式下是不起作用的熟掂,這點(diǎn)一定要注意桨昙。
問題七:Spring MVC整合fastjson組件
HttpMessageConvert是Spring3.0之后新增的一個(gè)重要接口讲弄,它負(fù)責(zé)將請(qǐng)求信息轉(zhuǎn)換為一個(gè)對(duì)象(類型為T)措左,并將對(duì)象(類型T)綁定到請(qǐng)求方法的參數(shù)中或輸出為響應(yīng)信息。在Spring中的dispatcherServlet默認(rèn)已經(jīng)裝配了RequestMappingHandlerAdapter作為HandleAdapter組件的實(shí)現(xiàn)類避除,也就是說RequestMappingHandlerAdapter默認(rèn)已經(jīng)使用了HttpMessageConvert怎披,會(huì)將請(qǐng)求信息轉(zhuǎn)換為對(duì)象,或?qū)?duì)象轉(zhuǎn)換為響應(yīng)信息瓶摆。Spring為HttpMessageConvert<T>提供了多個(gè)實(shí)現(xiàn)類凉逛,如下:
- StringHttpMessageConverter
- FormHttpMessageConverter
- XmlAwareFormHttpMessageConverter
- ResourceHttpMessageConverter
- BufferedImageHttpMessageConverter
- MappingJackson2HttpMessageConverter等等。
注意:如果在Spring Web容器中顯式定義了一個(gè)RequestMappingHandlerAdapter群井,則Spring MVC的RequestMappingHandlerAdapter默認(rèn)裝配的HttpMessageConverter將不再起作用状飞。
在項(xiàng)目開發(fā)中使用json數(shù)據(jù)越來(lái)越成為一種趨勢(shì)了,而Spring默認(rèn)使用Jackson處理json數(shù)據(jù)书斜。如果要使用Jackson處理json數(shù)據(jù)的話诬辈,就需要引入依賴的Jackson包,如果沒有依賴Jackson包荐吉,當(dāng)客戶端發(fā)送application/json格式的數(shù)據(jù)時(shí)焙糟,服務(wù)端就會(huì)報(bào)application/json not supported contentType錯(cuò)誤。使用默認(rèn)的Jackson處理json數(shù)據(jù)样屠,只需要引入依賴的jar包即可穿撮,不需要做額外的配置搓劫,否則的話,就需要添加額外的配置混巧,比如使用業(yè)界比較流行的fastjson組件枪向,添加的額外配置如下:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<!-- 配置fastjson中實(shí)現(xiàn)HttpMessageConvert接口的轉(zhuǎn)換器 -->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!-- 這里順序不能反,一定先寫text/html咧党,不然IE下會(huì)出現(xiàn)下載提示 -->
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
<property name="features">
<array>
<!-- 是否輸出值為null的字段秘蛔,默認(rèn)為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時(shí)是否使用雙引號(hào),默認(rèn)為true
- WriteMapNullValue :否輸出值為null的字段傍衡,默認(rèn)為false
- WriteNullListAsEmpty :List字段若為null深员,輸出[],而非null
- WriteNullNumberAsZero :數(shù)值字段若為null蛙埂,輸出0倦畅,而非null
- WriteNullStringAsEmpty :字符類型字段若為null,輸出”“绣的,而非null
- WriteNullBooleanAsFalse :Boolean字段若為null叠赐,輸出false,而非null
通過這樣的配置屡江,就可以使用Fastjson對(duì)json數(shù)據(jù)進(jìn)行處理了芭概,不再使用Jackson組件。
問題八:Spring MVC標(biāo)簽庫(kù)有哪些優(yōu)勢(shì)惩嘉?
Spring從2.0版本開始罢洲,提供了一組功能強(qiáng)大的標(biāo)簽用來(lái)在JSP和Spring Web MVC中處理其他元素,相比其他的標(biāo)簽庫(kù)文黎,Spring的標(biāo)簽庫(kù)集成在Spring Web MVC中惹苗,因此這里的標(biāo)簽庫(kù)可以訪問控制器(Controller)處理命令對(duì)象和綁定數(shù)據(jù),這樣會(huì)使JSP頁(yè)面開發(fā)更加容易耸峭。要使用Spring MVC的標(biāo)簽庫(kù)桩蓉,需要在JSP頁(yè)面的開頭處聲明一下taglib指令,如下所示:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
問題九:Spring MVC國(guó)際化要注意的問題
Spring MVC的國(guó)際化實(shí)現(xiàn)方式有3種抓艳,分別為AcceptHeaderLocaleResolver國(guó)際化触机,SessionLocaleResolver國(guó)際化,CookieLocaleResolver國(guó)際化玷或,具體的實(shí)現(xiàn)可以搜下文章看下儡首,這里主要講下自己遇到的一些問題。
先看下基于AcceptHeaderLocaleResolver國(guó)際化偏友,它是默認(rèn)的蔬胯,也是最容易使用的語(yǔ)言區(qū)域解析器,使用它Spring MVC會(huì)讀取瀏覽器的accept-language標(biāo)題位他,來(lái)確定使用哪個(gè)語(yǔ)言區(qū)域氛濒。AcceptHeaderLocalResolver可以不用顯式配置产场,也可以顯式配置,配置如下所示:
<!-- 國(guó)際化 -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>message</value>
</list>
</property>
</bean>
<!-- 國(guó)際化操作攔截器如果采用基于(Session/Cookie)則必須配置 -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
</mvc:interceptors>
<!--AcceptHeaderLocalResolver配置舞竿,因?yàn)锳cceptHeaderLocalResolver是默認(rèn)語(yǔ)言區(qū)域解析器京景,不配置也可以 -->
<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",一定要是這個(gè)值骗奖,不能做任何改動(dòng)确徙,否則就會(huì)報(bào)錯(cuò)說找不到對(duì)應(yīng)的國(guó)際化屬性文件,這個(gè)稍后會(huì)做解釋的执桌。另外鄙皇,需要注意的是美式英語(yǔ)和英語(yǔ)兩個(gè)是不同的,美式英語(yǔ)對(duì)應(yīng)的屬性文件名后綴為en_US仰挣,英語(yǔ)對(duì)應(yīng)的屬性文件名為en伴逸,所以在對(duì)屬性文件命名時(shí)要特別注意。最后膘壶,在更改或添加瀏覽器支持的語(yǔ)言時(shí)错蝴,也要注意選擇和屬性文件名相對(duì)應(yīng)的語(yǔ)言,如下圖所示:
從圖中也可以看到美式英語(yǔ)和英語(yǔ)是不一樣的香椎,所以在進(jìn)行國(guó)際化適配的時(shí)候漱竖,這點(diǎn)是需要注意的。
SessionLocaleResolver需要對(duì)其進(jìn)行顯式配置畜伐,會(huì)從HttpSession作用域中獲取用戶設(shè)置的語(yǔ)言區(qū)域,來(lái)確定使用哪個(gè)語(yǔ)言區(qū)域躺率。CookieLocaleResolver也需要對(duì)其進(jìn)行顯式配置玛界,會(huì)從Cookie中獲取用戶設(shè)置的語(yǔ)言區(qū)域,來(lái)確定使用哪個(gè)語(yǔ)言區(qū)域悼吱。它們的配置如上所示慎框,也都需要注意id="localeResolver"的值是不能做更改的,下面來(lái)解釋下為什么不能做更改后添。
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會(huì)根據(jù)localeResolver名稱找到對(duì)應(yīng)的bean笨枯,如果名稱發(fā)生了改變,就找不到對(duì)應(yīng)的bean遇西,所以這個(gè)時(shí)候基于SessionLocaleResolver的國(guó)際化就會(huì)失敗馅精,不起作用。這個(gè)時(shí)候粱檀,系統(tǒng)就會(huì)改用默認(rèn)的AcceptHeaderLocaleResolver來(lái)實(shí)現(xiàn)國(guó)際化洲敢。同樣id="messageSource"也不能做更改,否則就會(huì)出現(xiàn)找不到對(duì)應(yīng)屬性文件的問題茄蚯。
參考文章
Java深度歷險(xiǎn)(十)Java對(duì)象序列化與RMI
Cannot change HTTP accept header