Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
這篇快速文章中,我們將使用代碼示例討論所有Spring MVC JSP表單標(biāo)記。
這些Spring MVC表單標(biāo)簽為我們提供了額外的支持。它們支持?jǐn)?shù)據(jù)綁定,因此這允許我們自動(dòng)設(shè)置數(shù)據(jù)并從Java對(duì)象和bean中檢索數(shù)據(jù)。
從版本2.0開始,Spring提供了一組全面的數(shù)據(jù)綁定感知標(biāo)記,用于在使用JSP和Spring Web MVC時(shí)處理表單元素。每個(gè)標(biāo)記都支持其相應(yīng)HTML標(biāo)記對(duì)應(yīng)的屬性集,使標(biāo)記熟悉且直觀易用。標(biāo)記生成的HTML符合HTML 4.01 / XHTML 1.0。
讓我們?yōu)g覽所有這些表單標(biāo)簽,并查看每個(gè)標(biāo)簽的使用方式示例。我們已經(jīng)包含生成的HTML代碼段,其中某些代碼需要進(jìn)一步評(píng)論。
組態(tài)
表單標(biāo)簽庫(kù)捆綁在一起spring-webmvc.jar。調(diào)用庫(kù)描述符spring-form.tld。
如何引用Spring MVC表單標(biāo)簽
要使用此庫(kù)中的標(biāo)記,請(qǐng)將以下指令添加到JSP頁(yè)面的頂部:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
其中a form是要用于此庫(kù)中標(biāo)記的標(biāo)記名稱前綴。
表格標(biāo)簽
此標(biāo)記呈現(xiàn)HTML“表單”標(biāo)記,并公開內(nèi)部標(biāo)記的綁定路徑以進(jìn)行綁定。它將命令對(duì)象放在PageContext中,以便內(nèi)部標(biāo)記可以訪問命令對(duì)象。此庫(kù)中的所有其他標(biāo)記都是表單標(biāo)記的嵌套標(biāo)記。假設(shè)我們有一個(gè)名為的域?qū)ο骍ser。它是一個(gè)具有諸如和的屬性的JavaBean。我們將它用作返回表單控制器的表單后備對(duì)象。下面是一個(gè)示例:firstNamelastNameform.jspform.jsp+
<form:form>
<table>
<tr>
<td>First Name:</td>
<td>
<form:input path="firstName" />
</td>
</tr>
<tr>
<td>Last Name:</td>
<td>
<form:input path="lastName" />
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form:form>
該firstName和lastName值由放置在命令對(duì)象中檢索PageContext由頁(yè)控制器。加載表單時(shí),Spring MVC將類user.getFirstName()和getLastName()(getter方法)。提交表單時(shí),Spring MVC將調(diào)用user.setFirstName()和user.setLastName()方法。生成的HTML看起來像標(biāo)準(zhǔn)形式:
<form method="POST">
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value="Harry" /></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value="Potter" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form>
輸入標(biāo)簽
此輸入標(biāo)記默認(rèn)使用綁定值和type='text'呈現(xiàn)HTML'input'標(biāo)記。舉個(gè)例子:
<form:input path="firstName"/>
<form:input path="lastName"/>
生成的HTML看起來像標(biāo)準(zhǔn)形式:
<input name="firstName" type="text" value="Harry"/>
<input name="lastName" type="text" value="Potter"/>
從Spring 3.1開始,您可以使用其他類型,例如HTML5特定類型,如“email”,“tel”,“date”等。
復(fù)選框標(biāo)記
此標(biāo)記呈現(xiàn)帶有“復(fù)選框”類型的HTML“輸入”標(biāo)記。讓我們假設(shè)我們User有偏好,例如新聞?dòng)嗛喓蛺酆昧斜怼R韵率荘references該類的示例:
public class Preferences {
private boolean receiveNewsletter;
private String[] interests;
private String favouriteWord;
public boolean isReceiveNewsletter() {
return receiveNewsletter;
}
public void setReceiveNewsletter(boolean receiveNewsletter) {
this.receiveNewsletter=receiveNewsletter;
}
public String[] getInterests() {
return interests;
}
public void setInterests(String[] interests) {
this.interests=interests;
}
public String getFavouriteWord() {
return favouriteWord;
}
public void setFavouriteWord(String favouriteWord) {
this.favouriteWord=favouriteWord;
}
}
form.jsp看起來像:
<form:form>
<table>
<tr>
<td>Subscribe to newsletter?:</td>
<%-- Approach 1: Property is of type java.lang.Boolean --%>
<td>
<form:checkbox path="preferences.receiveNewsletter" />
</td>
</tr>
<tr>
<td>Interests:</td>
<%-- Approach 2: Property is of an array or of type java.util.Collection --%>
<td>
Quidditch:
<form:checkbox path="preferences.interests" value="Quidditch" /> Herbology:
<form:checkbox path="preferences.interests" value="Herbology" /> Defence Against the Dark Arts:
<form:checkbox path="preferences.interests" value="Defence Against the Dark Arts" />
</td>
</tr>
<tr>
<td>Favourite Word:</td>
<%-- Approach 3: Property is of type java.lang.Object --%>
<td>
Magic:
<form:checkbox path="preferences.favouriteWord" value="Magic" />
</td>
</tr>
</table>
</form:form>
checkbox標(biāo)簽有三種方法可以滿足我們所有的復(fù)選框需求。
方法一 - 當(dāng)綁定值為類型時(shí)java.lang.Boolean,如果綁定值為true,則輸入(復(fù)選框)將標(biāo)記為“已檢查”。value屬性對(duì)應(yīng)value屬性的已解析值setValue(Object)。
方法二 - 當(dāng)綁定值為數(shù)組類型時(shí) java.util.Collection,如果setValue(Object) 綁定中存在配置的值 ,則輸入(復(fù)選框)將標(biāo)記為“已檢查” Collection。
方法三 - 對(duì)于任何其他綁定值類型,如果配置setValue(Object) 等于綁定值,則輸入(復(fù)選框)將標(biāo)記為“已檢查” 。
請(qǐng)注意,無論采用何種方法,都會(huì)生成相同的HTML結(jié)構(gòu)。以下是一些復(fù)選框的HTML代碼段:
<tr>
<td>Interests:</td>
<td>
Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch" />
<input type="hidden" value="1" name="_preferences.interests" /> Herbology: <input name="preferences.interests" type="checkbox" value="Herbology" />
<input type="hidden" value="1" name="_preferences.interests" /> Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts" />
<input type="hidden" value="1" name="_preferences.interests" />
</td>
</tr>
標(biāo)簽復(fù)選框
此標(biāo)記呈現(xiàn)多個(gè)帶有“復(fù)選框”類型的HTML“輸入”標(biāo)記。下面是使用此標(biāo)記的JSP示例:
<form:form>
<table>
<tr>
<td>Interests:</td>
<td>
<%-- Property is of an array or of type java.util.Collection --%>
<form:checkboxes path="preferences.interests" items="${interestList}" />
</td>
</tr>
</table>
</form:form>
radiobutton標(biāo)簽
此標(biāo)記呈現(xiàn)帶有“radio”類型的HTML“input”標(biāo)記。典型的使用模式將涉及綁定到同一屬性但具有不同值的多個(gè)標(biāo)記實(shí)例。
<tr>
<td>Sex:</td>
<td>
Male:
<form:radiobutton path="sex" value="M" /> <br/> Female:
<form:radiobutton path="sex" value="F" />
</td>
</tr>
radiobuttons標(biāo)簽
此標(biāo)記呈現(xiàn)多個(gè)帶有“radio”類型的HTML“input”標(biāo)記。例如:
<tr>
<td>Sex:</td>
<td>
<form:radiobuttons path="sex" items="${sexOptions}" />
</td>
</tr>
密碼標(biāo)簽
此標(biāo)記使用綁定值呈現(xiàn)帶有“password”類型的HTML“input”標(biāo)記。
<tr>
<td>Password:</td>
<td>
<form:password path="password" />
</td>
</tr>
請(qǐng)注意,默認(rèn)情況下,密碼值不會(huì)顯示。如果您確實(shí)需要顯示密碼值,請(qǐng)將“showPassword”屬性的值設(shè)置為true,如下所示。
<tr>
<td>Password:</td>
<td>
<form:password path="password" value="^76525bvHGq" showPassword="true" />
</td>
</tr>
選擇標(biāo)簽
此標(biāo)記呈現(xiàn)HTML“select”元素。它支持?jǐn)?shù)據(jù)綁定到所選選項(xiàng)以及使用嵌套選項(xiàng)和選項(xiàng)標(biāo)記。讓我們假設(shè)一個(gè)用戶有一個(gè)技能列表。
<tr>
<td>Skills:</td>
<td>
<form:select path="skills" items="${skills}" />
</td>
</tr>
如果用戶的技能屬于草藥學(xué),那么“技能”行的HTML源代碼如下所示:
<tr>
<td>Skills:</td>
<td>
<select name="skills" multiple="true">
<option value="Potions">Potions</option>
<option value="Herbology" selected="selected">Herbology</option>
<option value="Quidditch">Quidditch</option>
</select>
</td>
</tr>
選項(xiàng)標(biāo)簽
此標(biāo)記呈現(xiàn)HTML“選項(xiàng)”。它根據(jù)綁定值設(shè)置“選中”。
<tr>
<td>House:</td>
<td>
<form:select path="house">
<form:option value="Gryffindor" />
<form:option value="Hufflepuff" />
<form:option value="Ravenclaw" />
<form:option value="Slytherin" />
</form:select>
</td>
</tr>
如果用戶的房子在格蘭芬多,那么'House'行的HTML源代碼如下所示:
<tr>
<td>House:</td>
<td>
<select name="house">
<option value="Gryffindor" selected="selected">Gryffindor</option>
<option value="Hufflepuff">Hufflepuff</option>
<option value="Ravenclaw">Ravenclaw</option>
<option value="Slytherin">Slytherin</option>
</select>
</td>
</tr>
選項(xiàng)標(biāo)簽
此標(biāo)記呈現(xiàn)HTML'選項(xiàng)'標(biāo)記的列表。它根據(jù)綁定值設(shè)置“selected”屬性。
<tr>
<td>Country:</td>
<td>
<form:select path="country">
<form:option value="-" label="--Please Select" />
<form:options items="${countryList}" itemValue="code" itemLabel="name" />
</form:select>
</td>
</tr>
如果用戶居住在英國(guó),“國(guó)家/地區(qū)”行的HTML源代碼如下所示:
<tr>
<td>Country:</td>
<td>
<select name="country">
<option value="-">--Please Select</option>
<option value="AT">Austria</option>
<option value="UK" selected="selected">United Kingdom</option>
<option value="US">United States</option>
</select>
</td>
</tr>
textarea標(biāo)簽
此標(biāo)記呈現(xiàn)HTML“textarea”。
<tr>
<td>Notes:</td>
<td>
<form:textarea path="notes" rows="3" cols="20" />
</td>
<td>
<form:errors path="notes" />
</td>
</tr>
隱藏的標(biāo)簽
此標(biāo)記使用綁定值呈現(xiàn)類型為“hidden”的HTML“input”標(biāo)記。要提交未綁定的隱藏值,請(qǐng)使用類型為“hidden”的HTML輸入標(biāo)記。
<form:hidden path="house"/>
如果我們選擇將“house”值作為隱藏值提交,則HTML將如下所示:
<input name="house" type="hidden" value="Gryffindor"/>
錯(cuò)誤標(biāo)簽
此標(biāo)記在HTML“span”標(biāo)記中呈現(xiàn)字段錯(cuò)誤。它可以訪問控制器中創(chuàng)建的錯(cuò)誤或由與控制器關(guān)聯(lián)的任何驗(yàn)證器創(chuàng)建的錯(cuò)誤。假設(shè)我們希望在提交表單后顯示firstName和lastName字段的所有錯(cuò)誤消息。我們有一個(gè)用于調(diào)用User類實(shí)例的驗(yàn)證器UserValidator。
public class UserValidator implements Validator {
public boolean supports(Class candidate) {
return User.class.isAssignableFrom(candidate);
}
public void validate(Object obj, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
}
}
form.jsp看起來像:
<form:form>
<table>
<tr>
<td>First Name:</td>
<td>
<form:input path="firstName" />
</td>
<%-- Show errors for firstName field --%>
<td>
<form:errors path="firstName" />
</td>
</tr>
<tr>
<td>Last Name:</td>
<td>
<form:input path="lastName" />
</td>
<%-- Show errors for lastName field --%>
<td>
<form:errors path="lastName" />
</td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form:form>
如果我們?cè)趂irstName和lastName字段中提交一個(gè)空值的表單,那么這就是HTML的樣子:
<form method="POST">
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value="" /></td>
<%-- Associated errors to firstName field displayed --%>
<td><span name="firstName.errors">Field is required.</span></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value="" /></td>
<%-- Associated errors to lastName field displayed --%>
<td><span name="lastName.errors">Field is required.</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form>
如果我們想顯示給定頁(yè)面的整個(gè)錯(cuò)誤列表怎么辦?下面的示例顯示errors標(biāo)記還支持一些基本的通配符功能。path=“*”顯示所有錯(cuò)誤; path=“l(fā)astName”顯示與lastName字段關(guān)聯(lián)的所有錯(cuò)誤(如果省略路徑); 僅顯示對(duì)象錯(cuò)誤。下面的示例將在頁(yè)面頂部顯示錯(cuò)誤列表,然后是字段旁邊的字段特定錯(cuò)誤:
<form:form>
<form:errors path="*" cssClass="errorBox" />
<table>
<tr>
<td>First Name:</td>
<td>
<form:input path="firstName" />
</td>
<td>
<form:errors path="firstName" />
</td>
</tr>
<tr>
<td>Last Name:</td>
<td>
<form:input path="lastName" />
</td>
<td>
<form:errors path="lastName" />
</td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form:form>
HTML看起來像:
<form method="POST">
<span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value="" /></td>
<td><span name="firstName.errors">Field is required.</span></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value="" /></td>
<td><span name="lastName.errors">Field is required.</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form>
HTML5標(biāo)簽
從Spring 3開始,Spring表單標(biāo)記庫(kù)允許輸入動(dòng)態(tài)屬性,這意味著您可以輸入任何HTML5特定屬性。在Spring 3.1中,表單輸入標(biāo)記支持輸入“text”以外的type屬性。這旨在允許呈現(xiàn)新的HTML5特定輸入類型,例如“電子郵件”,“日期”,“范圍”等。請(qǐng)注意,不需要輸入type='text',因?yàn)?text'是默認(rèn)類型。
者:國(guó)士無雙A
來源:簡(jiǎn)書
應(yīng)該將所有控制器類都放在基本包下,并且指定該掃描包,避免Spring MVC掃描了無關(guān)的包。比如所有控制器類全部放在com.dodonew.controller包下面,掃描配置如下所示:
<!-- 指定包的掃描位置 --> <context:component-scan base-package="com.dodonew.controller"/>
而不應(yīng)該配置為com.dodonew,因?yàn)閽呙枇藷o關(guān)的包,會(huì)影響應(yīng)用程序的效率。
@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)中所包含的類型,如下圖所示:
annotation 1-1.png
先看下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)文主體,如下圖所示:
annotation 1-2.png
請(qǐng)求報(bào)文和響應(yīng)報(bào)文的結(jié)構(gòu)如下圖所示:
annotation 1-3.png
從圖中可以看出,通用首部字段、實(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:
其中后面四個(gè)是post的發(fā)包方式,也就是說在發(fā)送post請(qǐng)求的時(shí)候指定的Content-Type就是這些值。下面通過一幅圖來更加形象的理解下Content-Type,如下圖所示:
annotation 1-4.png
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里面存放的東西只對(duì)服務(wù)器端開放,對(duì)客戶端是不可見的。所以在使用的時(shí)候就需要注意以下幾點(diǎn):
先看下什么是序列化?對(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)用中,用來對(duì)開發(fā)人員屏蔽底層實(shí)現(xiàn)細(xì)節(jié)。對(duì)于一個(gè)存在Java虛擬機(jī)中的對(duì)象來說,其內(nèi)部的狀態(tài)只保持在內(nèi)存中。JVM停止之后,這些狀態(tài)就丟失了。在很多情況下,對(duì)象的內(nèi)部狀態(tà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ù)之中。在保存完成之后,有可能原來的Java類有了更新,比如添加了額外的域。這個(gè)時(shí)候從兼容性的角度出發(fā),要求仍然能夠讀取舊版本的序列化數(shù)據(jù)。在讀取的過程中,當(dāng)ObjectInputStream發(fā)現(xiàn)一個(gè)對(duì)象的定義的時(shí)候,會(huì)嘗試在當(dāng)前JVM中查找其Java類定義。這個(gè)查找過程不能僅根據(jù)Java類的全名來判斷,因?yàn)楫?dāng)前JVM中可能存在名稱相同,但是含義完全不同的Java 類。這個(gè)對(duì)應(yīng)關(guān)系是通過一個(gè)全局惟一標(biāo)識(shí)符serialVersionUID來實(shí)現(xiàn)的。通過在實(shí)現(xiàn)了Serializable接口的類中定義該域,就聲明了該Java類的一個(gè)惟一的序列化版本號(hào)。JVM會(huì)比對(duì)從字節(jié)數(shù)組中得出的類的版本號(hào),與JVM中查找到的類的版本號(hào)是否一致,來決定兩個(gè)類是否是兼容的。對(duì)于開發(fā)人員來說,需要記得的就是在實(shí)現(xiàn)了Serializable接口的類中定義這樣的一個(gè)域,并在版本更新過程中保持該值不變。當(dāng)然,如果不希望維持這種向后兼容性,換一個(gè)版本號(hào)即可。該域的值一般是綜合Java類的各個(gè)特性而計(jì)算出來的一個(gè)哈希值,可以通過Java提供的serialver命令來生成。在Eclipse中,如果Java類實(shí)現(xiàn)了Serializable接口,Eclipse會(huì)提示并幫你生成這個(gè)serialVersionUID。在IntelliJ IDEA開發(fā)工具中,需要進(jìn)行下設(shè)置,打開偏好設(shè)置,如下圖所示:
annotation 1-5.png
這樣就設(shè)置好了,在使用的時(shí)候把鼠標(biāo)放在類名上,比如定義了一個(gè)User類,就放在User類上面,在mac上,按住option+enter鍵就會(huì)出現(xiàn)對(duì)應(yīng)的提示了。在windows上,按住alter+enter,效果如下圖所示:
annotation 1-6.png
打開tomcat設(shè)置,如下圖所示:
annotation 1-7.png
在紅色標(biāo)注的地方,都選擇了Update classes and resouces,選擇了這兩個(gè)配置的作用就是當(dāng)你更改了一個(gè)類的時(shí)候,不需要重新啟動(dòng)tomcat。但是這兩個(gè)配置只在Debug模式起作用,在Run模式下是不起作用的,這點(diǎn)一定要注意。
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)類,如下:
注意:如果在Spring Web容器中顯式定義了一個(gè)RequestMappingHandlerAdapter,則Spring MVC的RequestMappingHandlerAdapter默認(rèn)裝配的HttpMessageConverter將不再起作用。
在項(xiàng)目開發(fā)中使用json數(shù)據(jù)越來越成為一種趨勢(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>
通過這樣的配置,就可以使用Fastjson對(duì)json數(shù)據(jù)進(jìn)行處理了,不再使用Jackson組件。
Spring從2.0版本開始,提供了一組功能強(qiáng)大的標(biāo)簽用來在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ó)際化實(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)題,來確定使用哪個(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ǔ)言,如下圖所示:
annotation 1-8.png
從圖中也可以看到美式英語(yǔ)和英語(yǔ)是不一樣的,所以在進(jìn)行國(guó)際化適配的時(shí)候,這點(diǎn)是需要注意的。
SessionLocaleResolver需要對(duì)其進(jìn)行顯式配置,會(huì)從HttpSession作用域中獲取用戶設(shè)置的語(yǔ)言區(qū)域,來確定使用哪個(gè)語(yǔ)言區(qū)域。CookieLocaleResolver也需要對(duì)其進(jìn)行顯式配置,會(huì)從Cookie中獲取用戶設(shè)置的語(yǔ)言區(qū)域,來確定使用哪個(gè)語(yǔ)言區(qū)域。它們的配置如上所示,也都需要注意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會(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來實(shí)現(xiàn)國(guó)際化。同樣id="messageSource"也不能做更改,否則就會(huì)出現(xiàn)找不到對(duì)應(yīng)屬性文件的問題。
最后,我自己是一名從事了多年開發(fā)的JAVA老程序員,辭職目前在做自己的java私人定制課程,今年年初我花了一個(gè)月整理了一份最適合2019年學(xué)習(xí)的java學(xué)習(xí)干貨,可以送給每一位喜歡java的小伙伴,想要獲取的可以關(guān)注我的頭條號(hào)并在后臺(tái)私信我:交流,即可免費(fèi)獲取。
SpringMVC是一種基于Java的Web框架,?用于構(gòu)建Web應(yīng)用程序。?
SpringMVC的核心在于其設(shè)計(jì)模式——MVC(?Model-View-Controller)?,?這種設(shè)計(jì)模式將應(yīng)用程序的數(shù)據(jù)處理、?用戶接口和控制邏輯分開,?使得代碼結(jié)構(gòu)更加清晰,?便于維護(hù)和擴(kuò)展。?在SpringMVC中,?Model代表數(shù)據(jù)和相關(guān)的業(yè)務(wù)邏輯,?View負(fù)責(zé)顯示數(shù)據(jù)給用戶,?而Controller則是協(xié)調(diào)Model和View的橋梁,?處理用戶請(qǐng)求并返回相應(yīng)的視圖。
MVC全稱Model View Controller,是一種設(shè)計(jì)創(chuàng)建Web應(yīng)用程序的模式。這三個(gè)單詞分別代表Web應(yīng)用程序的三個(gè)部分:
(1)前端控制器 DispatcherServlet(不需要開發(fā),由框架提供【核心】)
DispatcherServlet 是 Spring MVC 的入口函數(shù)。接收請(qǐng)求,響應(yīng)結(jié)果,相當(dāng)于轉(zhuǎn)發(fā)器,中央處理器。有了 DispatcherServlet ,可以大大減少其它組件之間的耦合度。
用戶請(qǐng)求到達(dá)前端控制器,就相當(dāng)于 mvc 模式中的 c,DispatcherServlet 是整個(gè)流程控制的中心,由它調(diào)用其它組件來處理用戶的請(qǐng)求。
(2)處理器映射器 HandlerMapping (不需要開發(fā),由框架提供)
HandlerMapping 負(fù)責(zé)根據(jù)用戶請(qǐng)求(URL),找到相應(yīng)的 Handler 即處理器(Controller),SpringMVC 提供了不同映射器實(shí)現(xiàn)的不同映射方式,例如:配置文件方式,實(shí)現(xiàn)接口方式,注解方式等。
(3)處理器適配器 HandlerAdapter (不需要開發(fā),由框架提供)
按照特定規(guī)則(HandlerAdapter 要求的規(guī)則)去執(zhí)行 Handler,通過 HandlerAdapter 對(duì)處理器進(jìn)行執(zhí)行,這是適配器模式的應(yīng)用,通過擴(kuò)展適配器可以對(duì)更多類型的處理器進(jìn)行處理。
(4)處理器 Handler (需要工程師開發(fā))
Handler 是繼 DispatcherServlet 前端控制器的后端控制器,在 DispatcherServlet 的控制下,Handler 對(duì)具體的用戶請(qǐng)求進(jìn)行處理。由于 Handler 涉及到具體的用戶業(yè)務(wù)請(qǐng)求,所以一般情況下需要工程師根據(jù)業(yè)務(wù)需求來開發(fā) Handler。
(5)視圖解析器 View Resolver (不需要開發(fā),由框架提供)
作用:進(jìn)行視圖解析,根據(jù)邏輯視圖名解析成真正的視圖(View),View Resolver 負(fù)責(zé)將處理結(jié)果生成 View 視圖。首先,根據(jù)邏輯視圖名解析成物理視圖名(即具體的頁(yè)面地址),再生成 View 視圖對(duì)象,最后對(duì) View 進(jìn)行渲染,將處理結(jié)果通過頁(yè)面展示給用戶。
Spring MVC 框架提供了很多的 View 視圖類型,包括:jstlView、freemarkerView、pdfView 等。 一般情況下,需要通過頁(yè)面標(biāo)簽或頁(yè)面模版技術(shù),將模型數(shù)據(jù)通過頁(yè)面展示給用戶,這需要由工程師根據(jù)業(yè)務(wù)需求開發(fā)具體的頁(yè)面。
(6)視圖 View (需要工程師開發(fā))
View 是一個(gè)接口,實(shí)現(xiàn)類才可以支持不同的View類型(jsp、freemarker、pdf...)
HandlerMapping負(fù)責(zé)根據(jù)用戶請(qǐng)求url找到Handler即處理器,springmvc提供了不同的映射器實(shí)現(xiàn)不同的映射方式,例如:配置文件方式,實(shí)現(xiàn)接口方式,注解方式等。
Handler 是繼DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler對(duì)具體的用戶請(qǐng)求進(jìn)行處理。
ViewResolver通過HandlerAdapter對(duì)處理器進(jìn)行執(zhí)行,這是適配器模式的應(yīng)用,通過擴(kuò)展適配器可以對(duì)更多類型的處理器進(jìn)行執(zhí)行。
Spring MVC所有的請(qǐng)求都經(jīng)過DispatcherServlet來統(tǒng)一分發(fā)。DispatcherServlet將請(qǐng)求分發(fā)給Controller之前,需要借助于Spring MVC提供的HandlerMapping定位到具體的Controller。 HandlerMapping接口負(fù)責(zé)完成客戶請(qǐng)求到Controller映射。 Controller接口將處理用戶請(qǐng)求,這和Java Servlet扮演的角色是一致的。一旦Controller處理完用戶請(qǐng)求,則返回ModelAndView(數(shù)據(jù)和視圖)對(duì)象給DispatcherServlet前端控制器。從宏觀角度考慮,DispatcherServlet是整個(gè)Web應(yīng)用的控制器;從微觀考慮,Controller是單個(gè)Http請(qǐng)求處理過程中的控制器,而ModelAndView是Http請(qǐng)求過程中返回的模型(Model)和視圖(View)。 返回的視圖需要通過ViewResolver接口(視圖解析器)在Web應(yīng)用中負(fù)責(zé)查找View對(duì)象,從從而將相應(yīng)結(jié)果渲染給客戶。
<!-- springmvc依賴 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- servlet依賴 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- jsp依賴 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--聲明springmvc的核心對(duì)象
訪問mymvc地址后,報(bào)錯(cuò),文件沒有找到。找到文件是/WEB-INF/springmvc-servlet.xml或者myweb-servlet.xml(這個(gè))
錯(cuò)誤原因:在Servlet的init()方法中,創(chuàng)建springmvc使用的容器對(duì)象WebApplicationContext
WebApplicationContext ctx=new ClassPathXmlApplicationContext(配置文件)
配置文件的默認(rèn)路徑:/WEB-INF/<servlet-name>-servlet.xml
DispatcherServlet作用:
1.在init()中創(chuàng)建springmvc的容器對(duì)象 WebApplicationContext,創(chuàng)建springmvc配置文件的所有Java對(duì)象。
java對(duì)象就是Controller對(duì)象
2.DispatcherServlet 是一個(gè)Servlet,能夠接受請(qǐng)求。
-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 如果是自定義的文件,需要在這寫自定義配置文件的位置 和監(jiān)聽器的是一樣的-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 在服務(wù)器啟動(dòng)時(shí)候創(chuàng)建對(duì)象,和容器的順序 在啟動(dòng)時(shí)裝載對(duì)象 隨意給個(gè)值要求大于等于0 數(shù)值越小,創(chuàng)建的越早-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- url-pattern 作用:把一些請(qǐng)求交給servlet處理 就例如將/mymvc交給springmvc處理
使用中央調(diào)度器(DispatcherServlet) 1.使用擴(kuò)展名方式,格式/*.xxx 例如:xxx.xml表示以xml結(jié)尾的都算
-->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>第一個(gè)springmvc</title>
</head>
<body>
<a href="some.do">發(fā)起一個(gè)som.do的請(qǐng)求</a>
</body>
</html>
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/** @Controller: 創(chuàng)建控制器(處理器)對(duì)象
* 控制器:叫做后端控制器(back controller),自定義的類處理請(qǐng)求的。
* 位置:在類的上面,表示創(chuàng)建此類的對(duì)象,對(duì)象放在springmvc的容器中
*
*/
@Controller
public class MyController {
/*
Springmvc框架使用 ,使用控制器類中的方法,處理請(qǐng)求
方法的特點(diǎn): 1.方法的形參,表示請(qǐng)求中的參數(shù) 2.方法的返回值,表示本次請(qǐng)求的處理請(qǐng)求
*/
/**
* @RequestMapping :請(qǐng)求映射
* 屬性:value 請(qǐng)求中的uri地址,唯一值,以"/"開頭
* 位置:1.在方法上面(必須) 2.在類定義的上面(可選)
* 作用:指定的請(qǐng)求,交給指定的方法處理,等同于url-pattern(個(gè)人理解 相當(dāng)于可以做doget相關(guān)的操作)
* 返回值ModelAndView:表示本次請(qǐng)求的處理結(jié)果(數(shù)據(jù)和視圖) model:表示數(shù)據(jù) view:表示視圖
*/
//可以在一個(gè)類中定義多個(gè)方法使用多個(gè)@RequestMapping注解
@RequestMapping(value={"/some.do","/first.do"}) //value是一個(gè)數(shù)組,可以有多個(gè)值,相當(dāng)于將該方法起一個(gè)名字
public ModelAndView doSome(){ //doGet()
//使用這個(gè)方法處理請(qǐng)求,能夠處理請(qǐng)求的方法叫做控制器方法
//調(diào)用service對(duì)象,處理請(qǐng)求,返回?cái)?shù)據(jù)
ModelAndView mv=new ModelAndView();
//添加數(shù)據(jù)
mv.addObject("msg","在ModelAddView中處理了some.do的請(qǐng)求");
mv.addObject("fun","執(zhí)行了dosome的方法");
//指定視圖,setviewName("視圖路徑") 相當(dāng)于請(qǐng)求轉(zhuǎn)發(fā)request.getRequestDis...("/show.jsp").forward(..)
// mv.setViewName("/WEB-INF/view/show.jsp");
//當(dāng)配置了視圖解析器,使用文件名稱作為視圖名使用,叫做視圖邏輯名稱
//使用了邏輯名稱,框架使用配置文件中視圖解析器的前綴和后綴,拼接為完整地視圖路徑 ,例如/WEB-INF/view/ + show + .jsp
mv.setViewName("show");
/*
當(dāng)框架調(diào)用完dosome方法后,得到返回中modelandview 框架會(huì)在后續(xù)的處理邏輯值,處理mv對(duì)象里的數(shù)據(jù)和視圖
對(duì)數(shù)據(jù)執(zhí)行requert,setAttribute(“msg”,“處理了some.do請(qǐng)求”);把數(shù)據(jù)放到request作用域中
對(duì)視圖進(jìn)行轉(zhuǎn)發(fā)操作
*/
return mv;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
/show.jsp,顯示request作用域中的數(shù)據(jù)<br>
<h2>msg數(shù)據(jù):<%=request.getAttribute("msg")%></h2>
<h2>fun數(shù)據(jù):${fun}</h2>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--spring的配置文件 聲明組件掃描器-->
<context:component-scan base-package="com.aiowang.controller"/>
<!-- 聲明視圖解析器;幫助處理視圖 主要幫助我們處理重復(fù)的多余的冗余路徑等-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前綴:指定試圖文件的路徑-->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 后綴,試圖文件的擴(kuò)展名-->
<property name="suffix" value=".jsp"/> <!--表示所有的jsp文件-->
</bean>
</beans>
(1)Tomcat9.0下載 https://tomcat.apache.org/download-90.cgi
(2)IDEA配置Tomcat
打開配置選項(xiàng)
找到左側(cè)Tomcat圖標(biāo),新建,選擇下載好并解壓的Tomcat路徑
部署
正常運(yùn)行,成功
(1) 導(dǎo)入依賴
junit是測(cè)試用的
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
(2)基本使用
新建一個(gè)測(cè)試類HelloLog.java
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
public class LogTest {
//日志對(duì)象
private Log log=LogFactory.getLog(LogTest.class);
@Test
public void test1(){
log.trace("hello trace!");
log.debug("hello debug");
log.info("hello info");
log.warn("hello warn");
log.error("hello error");
log.fatal("hello fatal");
}
}
(3)新建log4j配置文件
寫好了測(cè)試類運(yùn)行發(fā)現(xiàn)沒有打印 因?yàn)榕渲米芳悠鳎芳悠?<appender>的意思是我們的日志要輸出到哪里 寫一個(gè)log4j的配置文件 新建log4j.xml,配置信息如下
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE log4j:configuration PUBLIC "-//LOGGER"
"http://org/apache/log4j/xml/log4j.dtd">
<log4j:configuration>
<!--org.apache.log4j.ConsoleAppender 輸出到控制臺(tái)-->
<appender name="myConsole" class="org.apache.log4j.ConsoleAppender">
<!--輸出格式-->
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n"/>
<!--%-d{yyyy-MM-dd HH:mm:ss,SSS}是當(dāng)前時(shí)間
[%c]是日志出現(xiàn)的包和類 %p是日志的級(jí)別 %m是message也就是日志的消息,%n是換行符 -->
</layout>
</appender>
<!-- 輸出到文件H:/log/hello.log中-->
<appender name="myFile1" class="org.apache.log4j.RollingFileAppender">
<param name="File" value="D:/log/hello.log"/><!--文件位置-->
<param name="Append" value="true"/><!--是否選中追加-->
<param name="MaxFileSize" value="1kb"/><!--文件最大字節(jié)數(shù)-->
<param name="MaxBackupIndex" value="2"/>
<!--第一個(gè)文件超出上面設(shè)置的文件最大字節(jié)數(shù)后,
可以新增的新文件數(shù)量,這里只能新增2個(gè),
當(dāng)日志文件要輸出的內(nèi)容超出3個(gè)1kb(第一個(gè)加上新增的兩個(gè)),則覆蓋新增的第一個(gè)文件,再覆蓋第二個(gè)文件-->
<!--日志的輸出格式-->
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n"/>
<!--%-d{yyyy-MM-dd HH:mm:ss,SSS}是輸出當(dāng)前時(shí)間
[%c]是輸出日志出現(xiàn)的包和類 %p是日志的級(jí)別 %m是message也就是日志的消息,%n是換行符 -->
</layout>
</appender>
<!-- 輸出到文件,每天輸出一個(gè)文件-->
<appender name="myFile2" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="h:/log/world.log"/>
<param name="Append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss,SSS} [%c]-[%p] %m%n"/>
</layout>
</appender>
<root>
<!--優(yōu)先級(jí)設(shè)置,all < trace < debug < info < warn < error < fatal < off-->
<priority value="all"/>
<appender-ref ref="myConsole"/>
<appender-ref ref="myFile1"/>
<appender-ref ref="myFile2"/>
</root>
</log4j:configuration>
(4)日志輸出格式
Spring MVC 框架中的常用注解主要包括在控制器層(Controller)、服務(wù)層(Service)、數(shù)據(jù)訪問層(Repository)、實(shí)體類(Entity)、請(qǐng)求參數(shù)(Request Parameters)等方面。以下是這些注解的主要含義和用例 @Autowired、@ComponentScan、@Configuration 和 @Bean 是 Spring 框架中常用的注解,用于實(shí)現(xiàn)依賴注入和配置管理。
1、@Controller: 含義: 標(biāo)識(shí)一個(gè)類為 Spring MVC 控制器。 用例:
@Controller
public class MyController {
// Controller methods
}
2、@RequestMapping: 含義: 映射 HTTP 請(qǐng)求的 URL 到一個(gè)具體的處理方法。 用例:
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/hello")
public String hello() {
return "hello";
}
}
3、@RequestParam: 含義: 用于提取請(qǐng)求中的參數(shù)值。 客戶端發(fā)送請(qǐng)求 /example/greet?name=John 用例:
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/greet")
public String greet(@RequestParam("name") String name) {
return "Hello, " + name + "!";
}
}
4、@PathVariable: 含義: 用于將 URI 模板變量映射到處理方法的參數(shù)。 客戶端發(fā)送請(qǐng)求 /example/user/123 用例:
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/user/{id}")
public String getUserById(@PathVariable("id") Long userId) {
// Retrieve user with the specified ID
return "userDetails";
}
}
5、@PatchMapping: 含義:用于映射PATCH請(qǐng)求到控制器方法。@PatchMapping是一個(gè)用于映射HTTP PATCH請(qǐng)求到控制器方法的注解,在SpringMVC中也可以使用。它可以用于方法級(jí)別,用于指定處理PATCH請(qǐng)求的方法。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@PatchMapping("/{id}")
public String updateUser(@PathVariable Long id, @RequestBody User user) {
// ...
}
}
1、@GetMapping: (查詢) 含義:處理 HTTP GET 請(qǐng)求。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable Long id) {
// ...
}
}
2、@PostMapping: (新增) 含義:處理 HTTP POST 請(qǐng)求。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@PostMapping
public String createUser(@ModelAttribute User user) {
// ...
}
}
3、@PutMapping:(更新) 含義:處理 HTTP PUT 請(qǐng)求。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@PutMapping("/{id}")
public String updateUser(@PathVariable Long id, @ModelAttribute User user) {
// ...
}
}
4、@DeleteMapping:(刪除) 含義:處理 HTTP DELETE 請(qǐng)求。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
// ...
}
}
5、@PatchMapping: 含義:處理 HTTP PATCH 請(qǐng)求。 用例:
@Controller
@RequestMapping("/users")
public class UserController {
@PatchMapping("/{id}")
public String updateUser(@PathVariable Long id, @RequestBody User user) {
// ...
}
}
1、@Service: 含義: 標(biāo)識(shí)一個(gè)類為服務(wù)層的組件。 用例:
@Service
public class MyService {
// Service methods
}
@Service 是 Spring Framework 中的一個(gè)注解,用于標(biāo)識(shí)一個(gè)類為服務(wù)層(Service Layer)的組件。服務(wù)層通常包含應(yīng)用程序的業(yè)務(wù)邏輯,負(fù)責(zé)處理業(yè)務(wù)規(guī)則、調(diào)用數(shù)據(jù)訪問層(Repository 或 DAO)執(zhí)行數(shù)據(jù)庫(kù)操作,并協(xié)調(diào)應(yīng)用程序的不同部分
組件掃描: @Service 是 Spring 的組件掃描機(jī)制的一部分,標(biāo)識(shí)帶有該注解的類為一個(gè)服務(wù)層組件。在應(yīng)用程序啟動(dòng)時(shí),Spring 會(huì)掃描包路徑下的所有組件,并注冊(cè)為 Spring 容器中的 Bean。
依賴注入: 通過將 @Service 注解添加到類上,Spring IoC 容器會(huì)自動(dòng)將該類的實(shí)例注入到其他需要依賴的組件中,例如控制器(Controller)或其他服務(wù)層組件。
事務(wù)管理: 在服務(wù)層執(zhí)行的方法通常涉及數(shù)據(jù)庫(kù)操作,@Service 注解通常與 @Transactional 注解一起使用,以啟用事務(wù)管理。這確保了在業(yè)務(wù)方法中的一系列操作要么全部成功,要么全部失敗(回滾)。
用例:
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
public String performBusinessLogic() {
// Business logic implementation
return "Business logic executed successfully";
}
public List<MyEntity> getAllEntities() {
return myRepository.findAll();
}
}
1、@Repository: 含義: 標(biāo)識(shí)一個(gè)類為數(shù)據(jù)訪問層的組件,通常與 Spring 的數(shù)據(jù)訪問異常轉(zhuǎn)換一起使用。 用例:
@Repository
public class MyRepository {
// Repository methods
}
1、@Entity: 含義: 標(biāo)識(shí)一個(gè)類為 JPA 實(shí)體類。 用例:
@Entity
public class User {
// Entity properties and methods
}
@Entity 注解是 Java Persistence API (JPA) 的一部分,用于標(biāo)識(shí)一個(gè)類為 JPA 實(shí)體類。JPA 是一種規(guī)范,用于描述如何通過 Java 對(duì)象與關(guān)系型數(shù)據(jù)庫(kù)進(jìn)行映射。@Entity 注解告訴 JPA,被注解的類將映射到數(shù)據(jù)庫(kù)中的一個(gè)表。
數(shù)據(jù)庫(kù)映射: @Entity 注解告訴 JPA 這個(gè)類與數(shù)據(jù)庫(kù)中的表存在映射關(guān)系。類中的字段(成員變量)通常與表中的列相對(duì)應(yīng)。
主鍵標(biāo)識(shí): 實(shí)體類通常需要一個(gè)主鍵,用于唯一標(biāo)識(shí)每個(gè)實(shí)體對(duì)象。通過 @Entity 注解,JPA 可以識(shí)別實(shí)體類中的主鍵。
實(shí)體類識(shí)別: 當(dāng)應(yīng)用程序使用 JPA 進(jìn)行持久化操作時(shí),JPA 需要知道哪些類是實(shí)體類。@Entity 注解是 JPA 識(shí)別實(shí)體類的標(biāo)志。
持久性操作: 通過實(shí)體類,可以執(zhí)行 CRUD(Create, Read, Update, Delete)操作。JPA 提供了 EntityManager 接口,可以用于執(zhí)行這些操作。
關(guān)系映射: 實(shí)體類之間的關(guān)系可以通過 JPA 進(jìn)行映射,包括一對(duì)一、一對(duì)多、多對(duì)一、多對(duì)多等關(guān)系。
示例:
@Entity
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name="username")
private String username;
@Column(name="email")
private String email;
// Getters and setters
}
1、@RequestBody: 含義: 用于將 HTTP 請(qǐng)求的正文映射到方法參數(shù)。 用例:
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/processJson")
public String processJson(@RequestBody MyJsonModel jsonModel) {
// Process JSON data
return "result";
}
}
2、@ResponseBody: 含義: 表示方法的返回值直接作為響應(yīng)體,而不是視圖名稱。 用例:
@Controller
@RequestMapping("/example")
public class MyController {
@RequestMapping("/getJson")
@ResponseBody
public MyJsonModel getJson() {
// Return JSON data directly
}
}
含義: 用于自動(dòng)裝配,將指定類型的 Bean 注入到屬性、構(gòu)造函數(shù)或方法參數(shù)中。 用例:
@Service
public class MyService {
private final MyRepository repository;
@Autowired
public MyService(MyRepository repository) {
this.repository=repository;
}
}
在上例中,MyService 類通過 @Autowired 注解將 MyRepository 類型的 Bean 自動(dòng)注入到構(gòu)造函數(shù)中。
含義: 掃描指定包路徑,尋找標(biāo)有 @Component、@Service、@Repository、@Controller 注解的類,并將其注冊(cè)為 Spring Bean。 用例:
@Configuration
@ComponentScan(basePackages="com.example")
public class AppConfig {
// Configuration content
}
在上例中,@ComponentScan 注解掃描 com.example 包路徑下的所有類,將帶有相應(yīng)注解的類注冊(cè)為 Spring Bean。
含義: 聲明當(dāng)前類是一個(gè)配置類,通常與 @Bean 注解一起使用,用于配置 Spring 應(yīng)用上下文。 用例:
@Configuration
public class AppConfig {
// Bean declarations using @Bean
}
在上例中,AppConfig 被聲明為配置類,用于定義 Spring Bean。
含義: 在配置類中使用,用于聲明一個(gè) Bean。 用例:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService(myRepository());
}
@Bean
public MyRepository myRepository() {
return new MyRepository();
}
}
在上例中,@Bean 注解用于聲明兩個(gè) Bean:MyService 和 MyRepository。
springmvc靜態(tài)資源配置
在javaweb項(xiàng)目中配置了DispatcherServlet的情況下,如果不進(jìn)行額外配置的話,幾乎所有的請(qǐng)求都會(huì)走這個(gè)servlet來處理,默認(rèn)靜態(tài)資源按路徑是訪問不到的會(huì)報(bào)404錯(cuò)誤,下面講一講如何配置才能訪問到靜態(tài)資源,本文將介紹三種方法
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<async-supported>false</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
@Configuration
@EnableWebMvc
public class MyMvcConfigurer implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// tomcat默認(rèn)處理靜態(tài)資源的servlet名稱為default,不指定也可以DefaultServletHttpRequestHandler.setServletContext會(huì)自動(dòng)獲取
// configurer.enable("default");
configurer.enable();
}
}
上述配置完成后org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#defaultServletHandlerMapping 方法會(huì)生成一個(gè)類名為SimpleUrlHandlerMapping的bean,當(dāng)其他handlerMapping無法處理請(qǐng)求時(shí)會(huì)接著調(diào)用SimpleUrlHandlerMapping對(duì)象進(jìn)行處理。SimpleUrlHandlerMapping中有一個(gè)urlMap屬性,key為請(qǐng)求路徑匹配模式串,/**能匹配所有的路徑, value為handler匹配完成后會(huì)調(diào)用handler處理請(qǐng)求。 接著調(diào)用DefaultServletHttpRequestHandler的handleRequest方法處理請(qǐng)求,邏輯比較簡(jiǎn)單,獲取請(qǐng)求轉(zhuǎn)發(fā)器進(jìn)行請(qǐng)求轉(zhuǎn)發(fā)交給tomcat默認(rèn)的servlet來進(jìn)行處理。
@Configuration
@EnableWebMvc
public class MyMvcConfigurer implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}
}
和第一種配置幾乎一樣,其實(shí)只是換了一個(gè)handler類型來處理請(qǐng)求罷了。
上述配置完成后org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#resourceHandlerMapping 方法會(huì)生成一個(gè)類名為SimpleUrlHandlerMapping的bean,當(dāng)其他handlerMapping無法處理請(qǐng)求時(shí)會(huì)接著調(diào)用SimpleUrlHandlerMapping對(duì)象進(jìn)行處理
ResourceHttpRequestHandler比DefaultServletHttpRequestHandler的構(gòu)建稍微復(fù)雜一點(diǎn)。之后也是調(diào)用SimpleUrlHandlerMapping相同的邏輯先根據(jù)請(qǐng)求路徑匹配找到對(duì)應(yīng)處理的handler,這里對(duì)應(yīng)的是ResourceHttpRequestHandler之后調(diào)用handleRequest方法,原理是先根據(jù)請(qǐng)求的路徑找到對(duì)應(yīng)的資源文件,再獲取資源文件的輸入流寫入到response響應(yīng)中。
就是利用容器自身的默認(rèn)Servlet, 以Tomcat為例,如下圖有一個(gè)默認(rèn)的Servlet,名稱就是default(也可以在tomcat的配置文件中修改為其他名稱,是在tomcat的目錄/conf/web.xml中配置的)。
我們只需要在web項(xiàng)目的web.xml中配置靜態(tài)文件是由此Servlet來映射即可。 default是容器的默認(rèn)servlet的名稱,示例為tomcat容器,其他容器根據(jù)實(shí)際情況來,如果tomcat配置文件修改了默認(rèn)servlet名稱,則也要修改為實(shí)際的。
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/static/*</url-pattern>
</servlet-mapping>
將帶有/static/xxx 路徑的請(qǐng)求直接交給tomcat默認(rèn)的servlet去進(jìn)行處理
<mvc:resources mapping="/images/**" location="/images/" />
<mvc:resources mapping="/js/**" location="/js/" />
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources location="/,classpath:/META-INF/publicResources/" mapping="/resources/**"/>
因?yàn)樯厦娴膌ocation屬性節(jié)點(diǎn)是Resource資源, 因此可以使用classpath這類寫法。 <mvc:resources />更進(jìn)一步,由Spring MVC框架自己處理靜態(tài)資源,并添加一些有用的附加值功能。
首先,<mvc:resources />允許靜態(tài)資源放在任何地方,如WEB-INF目錄下、類路徑下等,你甚至可以將JavaScript等靜態(tài)文件打到JAR包中。通過location屬性指定靜態(tài)資源的位置,由于location屬性是Resources類型,因此可以使用諸如"classpath:"等的資源前綴指定資源位置。傳統(tǒng)Web容器的靜態(tài)資源只能放在Web容器的根路徑下,<mvc:resources />完全打破了這個(gè)限制。
其次,<mvc:resources />依據(jù)當(dāng)前著名的Page Speed、YSlow等瀏覽器優(yōu)化原則對(duì)靜態(tài)資源提供優(yōu)化。你可以通過cacheSeconds屬性指定靜態(tài)資源在瀏覽器端的緩存時(shí)間,一般可將該時(shí)間設(shè)置為一年,以充分利用瀏覽器端的緩存。在輸出靜態(tài)資源時(shí),會(huì)根據(jù)配置設(shè)置好響應(yīng)報(bào)文頭的Expires 和 Cache-Control值。
在接收到靜態(tài)資源的獲取請(qǐng)求時(shí),會(huì)檢查請(qǐng)求頭的Last-Modified值,如果靜態(tài)資源沒有發(fā)生變化,則直接返回303相應(yīng)狀態(tài)碼,提示客戶端使用瀏覽器緩存的數(shù)據(jù),而非將靜態(tài)資源的內(nèi)容輸出到客戶端,以充分節(jié)省帶寬,提高程序性能。
第一種方式是要定義的Interceptor類要實(shí)現(xiàn)Spring的HandlerInterceptor 接口
第二種方式是繼承實(shí)現(xiàn)了抽象類HandlerInterceptorAdapter
public class UserInterceptor implements HandlerInterceptor{
/**
* 該方法在整個(gè)請(qǐng)求完成后執(zhí)行,主要用來清理資源
* 該方法只能在當(dāng)前interceptor的preHandler方法的返回值是true時(shí)才會(huì)執(zhí)行
*/
@Override
public void afterCompletion(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
}
/**
* 該方法在Controller的方法調(diào)用后執(zhí)行,在視圖被渲染以前被調(diào)用,所以可以用來對(duì)ModelAndView對(duì)象進(jìn)行操作
* 該方法只能在當(dāng)前interceptor的preHandler方法的返回值是true時(shí)才會(huì)執(zhí)行
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2, ModelAndView arg3) throws Exception {
}
/**
* 該方法在請(qǐng)求之前被調(diào)用
* 該方法返回為true時(shí)攔截器才會(huì)繼續(xù)往下執(zhí)行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
//用于判斷用戶是否登錄
boolean flag=false;
User user=(User) request.getSession().getAttribute("user");
if(user==null){
request.setAttribute("message", "請(qǐng)先登錄");
request.getRequestDispatcher("loginPage.jsp").forward(request, response);
}else{
flag=true;
}
return flag;
}
}
<mvc:mapping path=""/>配置攔截路徑
<mvc:exclude-mapping path=""/>配置不進(jìn)行攔截的路徑。
<!-- 配置攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 攔截路徑 -->
<mvc:mapping path="/*"/>
<!-- 不攔截的路徑 -->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/loginPage"/>
<bean class="com.dj.interceptor.UserInterceptor"></bean>
</mvc:interceptor>
<!-- 當(dāng)設(shè)置多個(gè)攔截器時(shí),先按**順序調(diào)用preHandle方法**,然后**逆序調(diào)用**每個(gè)攔截器的postHandle和afterCompletion方法 -->
</mvc:interceptors>
@Controller
public class UserController {
@RequestMapping(value="/{pagename}")
public String pageName(@PathVariable String pagename){
return pagename;
}
@RequestMapping("login")
public ModelAndView login(String username,String password
,ModelAndView mv,HttpSession session){
if(username!=null&&username.equals("aaa")&&password!=null&&password.equals("111")){
User user=new User();
user.setUsername(username);
user.setPassword(password);
session.setAttribute("user", user);
mv.setViewName("success");
}else{
mv.addObject("message", "賬號(hào)或密碼錯(cuò)誤");
mv.setViewName("loginPage");
}
return mv;
}
@RequestMapping("success")
public String success(){
return "success";
}
}
<form action="login" method="post">
<!-- 提示信息 -->
<font color="red">${requestScope.message }</font><br>
用戶名:<input type="text" name="username" /><br>
密碼:<input type="password" name="password"/>
<input type="submit" value="登錄"/>
</form>
<body> 登陸成功! </body>
直接訪問success頁(yè)面被攔截
訪問登錄頁(yè)面,因?yàn)榕渲昧瞬贿M(jìn)行攔截的路徑,所以顯示如下
輸入賬號(hào)密碼登錄成功
對(duì)于@ControllerAdvice,我們比較熟知的用法是結(jié)合@ExceptionHandler用于全局異常的處理,但其作用不僅限于此。ControllerAdvice拆分開來就是Controller Advice,關(guān)于Advice,前面我們講解Spring Aop時(shí)講到,其是用于封裝一個(gè)切面所有屬性的,包括切入點(diǎn)和需要織入的切面邏輯。這里ContrllerAdvice也可以這么理解,其抽象級(jí)別應(yīng)該是用于對(duì)Controller進(jìn)行“切面”環(huán)繞的,而具體的業(yè)務(wù)織入方式則是通過結(jié)合其他的注解來實(shí)現(xiàn)的。@ControllerAdvice是在類上聲明的注解,其用法主要有三點(diǎn):
在Spring MVC進(jìn)行調(diào)用的過程中,會(huì)有很多的特殊的需求。比如全局異常,分頁(yè)信息和分頁(yè)搜索條件,請(qǐng)求時(shí)帶來返回時(shí)還得回顯頁(yè)面。
Spring提供@ControllerAdvice對(duì)需要處理的范圍進(jìn)行配置。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
// 控制的掃描包范圍
@AliasFor("basePackages")
String[] value() default {};
// 控制的掃描包范圍
@AliasFor("value")
String[] basePackages() default {};
// 控制的包類
Class<?>[] basePackageClasses() default {};
// @Controller或者@RestController的類 的數(shù)據(jù)
Class<?>[] assignableTypes() default {};
// 控制范圍可以用注解進(jìn)行配置
Class<? extends Annotation>[] annotations() default {};
}
從上面的講解可以看出,@ControllerAdvice的用法基本是將其聲明在某個(gè)bean上,然后在該bean的方法上使用其他的注解來指定不同的織入邏輯。不過這里@ControllerAdvice并不是使用AOP的方式來織入業(yè)務(wù)邏輯的,而是Spring內(nèi)置對(duì)其各個(gè)邏輯的織入方式進(jìn)行了內(nèi)置支持。本文將對(duì)@ControllerAdvice的這三種使用方式分別進(jìn)行講解。
系統(tǒng)比較龐大時(shí)很多的異常是不能控制,或者未知的,不能將所有的sql異常,反射異常,類不存在等拋到頁(yè)面上展示給用戶。
則需要一個(gè)全局的攔截器處理,Spring 提供了@ExceptionHandler處理方式。
1)、全局異常處理定義
@ControllerAdvice(basePackages="com.kevin.tool")
public class ExceptionHandlerController {
/**
* 錯(cuò)誤后返回json
* 如果想跳轉(zhuǎn)到專門的異常界面,則可以返回{@link org.springframework.web.servlet.ModelAndView}
*
* @return 標(biāo)準(zhǔn)異常json
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public Map<String, String> handler() {
Map<String, String> errorMap=new HashMap<String, String>(16);
errorMap.put("code", "500");
errorMap.put("msg", "系統(tǒng)異常,請(qǐng)稍后重試");
return errorMap;
}
}
2)、控制器方法調(diào)用異常
@RestController
public class ControllerAdviceDemoController {
@ResponseBody
@RequestMapping("bindException")
public String bindException() {
getMessage();
return "ok";
}
private void getMessage() {
throw new RuntimeException("未知異常!");
}
}
3)、訪問效果
@InitBinder從字面意思可以看出這個(gè)的作用是給Binder做初始化的,@InitBinder主要用在@Controller中標(biāo)注于方法上(@RestController也算),表示初始化當(dāng)前控制器的數(shù)據(jù)綁定器(或者屬性綁定器),只對(duì)當(dāng)前的Controller有效。@InitBinder標(biāo)注的方法必須有一個(gè)參數(shù)WebDataBinder。所謂的屬性編輯器可以理解就是幫助我們完成參數(shù)綁定,然后是在請(qǐng)求到達(dá)controller要執(zhí)行方法前執(zhí)行!
數(shù)據(jù)綁定有很多的場(chǎng)景,當(dāng)前比如前端傳入的日期為字符串類型,后端按照Format進(jìn)解析為日期。
1)、全局日期綁定定義
@ControllerAdvice(basePackages="com.kevin.tool")
public class ExceptionHandlerController {
@InitBinder("date")
public void globalInitBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
}
2)、控制器方法調(diào)用日期轉(zhuǎn)換
@RestController
public class ControllerAdviceDemoController {
@ResponseBody
@RequestMapping(value="/initBind", method=RequestMethod.GET)
public String detail(@RequestParam("id") long id, Date date) {
System.out.println(date);
System.out.println(id);
return "ok";
}
}
3)、收到的日期類型效果
訪問地址為://127.0.0.1:9999/initBind?id=123&date=2019-12-30
先看看@ModelAttribute的注解信息,元注解@Target指定可以修飾方法參數(shù)和方法(全局)。當(dāng)前模擬一種常見,就是將所有輸出的信息都加上當(dāng)前的平臺(tái)信息(比如版本等公共信息,這種需求還是比較多的)。
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean binding() default true;
}
1)、全局返回屬性添加
@ControllerAdvice(basePackages="com.kevin.tool")
public class ExceptionHandlerController {
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("msg", "hello");
HashMap<String, String> map=new HashMap<>(16);
map.put("version", "1.0.0");
map.put("name", "XXX平臺(tái)");
model.addAttribute("platform", map);
}
}
2)、控制器方法訪問
@RestController
public class ControllerAdviceDemoController {
@GetMapping("/modelAttributeTest")
private String modelAttributeTest(@ModelAttribute("msg") String msg,
@ModelAttribute("platform") Map<String, String> platform) {
String result="msg:" + msg + "<br>" + "info:" + platform;
return result;
}
}
3)、輸出效果
在控制器方法中只需要實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)(只設(shè)置頁(yè)面視圖名稱)功能而沒有其他業(yè)務(wù),此時(shí)可以在SpringMvc的配置文件中使用view-controller標(biāo)簽表示控制器方法
在SpringMvC的核心配置文件中使用視圖控制器標(biāo)簽跳轉(zhuǎn)到首頁(yè)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--自動(dòng)掃描控制層組件-->
<context:component-scan base-package="com.atguigu.mvc.controller"></context:component-scan>
<!--配置Thymeleaf視圖解析器-->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
</bean>
<!--視圖控制器,設(shè)置請(qǐng)求對(duì)應(yīng)的視圖名稱實(shí)現(xiàn)頁(yè)面的跳轉(zhuǎn)-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--開啟MVC的注解驅(qū)動(dòng),保證視圖控制器設(shè)置的請(qǐng)求和控制器方法設(shè)置的請(qǐng)求全部都會(huì)被前端控制器處理-->
<mvc:annotation-driven />
</beans>
請(qǐng)求路徑path對(duì)應(yīng)的視圖名稱是view-name,即請(qǐng)求路徑/對(duì)應(yīng)的視圖名稱是index。 此時(shí),可以不需要控制器方法。
通過@RequestMapping注解: 匹配路徑與處理器
@RequestMapping注解用于建立請(qǐng)求URL路徑和處理器之間的對(duì)應(yīng)關(guān)系.
出現(xiàn)位置: 可以出現(xiàn)在類上,也可以出現(xiàn)在方法上.
當(dāng)它既出現(xiàn)在類上也出現(xiàn)在方法上時(shí),類上注解值為請(qǐng)求URL的一級(jí)目錄,方法上注解值為請(qǐng)求URL的二級(jí)目錄 當(dāng)它只出現(xiàn)在方法上時(shí),該注解值為請(qǐng)求URL的一級(jí)目錄 其屬性如下:
path: value屬性的別名,指定請(qǐng)求的URL,支持Ant風(fēng)格表達(dá)式,通配符如下:
通配符 | 說明 |
? | 匹配文件(路徑)名中的一個(gè)字符 |
* | 匹配文件(路徑)名中的任意數(shù)量(包括0個(gè))的字符 |
** | 匹配任意數(shù)量(包括0個(gè))的路徑 |
例如
路徑/project/*.a匹配項(xiàng)目根路徑下所有在/project路徑下的.a文件
路徑/project/p?ttern匹配項(xiàng)目根路徑下的/project/pattern和/project/pXttern,但不能匹配/project/pttern
路徑/**/example匹配項(xiàng)目根路徑下的/project/example,/project/foo/example,和/example
路徑/project/**/dir/file.*匹配項(xiàng)目根路徑下的/project/dir/file.jsp,/project/foo/dir/file.html,/project/foo/bar/dir/file.pdf
路徑/**/*.jsp匹配項(xiàng)目根路徑下的所有jsp文件
另外,遵循最長(zhǎng)匹配原則,若URL請(qǐng)求了/project/dir/file.jsp,現(xiàn)在存在兩個(gè)匹配模式:/**/*.jsp和/project/dir/*.jsp,那么會(huì)根據(jù)/project/dir/*.jsp來匹配.
@RequestMapping(params={"param1"}),表示請(qǐng)求參數(shù)中param1必須出現(xiàn)
@RequestMapping(params={"!param1"}),表示請(qǐng)求參數(shù)中param1不能出現(xiàn)
@RequestMapping(params={"param1=value1"}),表示請(qǐng)求參數(shù)中param1必須出現(xiàn)且為value1
@RequestMapping(params={"param1!value1"}),表示請(qǐng)求參數(shù)中param1必須出現(xiàn)且不為value1
多個(gè)值之間是與的關(guān)系
WebMvcConfigurerAdapter配置類是spring提供的一種配置方式,采用JavaBean的方式替代傳統(tǒng)的基于xml的配置來對(duì)spring框架進(jìn)行自定義的配置。因此,在spring boot提倡的基于注解的配置,采用“約定大于配置”的風(fēng)格下,當(dāng)需要進(jìn)行自定義的配置時(shí),便可以繼承WebMvcConfigurerAdapter這個(gè)抽象類,通過JavaBean來實(shí)現(xiàn)需要的配置。
WebMvcConfigurerAdapter是一個(gè)抽象類,它只提供了一些空的接口讓用戶去重寫,比如如果想添加攔截器的時(shí)候,需要去重寫一下addInterceptors()這個(gè)方法,去配置自定義的攔截器。我們可以看一下WebMvcConfigurerAdapter提供了哪些接口來供我們使用。
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
/*配置路徑匹配參數(shù)*/
public void configurePathMatch(PathMatchConfigurer configurer) {}
/*配置Web Service或REST API設(shè)計(jì)中內(nèi)容協(xié)商,即根據(jù)客戶端的支持內(nèi)容格式情況來封裝響應(yīng)消息體,如xml,json*/
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
/*配置路徑匹配參數(shù)*/
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
/* 使得springmvc在接口層支持異步*/
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
/* 注冊(cè)參數(shù)轉(zhuǎn)換和格式化器*/
public void addFormatters(FormatterRegistry registry) {}
/* 注冊(cè)配置的攔截器*/
public void addInterceptors(InterceptorRegistry registry) {}
/* 自定義靜態(tài)資源映射*/
public void addResourceHandlers(ResourceHandlerRegistry registry) {}
/* cors跨域訪問*/
public void addCorsMappings(CorsRegistry registry) {}
/* 配置頁(yè)面直接訪問,不走接口*/
public void addViewControllers(ViewControllerRegistry registry) {}
/* 注冊(cè)自定義的視圖解析器*/
public void configureViewResolvers(ViewResolverRegistry registry) {}
/* 注冊(cè)自定義控制器(controller)方法參數(shù)類型*/
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {}
/* 注冊(cè)自定義控制器(controller)方法返回類型*/
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {}
/* 重載會(huì)覆蓋掉spring mvc默認(rèn)注冊(cè)的多個(gè)HttpMessageConverter*/
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
/* 僅添加一個(gè)自定義的HttpMessageConverter,不覆蓋默認(rèn)注冊(cè)的HttpMessageConverter*/
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
/* 注冊(cè)異常處理*/
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
/* 多個(gè)異常處理,可以重寫次方法指定處理順序等*/
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
}
WebMvcConfigurerAdapter提供了很多的接口供用戶去實(shí)現(xiàn)自定義的配置項(xiàng)。下面挑幾個(gè)比較重要的介紹一下如何使用這些接口來自定義配置。
(1)注冊(cè)攔截器
首先,編寫攔截器的代碼:
public class LoginInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger=LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("-----------------------------");
logger.info(request.getRequestedSessionId());
logger.info("-----------------------------");
return true;
}
}
這里只打印相關(guān)信息,然后,需要寫一個(gè)config類去配置這個(gè)攔截器:
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/*
* 攔截器配置*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
}
配置類繼承了WebMvcConfigurerAdapter這個(gè)類,并且重寫了addInterceptors這個(gè)方法,在方法中,注冊(cè)了上面編寫的攔截器,并且為此攔截器配置了攔截路徑,這樣一來就算是配置好了這個(gè)攔截器。
(2)配置CORS跨域
只需要在上面的webConfig里重寫WebMvcConfigurerAdapter的addCorsMappings方法就可以獲得基于spring的跨域支持。
/**
* 跨域CORS配置
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
super.addCorsMappings(registry);
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("POST","GET")
.allowedOrigins("http://...")
.allowCredentials(true);
}
(3)配置ViewController
當(dāng)首頁(yè)或者登陸頁(yè)的頁(yè)面對(duì)外暴露,不需要加載任何的配置的時(shí)候,這些頁(yè)面將不通過接口層,而是直接訪問,這時(shí),就需要配置ViewController指定請(qǐng)求路徑直接到頁(yè)面。
/**
* 視圖控制器配置
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
super.addViewControllers(registry);
registry.addViewController("/").setViewName("forward:/index.html");
}
(4)配置ViewResolver
通常在使用jsp的項(xiàng)目中,會(huì)基于spring mvc配置的文件去配置視圖解析器,通過重寫WebMvcConfigurerAdapter里的configureViewResolvers也可以將自己定義的InternalResourceViewResolver配置整合進(jìn)spring中。
/**
* 配置請(qǐng)求視圖映射
*
* @return
*/
@Bean
public InternalResourceViewResolver resourceViewResolver() {
InternalResourceViewResolver internalResourceViewResolver=new InternalResourceViewResolver();
//請(qǐng)求視圖文件的前綴地址
internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
//請(qǐng)求視圖文件的后綴
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
super.configureViewResolvers(registry);
registry.viewResolver(resourceViewResolver());
}
可以看一下ViewResolverRegistry中的代碼:
public UrlBasedViewResolverRegistration jsp() {
return this.jsp("/WEB-INF/", ".jsp");
}
public UrlBasedViewResolverRegistration jsp(String prefix, String suffix) {
InternalResourceViewResolver resolver=new InternalResourceViewResolver();
resolver.setPrefix(prefix);
resolver.setSuffix(suffix);
this.viewResolvers.add(resolver);
return new UrlBasedViewResolverRegistration(resolver);
}
可以看到,即使不去配置,spring也會(huì)新建一個(gè)默認(rèn)的視圖解析器。十分方便。
(5)配置Formatter
當(dāng)請(qǐng)求的參數(shù)中帶有日期的參數(shù)的時(shí)候,可以在此配置formatter使得接收到日期參數(shù)格式統(tǒng)一。
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new Formatter<Date>() {
@Override
public Date parse(String date, Locale locale) {
return new Date(Long.parseLong(date));
}
@Override
public String print(Date date, Locale locale) {
return Long.valueOf(date.getTime()).toString();
}
});
}
WebMvcConfigurer配置類其實(shí)是Spring內(nèi)部的一種配置方式,采用JavaBean的形式來代替?zhèn)鹘y(tǒng)的xml配置文件形式進(jìn)行針對(duì)框架個(gè)性化定制,可以自定義一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要?jiǎng)?chuàng)建一個(gè)配置類并實(shí)現(xiàn)WebMvcConfigurer 接口;
在Spring Boot 1.5版本都是靠重寫WebMvcConfigurerAdapter的方法來添加自定義攔截器,消息轉(zhuǎn)換器等。SpringBoot 2.0 后,該類被標(biāo)記為@Deprecated(棄用)。官方推薦直接實(shí)現(xiàn)WebMvcConfigurer或者直接繼承WebMvcConfigurationSupport,方式一實(shí)現(xiàn)WebMvcConfigurer接口(推薦),方式二繼承WebMvcConfigurationSupport類,
Spring MVC 為文件上傳提供了直接支持,這種支持是通過即插即用的 MultipartResolver 實(shí)現(xiàn)的。Spring 使用 Jakarta Commons FileUpload 技術(shù)實(shí)現(xiàn)了一個(gè) MultipartResolver 實(shí)現(xiàn)類:CommonsMultipartResolver。
在 Spring MVC 上下文中默認(rèn)沒有裝配 MultipartResolver,因此默認(rèn)情況下不能處理文件的上傳工作。如果想使用 Spring 的文件上傳功能,則需要先在上下文中配置 MultipartResolver。
下面使用 CommonsMultipartResolver 配置一個(gè) MultipartResolver 解析器。
<!-- 文件上傳 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
p:defaultEncoding="UTF-8"//①請(qǐng)求的編碼格式,默認(rèn)為ISO-8859-1
p:maxUploadSize="5000000"//②上傳文件的大小上限,單位為字節(jié)(5MB)
p:uploadTempDir="file://d:/temp"/>//③上傳文件的臨時(shí)路徑
defaultEncoding 必須和用戶 JSP 的 pageEncoding 屬性一致,以便正確讀取表單的內(nèi)容。uploadTempDir 是文件上傳過程中所使用的臨時(shí)目錄,文件上傳完成后,臨時(shí)目錄中的臨時(shí)文件會(huì)被自動(dòng)清除。
為了讓 CommonsMultipartResolver 正常工作,必須先將 Jakarta Commons FileUpload 及 Jakarta Commons io 的類包添加到類路徑下。
在 UserController 中添加一個(gè)用于處理用戶頭像上傳的方法,如下面代碼所示。
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value="/uploadPage")//①
public String updatePage() {
return "uploadPage";
}
@RequestMapping(value="/upload")
public String updateThumb(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) throws Exception{
//②上傳的文件自動(dòng)綁定到MultipartFile中
if (!file.isEmpty()) {
file.transferTo(new File("d:/temp/"+file.getOriginalFilename()));
return "redirect:success.html";
}else{
return "redirect:fail.html";
}
}
}
Spring MVC 會(huì)將上傳文件綁定到 MultipartFile 對(duì)象中。MultipartFile 提供了獲取上傳文件內(nèi)容、文件名等方法,通過其 transferTo() 方法還可將文件存儲(chǔ)到硬件中,具體說明如下。
負(fù)責(zé)上傳文件的表單和一般表單有一些區(qū)別,表單的編碼類型必須是 multipart/form-data 類型。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>請(qǐng)上傳用戶頭像</title>
</head>
<body>
<h1>
請(qǐng)選擇上傳的頭像文件
</h1>
<form method="post" action="<c:url value="/user/upload.html"/>" enctype="multipart/form-data">//指定表單內(nèi)容類型,以便支持文件上傳
<input type="text" name="name" />
<input type="file" name="file" />//②上傳文件的組件名
<input type="submit" />
</form>
</body>
</html>
在Spring MVC中,HttpMessageConverter主要用于將HTTP請(qǐng)求的輸入內(nèi)容轉(zhuǎn)換為指定的Java對(duì)象,以及將Java對(duì)象轉(zhuǎn)換為HTTP響應(yīng)的輸出內(nèi)容。這種靈活的消息轉(zhuǎn)換機(jī)制就是利用HttpMessageConverter來實(shí)現(xiàn)的。
Spring MVC提供了多個(gè)默認(rèn)的HttpMessageConverter實(shí)現(xiàn),包括處理JSON、XML、文本等格式的Converter。另外,我們也可以自定義HttpMessageConverter來處理其他格式的數(shù)據(jù)。
Spring MVC提供了兩個(gè)注解:@RequestBody和@ResponseBody,分別用于完成請(qǐng)求報(bào)文到對(duì)象和對(duì)象到響應(yīng)報(bào)文的轉(zhuǎn)換。
然而,有時(shí)候默認(rèn)的HttpMessageConverter無法滿足特定的需求,例如,當(dāng)我們需要處理的數(shù)據(jù)格式?jīng)]有默認(rèn)的Converter時(shí),或者我們需要對(duì)現(xiàn)有的Converter進(jìn)行擴(kuò)展時(shí),就需要自定義HttpMessageConverter。
自定義HttpMessageConverter可以讓我們更加靈活地控制數(shù)據(jù)轉(zhuǎn)換的過程,例如我們可以自定義轉(zhuǎn)換規(guī)則、異常處理等。
接下來我們通過一個(gè)實(shí)例講解如何自定義HttpMessageConverter。
需求
接口請(qǐng)求數(shù)據(jù)格式:
xxx|yyy|zzz|...
接口返回JSON數(shù)據(jù)格式
{
"xxx": xxx,
"yyy": yyy,
"zzz": zzz,
...
}
其實(shí)就上面的數(shù)據(jù)格式,我們完全可以不用自定義HttpMessageConverter也是完全可以實(shí)現(xiàn)的。我們這里主要就是教大家如何在特殊的需求下實(shí)現(xiàn)特定的數(shù)據(jù)轉(zhuǎn)換處理。
(1)自定義HttpMessageConverter轉(zhuǎn)換器
public class PackHttpMessageConverter implements HttpMessageConverter<Object> {
// 設(shè)置自定義的Content-Type類型,這樣就限定了只有請(qǐng)求的內(nèi)容類型是該類型才會(huì)使用該轉(zhuǎn)換器進(jìn)行處理
private static final MediaType PACK=new MediaType("application", "pack", StandardCharsets.UTF_8) ;
// 判斷當(dāng)前轉(zhuǎn)換器是否能夠讀取數(shù)據(jù)
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return PACK.equals(mediaType) ;
}
// 判斷當(dāng)前轉(zhuǎn)換器是否可以將結(jié)果數(shù)據(jù)進(jìn)行輸出到客戶端
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return true ;
}
// 返回當(dāng)前轉(zhuǎn)換器只支持application/pack類型的數(shù)據(jù)格式
@Override
public List<MediaType> getSupportedMediaTypes() {
return Arrays.asList(PACK) ;
}
// 從請(qǐng)求中讀取數(shù)據(jù)
@Override
public Object read(Class<? extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
InputStream is=inputMessage.getBody() ;
String res=IOUtils.toString(is, StandardCharsets.UTF_8) ;
// 這里簡(jiǎn)單處理只針對(duì)Users類型的對(duì)象處理
if (clazz==Users.class) {
try {
// 創(chuàng)建實(shí)例
Users target=(Users) clazz.newInstance() ;
String[] s=res.split("\\|");
target.setId(Long.valueOf(s[0])) ;
target.setName(s[1]) ;
target.setAge(Integer.valueOf(s[2])) ;
target.setIdNo(s[3]) ;
return target ;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace() ;
}
}
return null ;
}
// 將Controller方法返回值寫到客戶端
@Override
public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// 設(shè)置響應(yīng)頭為json格式
outputMessage.getHeaders().add("Content-Type", "application/json;charset=UTF-8") ;
ObjectMapper mapper=new ObjectMapper() ;
OutputStream os=outputMessage.getBody();
// 輸出結(jié)果內(nèi)容
os.write(mapper.writeValueAsString(t).getBytes(StandardCharsets.UTF_8)) ;
os.flush();
}
}
(2)將PackHttpMessageConverter注冊(cè)到容器中
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new PackHttpMessageConverter()) ;
}
}
到這里自定義HttpMessageConverter及注冊(cè)到容器中就全部完成了,開發(fā)還是比較簡(jiǎn)單,接下來做測(cè)試
(3)接口
// 方法非常簡(jiǎn)單還是用的那些常用的類,@RequestBody接收請(qǐng)求body中的內(nèi)容
@PostMapping("/i")
public Object i(@RequestBody Users user) {
System.out.println(handlerAdapter) ;
return user ;
}
(4)通過Postman測(cè)試接口
設(shè)置請(qǐng)求的header
似乎沒有任何的問題,其實(shí)你只要在寫的方法中打印下日志,或者調(diào)試下,你會(huì)發(fā)現(xiàn)你的write方法根本就沒有被調(diào)用,也就是說寫數(shù)據(jù)并沒有使用到我們自定義的實(shí)現(xiàn),這是因?yàn)橛袃?yōu)先級(jí)比我們自定義的轉(zhuǎn)換器高,所以要想讓寫消息也調(diào)用自定義的。我們需要如下修改注冊(cè)方式:
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new PackHttpMessageConverter()) ;
}
這樣我們自定義的轉(zhuǎn)換器就排到了第一的位置,這樣就會(huì)調(diào)用我們自定義的write方法。
請(qǐng)求參數(shù)由于添加了@RequestBody,所以方法的參數(shù)解析器使用的是RequestResponseBodyMethodProcessor。
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// ...
// 讀取請(qǐng)求數(shù)據(jù);調(diào)用父類方法
Object arg=readWithMessageConverters(inputMessage, parameter, paramType);
// ...
}
}
AbstractMessageConverterMethodArgumentResolver
public abstract class AbstractMessageConverterMethodArgumentResolver {
protected <T> Object readWithMessageConverters(...) {
// ...
// 遍歷所有的消息轉(zhuǎn)換器
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType=(Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter=(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
// 判斷當(dāng)前轉(zhuǎn)換器是否讀,也就上面我們自定義中實(shí)現(xiàn)的canRead方法
if (genericConverter !=null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass !=null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse=getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
// 讀取具體的數(shù)據(jù)內(nèi)容
body=(genericConverter !=null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body=getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body=getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
}
原理也比較的簡(jiǎn)單。
自定義HttpMessageConverter是Spring MVC中一個(gè)強(qiáng)大的工具,它可以幫助開發(fā)者更加靈活地控制數(shù)據(jù)轉(zhuǎn)換的過程,滿足特定的需求。
MappingJackson2HttpMessageConverter是springboot中默認(rèn)的Json消息轉(zhuǎn)換器。這個(gè)類的繼承圖如下:
這個(gè)類的主要實(shí)現(xiàn)邏輯是在AbstractJackson2HttpMessageConverter抽象類中實(shí)現(xiàn)的。這個(gè)列實(shí)現(xiàn)序列化與反序列化的最核心組件是ObjectMapper這個(gè)類。
MappingJackson2HttpMessageConverter是一個(gè)Spring消息轉(zhuǎn)換器,用于在Web應(yīng)用程序中處理請(qǐng)求和響應(yīng)內(nèi)容。它的工作原理如下:
MappingJackson2HttpMessageConverter通過實(shí)現(xiàn)HttpMessageConverter接口并重寫相關(guān)方法,完成請(qǐng)求和響應(yīng)內(nèi)容的轉(zhuǎn)換。當(dāng)Spring處理請(qǐng)求或生成響應(yīng)時(shí),它會(huì)自動(dòng)選擇合適的消息轉(zhuǎn)換器,并使用它來處理請(qǐng)求和響應(yīng)內(nèi)容。
MappingJackson2HttpMessageConverter如何將請(qǐng)求內(nèi)容轉(zhuǎn)換為Java對(duì)象和響應(yīng)內(nèi)容轉(zhuǎn)換為JSON
(1)簡(jiǎn)單使用:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
}
@Bean
public ObjectMapper objectMapper() {
return new Jackson2ObjectMapperBuilder()
.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
.featuresToEnable(SerializationFeature.INDENT_OUTPUT)
.build();
}
}
這樣,在控制器方法中使用@RequestBody或@ResponseBody注解時(shí),就可以通過MappingJackson2HttpMessageConverter進(jìn)行序列化/反序列化操作了。
(2)自定義MappingJackson2HttpMessageConverter
將時(shí)間戳序列化為L(zhǎng)ocalDateTime,將LocalDateTime反序列化為時(shí)間戳
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
ObjectMapper mapper=new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 時(shí)間對(duì)象自定義格式化
JavaTimeModule javaTimeModule=new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
long timestamp=Long.parseLong(jsonParser.getText());
Instant instant=Instant.ofEpochMilli(timestamp);
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}
});
javaTimeModule.addSerializer(LocalDateTime.class, new JsonSerializer<LocalDateTime>() {
@Override
public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNumber(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
}
});
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
// Long轉(zhuǎn)換為String傳輸
javaTimeModule.addSerializer(Long.class, ToStringSerializer.instance);
mapper.registerModule(javaTimeModule);
converter.setObjectMapper(mapper);
return converter;
}
如果不生效:
StringHttpMessageConverter是Spring MVC中用于讀寫HTTP消息的字符串轉(zhuǎn)換器。它可以將請(qǐng)求的輸入流轉(zhuǎn)換為字符串,同樣也可以將字符串寫入HTTP響應(yīng)中
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public StringHttpMessageConverter stringHttpMessageConverter() {
StringHttpMessageConverter converter=new StringHttpMessageConverter();
converter.setWriteAcceptCharset(false); // 設(shè)置是否在寫入響應(yīng)時(shí)發(fā)送AcceptedCharset
return converter;
}
}
SSE技術(shù)是基于單工通信模式,只是單純的客戶端向服務(wù)端發(fā)送請(qǐng)求,服務(wù)端不會(huì)主動(dòng)發(fā)送給客戶端。服務(wù)端采取的策略是抓住這個(gè)請(qǐng)求不放,等數(shù)據(jù)更新的時(shí)候才返回給客戶端,當(dāng)客戶端接收到消息后,再向服務(wù)端發(fā)送請(qǐng)求,周而復(fù)始。
注意:因?yàn)镋ventSource對(duì)象是SSE的客戶端,可能會(huì)有瀏覽器對(duì)其不支持,但谷歌、火狐、360是可以的,IE不可以。
另外WebSocket技術(shù)是雙工模式。
服務(wù)端代碼如下:
//本文使用的是Spring4.x,無需其他類庫(kù),;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomeController {
@RequestMapping(value="/", method=RequestMethod.GET)
public String home(Locale locale, Model model) {
return "sse";
}
@RequestMapping(value="push",produces="text/event-stream")
public @ResponseBody String push(){
System.out.println("push msg..");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//注意:返回?cái)?shù)據(jù)的格式要嚴(yán)格按照這樣寫,‘\n\n’不可少
return "data:current time: "+new SimpleDateFormat("YYYY-MM-dd hh:mm:ss").format(new Date())+"\n\n";
}
}
客戶端代碼如下,sse.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>SSE方式消息推送</title>
</head>
<body>
<div id="msgFromPush"></div>
<!--這里的jquery僅僅用于數(shù)據(jù)的展示,不影響消息推送-->
<script type="text/javascript" src="<c:url value='resources/jquery-1.10.2.js'/>"></script>
<script type="text/javascript">
if(!!window.EventSource){
var source=new EventSource('push');
s='';
source.addEventListener('message',function(e){
console.log("get message"+e.data);
s+=e.data+"<br/>";
$("#msgFromPush").html(s);
});
source.addEventListener('open',function(e){
console.log("connect is open");
},false);
source.addEventListener('error',function(e){
if(e.readyState==EventSource.CLOSE){
console.log("connect is close");
}else{
console.log(e.readyState);
}
},false);
}else{
console.log("web is not support");
}
</script>
</body>
</html>
運(yùn)行結(jié)果:
Servlet3.0+ 異步處理方法通過設(shè)置動(dòng)態(tài)Servlet(即Dynamic)支持異步處理,在客戶端(瀏覽器)以ajax形式不斷發(fā)送請(qǐng)求,從而獲得信息。
(1)動(dòng)態(tài)Servlet支持異步處理
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import com.config.MvcConfig;
/** Use class that implements WebApplicationInitializer interface to substitute web.xml
* @author apple
*
*/
public class WebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext applicationContext= new AnnotationConfigWebApplicationContext();
applicationContext.register(MvcConfig.class); // process annotated class MvcConfig
applicationContext.setServletContext(servletContext); // make applicationContext and servletContext related
applicationContext.refresh();
Dynamic servlet=servletContext.addServlet("dispatcher", new DispatcherServlet(applicationContext));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
servlet.setAsyncSupported(true);
}
}
(2)Service Bean
該類僅僅是業(yè)務(wù)邏輯,與異步實(shí)現(xiàn)關(guān)系無關(guān)。
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;
/** 用于異步Servlet 3.0+的服務(wù)器端推送測(cè)試,
* 為Controller提供一個(gè)異步的定時(shí)更新的DeferredResult<String>
* @author apple
*
*/
@Service
public class PushService {
private DeferredResult<String> deferredResult ;
public DeferredResult<String> getAyncUpdateDeferredResult() {
this.deferredResult=new DeferredResult<>();
return deferredResult;
}
/**以下說明通過查看@Scheduled注解獲得。
* 由于@Scheduled注解的處理是通過注冊(cè)一個(gè)ScheduledAnnotationBeanPostProcessor完成的,
* 而后者是對(duì)方法被@Scheduled注解了的Bean,按照該注解的要求,
* 通過調(diào)用一個(gè)TaskScheduler進(jìn)行post process。
* 因此對(duì)于實(shí)例化的Bean,必須完成@Scheduled注解的方法后才能被調(diào)用。
*/
@Scheduled(fixedDelay=5000)
public void refresh() {
if (this.deferredResult !=null) {
this.deferredResult.setResult(String.valueOf(System.currentTimeMillis()));
}
}
}
(3) Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import com.service.PushService;
@Controller
public class PushController {
@Autowired
PushService pushService;
@RequestMapping(value="/defer")
@ResponseBody
public DeferredResult<String> deferredCall() {
// 通過Service Bean獲取異步更新的DeferredReuslt<String>
return pushService.getAyncUpdateDeferredResult();
}
}
(4) 測(cè)試頁(yè)面(jsp)
測(cè)試頁(yè)面采用ajax不斷發(fā)送請(qǐng)求,這些請(qǐng)求構(gòu)成了并發(fā)請(qǐng)求。由于前面Servlet支持異步處理,請(qǐng)求到達(dá)服務(wù)端后,直到Service Bean實(shí)例化,并按照@Scheduled要求延時(shí)至設(shè)定的時(shí)間(例中5000ms)進(jìn)行了設(shè)置,才通過Controller中以@RequestMapping注解的方法發(fā)送response到瀏覽器。 測(cè)試結(jié)果: 瀏覽器頁(yè)面顯示(數(shù)據(jù)為:System.currentTimeMillis()) 1528273203260 1528273208265 1528273213271 1528273218278 1528273223282 1528273228285 1528273233290 1528273238296 1528273243298 1528273248302
由結(jié)果可知,請(qǐng)求確實(shí)是并發(fā)的,Servlet 3.0+對(duì)請(qǐng)求進(jìn)行了異步處理。
1、什么是 mock 測(cè)試
在測(cè)試過程中,對(duì)于某些不容易構(gòu)造或者不容易獲取的對(duì)象,用一個(gè)虛擬的對(duì)象來創(chuàng)建以便測(cè)試的測(cè)試方法,就是 mock 測(cè)試在測(cè)試過程中,對(duì)于某些不容易構(gòu)造或者不容易獲取的對(duì)象,用一個(gè)虛擬的對(duì)象來創(chuàng)建以便測(cè)試的測(cè)試方法,就是mock測(cè)試。
2、為什么使用 mock 測(cè)試
3、MockMVC 介紹
基于 RESTful 風(fēng)格的 SpringMVC 的測(cè)試,我們可以測(cè)試完整的 Spring MVC 流程,即從 URL請(qǐng)求到控制器處理,再到視圖渲染都可以測(cè)試。
1)MockMvcBuilder
MockMvcBuilder 是用來構(gòu)造 MockMvc 的構(gòu)造器,其主要有兩個(gè)實(shí)現(xiàn):StandaloneMockMvcBuilder 和 DefaultMockMvcBuilder,對(duì)于我們來說直接使用靜態(tài)工廠 MockMvcBuilders 創(chuàng)建即可。 MockMvcBuilder 是用來構(gòu)造 MockMvc 的構(gòu)造器,其主要有兩個(gè)實(shí)現(xiàn):StandaloneMockMvcBuilder 和 DefaultMockMvcBuilder,對(duì)于我們來說直接使用靜態(tài)工廠 MockMvcBuilders 創(chuàng)建即可。
2)MockMvcBuilders
負(fù)責(zé)創(chuàng)建 MockMvcBuilder 對(duì)象,有兩種創(chuàng)建方式:
standaloneSetup(Object... controllers):通過參數(shù)指定一組控制器,這樣就不需要從上下文獲取了。
webAppContextSetup(WebApplicationContext wac):指定 WebApplicationContext,將會(huì)從該上下文獲取相應(yīng)的控制器并得到相應(yīng)的 MockMvc,本章節(jié)下面測(cè)試用例均使用這種方式創(chuàng)建 MockMvcBuilder 對(duì)象。
3)MockMvc
對(duì)于服務(wù)器端的 SpringMVC 測(cè)試支持主入口點(diǎn)。通過 MockMvcBuilder 構(gòu)造MockMvcBuilder 由 MockMvcBuilders 建造者的靜態(tài)方法去建造。
核心方法:perform(RequestBuilder rb) -- 執(zhí)行一個(gè) RequestBuilder 請(qǐng)求,會(huì)自動(dòng)執(zhí)行SpringMVC 的流程并映射到相應(yīng)的控制器執(zhí)行處理,該方法的返回值是一個(gè) ResultActions。
4)ResultActions
(1)andExpect:添加 ResultMatcher 驗(yàn)證規(guī)則,驗(yàn)證控制器執(zhí)行完成后結(jié)果是否正確;
(2)andDo:添加 ResultHandler 結(jié)果處理器,比如調(diào)試時(shí)打印結(jié)果到控制臺(tái);
(3)andReturn:最后返回相應(yīng)的 MvcResult;然后進(jìn)行自定義驗(yàn)證/進(jìn)行下一步的異步處理;
5)MockMvcRequestBuilders
用來構(gòu)建請(qǐng)求的,其主要有兩個(gè)子類 MockHttpServletRequestBuilder 和MockMultipartHttpServletRequestBuilder(如文件上傳使用),即用來 Mock 客戶端請(qǐng)求需要的所有數(shù)據(jù)。
6)MockMvcResultMatchers
(1)用來匹配執(zhí)行完請(qǐng)求后的結(jié)果驗(yàn)證
(2)如果匹配失敗將拋出相應(yīng)的異常
(3)包含了很多驗(yàn)證 API 方法
7)MockMvcResultHandlers
(1)結(jié)果處理器,表示要對(duì)結(jié)果做點(diǎn)什么事情
(2)比如此處使用 MockMvcResultHandlers.print() 輸出整個(gè)響應(yīng)結(jié)果信息
8)MvcResult
(1)單元測(cè)試執(zhí)行結(jié)果,可以針對(duì)執(zhí)行結(jié)果進(jìn)行自定義驗(yàn)證邏輯。
1、添加依賴
<!-- spring 單元測(cè)試組件包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!-- 單元測(cè)試Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- Mock測(cè)試使用的json-path依賴 -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.2.0</version>
</dependency>
前兩個(gè) jar 依賴我們都已經(jīng)接觸過了,對(duì)于返回視圖方法的測(cè)試這兩個(gè) jar 依賴已經(jīng)足夠了,第三個(gè) jar 依賴是用于處理返回 Json 數(shù)據(jù)方法的,這里要明白每個(gè) jar 的具體作用。
2、被測(cè)試的方法
@RequestMapping(value="editItem")
public String editItem(Integer id, Model model) {
Item item=itemService.getItemById(id);
model.addAttribute("item", item);
return "itemEdit";
}
@RequestMapping(value="getItem")
@ResponseBody
public Item getItem(Integer id) {
Item item=itemService.getItemById(id);
return item;
}
這里我們提供了兩個(gè)方法,一個(gè)是返回視圖的方法,另一個(gè)是返回 Json 數(shù)據(jù)的方法,下面我們會(huì)給出測(cè)試類,分別對(duì)這兩個(gè)方法進(jìn)行測(cè)試。
3、測(cè)試類:ItemMockTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/*.xml")
@WebAppConfiguration
public class ItemMockTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void init() {
mockMvc=MockMvcBuilders.webAppContextSetup(context).build();
}
}
這里前兩個(gè)注解就不再解釋了,我們?cè)趯W(xué)習(xí) Spring 與 Junit 整合的時(shí)候已經(jīng)講解過了,這里說一下第三個(gè)注解: @WebAppConfiguration:可以在單元測(cè)試的時(shí)候,不用啟動(dòng) Servlet 容器,就可以獲取一個(gè) Web 應(yīng)用上下文。
1)返回視圖方法測(cè)試
@Test
public void test() throws Exception {
MvcResult result=mockMvc.perform(MockMvcRequestBuilders.get("/editItem").param("id", "1"))
.andExpect(MockMvcResultMatchers.view().name("itemEdit"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
Assert.assertNotNull(result.getModelAndView().getModel().get("item"));
}
這三句代碼是我們對(duì)結(jié)果的期望,最后打印出了結(jié)果,說明執(zhí)行成功,所有期望都達(dá)到了,否則會(huì)直接報(bào)錯(cuò)。從結(jié)果中我們就可以看到這個(gè)請(qǐng)求測(cè)試的情況。
2、返回 Json 數(shù)據(jù)方法
@Test
public void test1() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/getItem")
.param("id", "1")
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1))
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("IPhone X"))
.andDo(MockMvcResultHandlers.print())
.andReturn();
}
在這個(gè)方法中比較特殊的就是設(shè)置 MediaType 類型,因?yàn)槎际鞘褂?Json 格式,所以設(shè)置了 MediaType.APPLICATION_JSON,jsonPath 用于比對(duì)期望的數(shù)據(jù)是否與返回的結(jié)果一致,這里需要注意的是 "$.id" 這 key 的種形式。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。