# 【第十三章】 測試 之 13.1 概述 13.2 單元測試 ——跟我學spring3
## 13.1? 概述
### 13.1.1? 測試
軟件測試的目的首先是為了保證軟件功能的正確性,其次是為了保證軟件的質量,軟件測試相當復雜,已經超出本書所涉及的范圍,本節將只介紹軟件測試流程中前兩個步驟:單元測試和集成測試。
Spring提供了專門的測試模塊用于簡化單元測試和集成測試,單元測試和集成測試一般由程序員實現。
## 13.2? 單元測試
### 13.2.1? 概述
單元測試是最細粒度的測試,即具有原子性,通常測試的是某個功能(如測試類中的某個方法的功能)。
采用依賴注入后我們的代碼對Spring IoC容器幾乎沒有任何依賴,因此在對我們代碼進行測試時無需依賴Spring IoC容器,我們只需要通過簡單的實例化對象、注入依賴然后測試相應方法來測試該方法是否完成我們預期的功能。
在本書中使用的傳統開發流程,即先編寫代碼實現功能,然后再寫測試來驗證功能是否正確,而不是測試驅動開發,測試驅動開發是指在編寫代碼實現功能之前先寫測試,然后再根據測試來寫滿足測試要求的功能代碼,通過測試來驅動開發,如果對測試驅動開發感興趣推薦閱讀【測試驅動開發的藝術】。
在實際工作中,應該只對一些復雜的功能進行單元測試,對于一些簡單的功能(如數據訪問層的CRUD)沒有必要花費時間進行單元測試。
Spring對單元測試提供如下支持:
* Mock對象:Spring通過Mock對象來簡化一些場景的單元測試:
**JNDI測試支持**:在org.springframework.mock.jndi包下通過了SimpleNamingContextBuilder來來創建JNDI上下文Mock對象,從而無需依賴特定Java EE容器即可完成JNDI測試。
**web測試支持:**在org.springframework.mock.web包中提供了一組Servlet API的Mock對象,從而可以無需Web容器即可測試web層的類。
* **工具類**:通過通用的工具類來簡化編寫測試代碼:
**反射工具類:**在org.springframework.test.util包下的ReflectionTestUtils能通過反射完成類的非public字段或setter方法的調用;
**JDBC工具類:**在org.springframework.test.util包下的SimpleJdbcTestUtils能讀取一個sql腳本文件并執行來簡化SQL的執行,還提供了如清空表、統計表中行數的簡便方法來簡化測試代碼的編寫。
接下來讓我們學習一下開發過程中各層代碼如何編寫測試用例。
### 13.2.2? 準備測試環境
**1、Junit安裝:**將Junit 4包添加到“pointShop”項目中,具體方法請參照【2.2.3? Hello World】。
**?????? 2、jMock安裝:**到jMock官網【http://www.jmock.org/】下載最新的jMock包,在本書中使用jMock2.5.1版本,將下載的“jmock-2.5.1-jars.zip ”包中的如下jar包拷貝到項目的lib目錄下并添加到類路徑:
+ objenesis-1.0.jar
+ jmock-script-2.5.1.jar
+ jmock-legacy-2.5.1.jar
+ jmock-junit4-2.5.1.jar
+ jmock-junit3-2.5.1.jar
+ jmock-2.5.1.jar
+ hamcrest-library-1.1.jar
+ hamcrest-core-1.1.jar
+ bsh-core-2.0b4.jar
注:cglib包無需添加到類路徑,因為我們之前已經提供。
**3、添加Spring測試支持包:**將下載的spring-framework-3.0.5.RELEASE-with-docs.zip包中的如下jar包拷貝到項目的lib目錄下并添加到類路徑:
dist\org.springframework.test-3.0.5.RELEASE.jar
4、在“pointShop”項目下新建test文件夾并將其添加到【Java Build Path】中,該文件夾用于存放測試代碼,從而分離測試代碼和開發代碼。
到此測試環境搭建完畢。
### 13.2.3? 數據訪問層
數據訪問層單元測試,目的是測試該層定義的接口實現方法的行為是否正確,其實本質是測試是否正確與數據庫交互,是否發送并執行了正確的SQL,SQL執行成功后是否正確的組裝了業務邏輯層需要的數據。
數據訪問層單元測試通過Mock對象與數據庫交互的API來完成測試。
接下來讓我們學習一下如何進行數據訪問層單元測試:
**1、在test文件夾下創建如下測試類:**
```
package cn.javass.point.dao.hibernate;
//省略import
public class GoodsHibernateDaoUnitTest {
//1、Mock對象上下文,用于創建Mock對象
private final Mockery context = new Mockery() {{
//1.1、表示可以支持Mock非接口類,默認只支持Mock接口
setImposteriser(ClassImposteriser.INSTANCE);
}};
//2、Mock HibernateTemplate類
private final HibernateTemplate mockHibernateTemplate = context.mock(HibernateTemplate.class);
private IGoodsDao goodsDao = null;
@Before
public void setUp() {
//3、創建IGoodsDao實現
GoodsHibernateDao goodsDaoTemp = new GoodsHibernateDao();
//4、通過ReflectionTestUtils注入需要的非public字段數據
ReflectionTestUtils.setField(goodsDaoTemp, "entityClass", GoodsModel.class);
//5、注入mockHibernateTemplate對象
goodsDaoTemp.setHibernateTemplate(mockHibernateTemplate);
//6、賦值給我們要使用的接口
goodsDao = goodsDaoTemp;
}
}
```
* Mockery:jMock核心類,用于創建Mock對象的,通過其mock方法來創建相應接口或類的Mock對象。
* goodsDaoTemp:需要測試的IGoodsDao實現,通過ReflectionTestUtils注入需要的非public字段數據。
**2、測試支持寫完后,接下來測試一下IGoodsDao的get方法是否滿足需求:**
```
@Test
public void testSave () {
//7、創建需要的Model數據
final GoodsModel expected = new GoodsModel();
//8、定義預期行為,并在后邊來驗證預期行為是否正確
context.checking(new org.jmock.Expectations() {
{
//9、表示需要調用且只調用一次mockHibernateTemplate的get方法,
//且get方法參數為(GoodsModel.class, 1),并將返回goods
one(mockHibernateTemplate).get(GoodsModel.class, 1);
will(returnValue(expected));
}
});
//10、調用goodsDao的get方法,在內部實現中將委托給
//getHibernateTemplate().get(this.entityClass, id);
//因此按照第8步定義的預期行為將返回goods
GoodsModel actual = goodsDao.get(1);
//11、來驗證第8步定義的預期行為是否調用了
context.assertIsSatisfied();
//12、驗證goodsDao.get(1)返回結果是否正確
Assert.assertEquals(goods, expected);
}
```
* **context.checking():**該方法中用于定義預期行為,其中第9步定義了需要調用一次且只調用一次mockHibernateTemplate的get方法,且get方法參數為(GoodsModel.class, 1),并將返回goods對象。
* **goodsDao.get(1):**調用goodsDao的get方法,在內部實現中將委托給“getHibernateTemplate().get(this.entityClass, id)”。
* **context.assertIsSatisfied():**來驗證前邊定義的預期行為是否執行,且是否正確。
* **Assert.assertEquals(expected, actual):**用于驗證“goodsDao.get(1) ”返回的結果是否是預期結果。
以上測試方法其實是沒有必要的,對于非常簡單的CRUD沒有必要寫單元測試,只有相當復雜的方法才有必要寫單元測試。
這種通過Mock對象來測試數據訪問層代碼其實一點意義沒有,因為這里沒有與數據庫交互,無法驗證真實環境中與數據庫交互是否正確,因此這里只是告訴你如何測試數據訪問層代碼,在實際工作中一般通過集成測試來完成數據訪問層測試。
### 13.2.4? 業務邏輯層
業務邏輯單元測試,目的是測試該層的業務邏輯是否正確并通過Mock 數據訪問層對象來隔離與數據庫交互,從而無需連接數據庫即可測試業務邏輯是否正確。
接下來讓我們學習一下如何進行業務邏輯層單元測試:
**1、在test文件夾下創建如下測試類:**
```
package cn.javass.point.service.impl;
//省略import
public class GoodsCodeServiceImplUnitTest {
//1、Mock對象上下文,用于創建Mock對象
private final Mockery context = new Mockery() {{
//1.1、表示可以支持Mock非接口類,默認只支持Mock接口
setImposteriser(ClassImposteriser.INSTANCE);
}};
//2、Mock IGoodsCodeDao接口
private IGoodsCodeDao goodsCodeDao = context.mock(IGoodsCodeDao.class);;
private IGoodsCodeService goodsCodeService;
@Before
public void setUp() {
GoodsCodeServiceImpl goodsCodeServiceTemp = new GoodsCodeServiceImpl();
//3、依賴注入
goodsCodeServiceTemp.setDao(goodsCodeDao);
goodsCodeService = goodsCodeServiceTemp;
}
}
```
以上測試支持代碼和數據訪問層測試代碼非常類似,在此不再闡述。
**2、測試支持寫完后,接下來測試一下購買商品Code碼是否滿足需求:**
測試業務邏輯時需要分別測試多種場景,即如在某種場景下成功或失敗等等,即測試應該全面,每個功能點都應該測試到。
**2.1、測試購買失敗的場景:**
```
@Test(expected = NotCodeException.class)
public void testBuyFail() {
final int goodsId = 1;
//4、定義預期行為,并在后邊來驗證預期行為是否正確
context.checking(new org.jmock.Expectations() {
{
//5、表示需要調用goodsCodeDao對象的getOneNotExchanged一次且僅以此
//且返回值為null
one(goodsCodeDao).getOneNotExchanged(goodsId);
will(returnValue(null));
}
});
goodsCodeService.buy("test", goodsId);
context.assertIsSatisfied();
}
```
* **context.checking():**該方法中用于定義預期行為,其中第5步定義了需要調用一次且只調用一次goodsCodeDao的getOneNotExchanged方法,且getOneNotExchanged方法參數為(goodsId),并將返回null。
* **goodsCodeService.buy("test", goodsId):**調用goodsCodeService的buy方法,由于調用goodsCodeDao的getOneNotExchanged方法將返回null,因此buy方法將拋出“NotCodeException”異常,從而表示沒有Code碼。
* **context.assertIsSatisfied():**來驗證前邊定義的預期行為是否執行,且是否正確。
* 由于我們在預期行為中調用getOneNotExchanged將返回null,因此測試將失敗且拋出NotCodeException異常。
**2.2、測試購買成功的場景:**
```
@Test()
public void testBuySuccess () {
final int goodsId = 1;
final GoodsCodeModel goodsCode = new GoodsCodeModel();
//6、定義預期行為,并在后邊來驗證預期行為是否正確
context.checking(new org.jmock.Expectations() {
{
//7、表示需要調用goodsCodeDao對象的getOneNotExchanged一次且僅以此
//且返回值為null
one(goodsCodeDao).getOneNotExchanged(goodsId);
will(returnValue(goodsCode));
//8、表示需要調用goodsCodeDao對象的save方法一次且僅一次
//且參數為goodsCode
one(goodsCodeDao).save(goodsCode);
}
});
goodsCodeService.buy("test", goodsId);
context.assertIsSatisfied();
Assert.assertEquals(goodsCode.isExchanged(), true);
}
```
* **context.checking():**該方法中用于定義預期行為,其中第7步定義了需要調用一次且只調用一次goodsCodeDao的getOneNotExchanged方法,且getOneNotExchanged方法參數為(goodsId),并將返回goodsCode對象;第8步定義了需要調用goodsCodeDao對象的save一次且僅一次。
* **goodsCodeService.buy("test", goodsId):**調用goodsCodeService的buy方法,由于調用goodsCodeDao的getOneNotExchanged方法將返回goodsCode,因此buy方法將成功執行。
* **context.assertIsSatisfied():**來驗證前邊定義的預期行為是否執行,且是否正確。
* **Assert.assertEquals(goodsCode.isExchanged(), true):**表示goodsCode已經被購買過了。
* 由于我們在預期行為中調用getOneNotExchanged將返回一個goodsCode對象,因此測試將成功,如果失敗說明業務邏輯寫錯了。
到此業務邏輯層測試完畢,在進行業務邏輯層測試時我們只關心業務邏輯是否正確,而不關心底層數據訪問層如何實現,因此測試業務邏輯層時只需Mock 數據訪問層對象,然后定義一些預期行為來滿足業務邏輯測試需求即可。
### 13.2.5? 表現層
表現層測試包括如Struts2的Action單元測試、攔截器單元測試、JSP單元測試等等,在此我們只學習Struts2的Action單元測試。
Struts2的Action測試相對業務邏輯層測試相對復雜一些,因為牽扯到使用如Servlet API、ActionContext等等,這里需要通過stub(樁)實現或mock對象來模擬如HttpServletRequest等對象。
一、首先學習一些最簡單的Action測試:
**1、在test文件夾下創建如下測試類:**
```
package cn.javass.point.web.front;
import cn.javass.point.service.IGoodsCodeService;
import cn.javass.point.web.front.action.GoodsAction;
//省略部分import
public class GoodsActionUnitTest {
//1、Mock對象上下文,用于創建Mock對象
private final Mockery context = new Mockery() {{
//1.1、表示可以支持Mock非接口類,默認只支持Mock接口
setImposteriser(ClassImposteriser.INSTANCE);
}};
//2、Mock IGoodsCodeService接口
private IGoodsCodeService goodsCodeService = context.mock(IGoodsCodeService.class);
private GoodsAction goodsAction;
@Before
public void setUp() {
goodsAction = new GoodsAction();
//3、依賴注入
goodsAction.setGoodsCodeService(goodsCodeService);
}
}
```
以上測試支持代碼和業務邏輯層測試代碼非常類似,在此不再闡述。
**2、測試支持寫完后,接下來測試一下前臺購買商品Code碼是否滿足需求:**
類似于測試業務邏輯時需要分別測試多種場景,測試Action同樣需要分別測試多種場景。
**2.1、測試購買失敗的場景:**
```
@Test
public void testBuyFail() {
final int goodsId = 1;
//4、定義預期行為,并在后邊來驗證預期行為是否正確
context.checking(new org.jmock.Expectations() {
{
//5、表示需要調用goodsCodeService對象的buy方法一次且僅一次
//且拋出NotCodeException異常
one(goodsCodeService).buy("test", goodsId);
will(throwException(new NotCodeException()));
}
});
//6、模擬Struts注入請求參數
goodsAction.setGoodsId(goodsId);
String actualResultCode = goodsAction.buy();
context.assertIsSatisfied();
Assert.assertEquals(ReflectionTestUtils.getField(goodsAction, "BUY_RESULT"), actualResultCode);
Assert.assertTrue(goodsAction.getActionErrors().size() > 0);
}
```
* **context.checking():**該方法中用于定義預期行為,其中第5步定義了需要調用goodsCodeService對象的buy方法一次且僅一次且將拋出NotCodeException異常。
* **goodsAction.setGoodsId(goodsId)**:用于模擬Struts注入請求參數,即完成數據綁定。
* **goodsAction.buy():**調用goodsAction的buy方法,該方法將委托給IGoodsCodeService實現完成,返回值用于定位視圖。
* **context.assertIsSatisfied():**來驗證前邊定義的預期行為是否執行,且是否正確。
* **Assert.assertEquals(ReflectionTestUtils.getField(goodsAction, "BUY_RESULT"), actualResultCode)**:驗證返回的Result是否是我們指定的。
* **Assert.assertTrue(goodsAction.getActionErrors().size() > 0)**:表示執行Action時有錯誤,即Action動作錯誤。如果條件不成立,說明我們Action功能是錯誤的,需要修改。
**2.2、測試購買成功的場景:**
```
@Test
public void testBuySuccess() {
final int goodsId = 1;
final GoodsCodeModel goodsCode = new GoodsCodeModel();
//7、定義預期行為,并在后邊來驗證預期行為是否正確
context.checking(new org.jmock.Expectations() {
{
//8、表示需要調用goodsCodeService對象的buy方法一次且僅一次
//且返回goodsCode對象
one(goodsCodeService).buy("test", goodsId);
will(returnValue(goodsCode));
}
});
//9、模擬Struts注入請求參數
goodsAction.setGoodsId(goodsId);
String actualResultCode = goodsAction.buy();
context.assertIsSatisfied();
Assert.assertEquals(ReflectionTestUtils.getField(goodsAction, "BUY_RESULT"), actualResultCode);
Assert.assertTrue(goodsAction.getActionErrors().size() == 0);
}
```
* **context.checking():**該方法中用于定義預期行為,其中第5步定義了需要調用goodsCodeService對象的buy方法一次且僅一次且將返回goodsCode對象。
* **goodsAction.setGoodsId(goodsId)**:用于模擬Struts注入請求參數,即完成數據綁定。
* **goodsAction.buy():**調用goodsAction的buy方法,該方法將委托給IGoodsCodeService實現完成,返回值用于定位視圖。
* **context.assertIsSatisfied():**來驗證前邊定義的預期行為是否執行,且是否正確。
* **Assert.assertEquals(ReflectionTestUtils.getField(goodsAction, "BUY_RESULT"), actualResultCode)**:驗證返回的Result是否是我們指定的。
* **Assert.assertTrue(goodsAction.getActionErrors().size() == 0)**:表示執行Action時沒有錯誤,即Action動作正確。如果條件不成立,說明我們Action功能是錯誤的,需要修改。
通過模擬ActionContext對象內容從而可以非常容易的測試Action中各種與http請求相關情況,無需依賴web服務器即可完成測試。但對于如果我們使用htpp請求相關對象的該如何測試?如果我們需要使用ActionContext獲取值棧數據應該怎么辦?這就需要Struts提供的junit插件支持了。我們會在集成測試中介紹。
對于表現層其他功能的單元測試本書不再介紹,如JSP單元測試、攔截器單元測試等等。
原創內容,轉載請注明私塾在線【[http://sishuok.com/forum/blogPost/list/0/2555.html](http://sishuok.com/forum/blogPost/list/0/2555.html#7330)】
- 跟我學 Spring3
- 【第二章】 IoC 之 2.1 IoC基礎 ——跟我學Spring3
- 【第二章】 IoC 之 2.2 IoC 容器基本原理 ——跟我學Spring3
- 【第二章】 IoC 之 2.3 IoC的配置使用——跟我學Spring3
- 【第三章】 DI 之 3.1 DI的配置使用 ——跟我學spring3
- 【第三章】 DI 之 3.2 循環依賴 ——跟我學spring3
- 【第三章】 DI 之 3.3 更多DI的知識 ——跟我學spring3
- 【第三章】 DI 之 3.4 Bean的作用域 ——跟我學spring3
- 【第四章】 資源 之 4.1 基礎知識 ——跟我學spring3
- 【第四章】 資源 之 4.2 內置Resource實現 ——跟我學spring3
- 【第四章】 資源 之 4.3 訪問Resource ——跟我學spring3
- 【第四章】 資源 之 4.4 Resource通配符路徑 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.1 概述 5.2 SpEL基礎 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.3 SpEL語法 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.4在Bean定義中使用EL—跟我學spring3
- 【第六章】 AOP 之 6.1 AOP基礎 ——跟我學spring3
- 【第六章】 AOP 之 6.2 AOP的HelloWorld ——跟我學spring3
- 【第六章】 AOP 之 6.3 基于Schema的AOP ——跟我學spring3
- 【第六章】 AOP 之 6.4 基于@AspectJ的AOP ——跟我學spring3
- 【第六章】 AOP 之 6.5 AspectJ切入點語法詳解 ——跟我學spring3
- 【第六章】 AOP 之 6.6 通知參數 ——跟我學spring3
- 【第六章】 AOP 之 6.7 通知順序 ——跟我學spring3
- 【第六章】 AOP 之 6.8 切面實例化模型 ——跟我學spring3
- 【第六章】 AOP 之 6.9 代理機制 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.1 概述 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.2 JDBC模板類 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.3 關系數據庫操作對象化 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.4 Spring提供的其它幫助 ——跟我學spring3【私塾在線原創】
- 【第七章】 對JDBC的支持 之 7.5 集成Spring JDBC及最佳實踐 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.1 概述 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.2 集成Hibernate3 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.3 集成iBATIS ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.4 集成JPA ——跟我學spring3
- 【第九章】 Spring的事務 之 9.1 數據庫事務概述 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.2 事務管理器 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.3 編程式事務 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.4 聲明式事務 ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.1 概述 ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.2 集成Struts1.x ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.3 集成Struts2.x ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.4 集成JSF ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.1 概述 ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.2 實現通用層 ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.3 實現積分商城層 ——跟我學spring3
- 【第十二章】零配置 之 12.1 概述 ——跟我學spring3
- 【第十二章】零配置 之 12.2 注解實現Bean依賴注入 ——跟我學spring3
- 【第十二章】零配置 之 12.3 注解實現Bean定義 ——跟我學spring3
- 【第十二章】零配置 之 12.4 基于Java類定義Bean配置元數據 ——跟我學spring3
- 【第十二章】零配置 之 12.5 綜合示例-積分商城 ——跟我學spring3
- 【第十三章】 測試 之 13.1 概述 13.2 單元測試 ——跟我學spring3
- 【第十三章】 測試 之 13.3 集成測試 ——跟我學spring3
- 跟我學 Spring MVC
- SpringMVC + spring3.1.1 + hibernate4.1.0 集成及常見問題總結
- Spring Web MVC中的頁面緩存支持 ——跟我學SpringMVC系列
- Spring3 Web MVC下的數據類型轉換(第一篇)——《跟我學Spring3 Web MVC》搶先看
- Spring3 Web MVC下的數據格式化(第二篇)——《跟我學Spring3 Web MVC》搶先看
- 第一章 Web MVC簡介 —— 跟開濤學SpringMVC
- 第二章 Spring MVC入門 —— 跟開濤學SpringMVC
- 第三章 DispatcherServlet詳解 ——跟開濤學SpringMVC
- 第四章 Controller接口控制器詳解(1)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(2)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(3)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解 (4)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(5)——跟著開濤學SpringMVC
- 跟著開濤學SpringMVC 第一章源代碼下載
- 第二章 Spring MVC入門 源代碼下載
- 第四章 Controller接口控制器詳解 源代碼下載
- 第四章 Controller接口控制器詳解(6)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(7 完)——跟著開濤學SpringMVC
- 第五章 處理器攔截器詳解——跟著開濤學SpringMVC
- 源代碼下載 第五章 處理器攔截器詳解——跟著開濤學SpringMVC
- 注解式控制器運行流程及處理器定義 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- 源代碼下載 第六章 注解式控制器詳解
- SpringMVC3強大的請求映射規則詳解 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- Spring MVC 3.1新特性 生產者、消費者請求限定 —— 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC強大的數據綁定(1)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC強大的數據綁定(2)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC數據類型轉換——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC
- SpringMVC數據格式化——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC
- SpringMVC數據驗證——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC