# Spring3 Web MVC下的数据类型转换(第一篇)——《跟我学Spring3 Web MVC》抢先看
基于spring-framework-3.1.1.RELEASE
### 7.1、简介
在编写可视化界面项目时,我们通常需要对数据进行类型转换、验证及格式化。
**一、在Spring3之前,我们使用如下架构进行类型转换、验证及格式化:**
![](https://box.kancloud.cn/2016-05-13_57354724bd86c.jpg)
**流程:**
①:类型转换:首先调用PropertyEditor的setAsText(String),内部根据需要调用setValue(Object)方法进行设置转换后的值;
②:数据验证:需要显示调用Spring的Validator接口实现进行数据验证;
③:格式化显示:需要调用PropertyEditor的getText进行格式化显示。
**使用如上架构的缺点是:**
(1、PropertyEditor被设计为只能String<——>Object之间转换,不能任意对象类型<——>任意类型,如我们常见的Long时间戳到Date类型的转换是办不到的;
(2、PropertyEditor是线程不安全的,也就是有状态的,因此每次使用时都需要创建一个,不可重用;
(3、PropertyEditor不是强类型的,setValue(Object)可以接受任意类型,因此需要我们自己判断类型是否兼容;
(4、需要自己编程实现验证,Spring3支持更棒的注解验证支持;
(5、在使用SpEL表达式语言或DataBinder时,只能进行String<--->Object之间的类型转换;
`(6`、`不支持细粒度的类型转换/格式化,如UserModel的registerDate需要转换/格式化类似“``2012-05-01``”的数据,而OrderModel的orderDate需要转换/格式化类似“2012-05-01 15:11:13”的数据,因为大家都为java.util.Date类型,因此不太容易进行细粒度转换/格式化。`
`**在Spring Web MVC环境中,数据类型转换、验证及格式化通常是这样使用的:**`
![](https://box.kancloud.cn/2016-05-13_57354724d0bc1.jpg)
**流程:**
`①、类型转换:首先表单数据(全部是字符串)通过WebDataBinder进行绑定到命令对象,内部通过PropertyEditor实现;`
②:数据验证:在控制器中的功能处理方法中,需要显示的调用Spring的Validator实现并将错误信息添加到BindingResult对象中;
③:格式化显示:在表单页面可以通过如下方式展示通过`PropertyEditor`格式化的数据和错误信息:
```
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
```
首先需要通过如上taglib指令引入spring的两个标签库。
```
//1、格式化单个命令/表单对象的值(好像比较麻烦,真心没有好办法)
<spring:bind path="dataBinderTest.phoneNumber">${status.value}</spring:bind>
```
```
//2、<spring:eval>标签,自动调用ConversionService并选择相应的Converter SPI进行格式化展示
<spring:eval expression="dataBinderTest.phoneNumber"></spring:eval>
```
如上代码能工作的前提是在RequestMappingHandlerMapping配置了ConversionServiceExposingInterceptor,它的作用是暴露conversionService到请求中以便如<spring:eval>标签使用。
```
//3、通过form标签,内部的表单标签会自动调用命令/表单对象属性对应的PropertyEditor进行格式化显示
<form:form commandName="dataBinderTest">
<form:input path="phoneNumber"/><!-- 如果出错会显示错误之前的数据而不是空 -->
</form:form>
```
```
//4、显示验证失败后的错误信息
<form:errors></form:errors>
```
接下来我们就详细学习一下这些知识吧。
## 7.2、数据类型转换
### 7.2.1、Spring3之前的PropertyEditor
PropertyEditor介绍请参考【4.16.1、数据类型转换】。
**一、测试之前我们需要准备好测试环境:**
(1、模型对象,和【4.16.1、数据类型转换】使用的一样,需要将DataBinderTestModel模型类及相关类拷贝过来放入cn.javass.chapter7.model包中。
(2、控制器定义:
```
package cn.javass.chapter7.web.controller;
//省略import
@Controller
public class DataBinderTestController {
@RequestMapping(value = "/dataBind")
public String test(DataBinderTestModel command) {
//输出command对象看看是否绑定正确
System.out.println(command);
model.addAttribute("dataBinderTest", command);
return "bind/success";
}
}
```
(3、Spring配置文件定义,请参考chapter7-servlet.xml,并注册DataBinderTestController:
```
<bean class="cn.javass.chapter7.web.controller.DataBinderTestController"/>
```
(4、测试的URL:
http://localhost:9080/springmvc-chapter7/dataBind?username=zhang&bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&phoneNumber=010-12345678&date=2012-3-18 16:48:48&state=blocked
**二、注解式控制器注册PropertyEditor:**
**1、使用WebDataBinder进行控制器级别注册PropertyEditor(控制器独享)**
```
@InitBinder
//此处的参数也可以是ServletRequestDataBinder类型
public void initBinder(WebDataBinder binder) throws Exception {
//注册自定义的属性编辑器
//1、日期
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
CustomDateEditor dateEditor = new CustomDateEditor(df, true);
//表示如果命令对象有Date类型的属性,将使用该属性编辑器进行类型转换
binder.registerCustomEditor(Date.class, dateEditor);
//自定义的电话号码编辑器(和【4.16.1、数据类型转换】一样)
binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());
}
```
和【4.16.1、数据类型转换】一节类似,只是此处需要通过@InitBinder来注册自定义的PropertyEditor。
**2、使用**`**WebBindingInitializer批量注册**`**PropertyEditor**
和【4.16.1、数据类型转换】不太一样,因为我们的注解式控制器是POJO,没有实现任何东西,因此无法注入WebBindingInitializer,此时我们需要把WebBindingInitializer注入到我们的RequestMappingHandlerAdapter或AnnotationMethodHandlerAdapter,这样对于所有的注解式控制器都是共享的。
```
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="webBindingInitializer">
<bean class="cn.javass.chapter7.web.controller.support.initializer.MyWebBindingInitializer"/>
</property>
</bean>
```
此时我们注释掉控制器级别通过@InitBinder注册PropertyEditor的方法。
**3、全局级别注册PropertyEditor(全局共享)**
和【4.16.1、数据类型转换】一节一样,此处不再重复。请参考【4.16.1、数据类型转换】的【全局级别注册PropertyEditor(全局共享)】。
`接下来我们看一下Spring3提供的更强大的类型转换支持。`
### 7.2.2、Spring3开始的类型转换系统
Spring3引入了更加通用的类型转换系统,其定义了SPI接口(Converter等)和相应的运行时执行类型转换的API(ConversionService等),在Spring中它和PropertyEditor功能类似,可以替代PropertyEditor来转换外部Bean属性的值到Bean属性需要的类型。
该类型转换系统是Spring通用的,其定义在org.springframework.core.convert包中,不仅仅在Spring Web MVC场景下。目标是完全替换PropertyEditor,提供无状态、强类型且可以在任意类型之间转换的类型转换系统,可以用于任何需要的地方,如SpEL、数据绑定。
Converter SPI完成通用的类型转换逻辑,如java.util.Date<---->java.lang.Long或java.lang.String---->PhoneNumberModel等。
#### 7.2.2.1、架构
**1、类型转换器:**提供类型转换的实现支持。
![](https://box.kancloud.cn/2016-05-13_57354724e860b.jpg)
一个有如下三种接口:
**(1、Converter:**类型转换器,用于转换S类型到T类型,此接口的实现必须是线程安全的且可以被共享。
```
package org.springframework.core.convert.converter;
public interface Converter<S, T> { //① S是源类型 T是目标类型
T convert(S source); //② 转换S类型的source到T目标类型的转换方法
}
```
示例:请参考cn.javass.chapter7.converter.support.StringToPhoneNumberConverter转换器,用于将String--->PhoneNumberModel。
此处我们可以看到Converter接口实现只能转换一种类型到另一种类型,不能进行多类型转换,如将一个数组转换成集合,如(String[] ----> List<String>、String[]----->List<PhoneNumberModel>等)。
**(2、GenericConverter和ConditionalGenericConverter:**GenericConverter接口实现能在多种类型之间进行转换,ConditionalGenericConverter是有条件的在多种类型之间进行转换。
```
package org.springframework.core.convert.converter;
public interface GenericConverter {
Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
```
getConvertibleTypes:指定了可以转换的目标类型对;
convert:在sourceType和targetType类型之间进行转换。
```
package org.springframework.core.convert.converter;
public interface ConditionalGenericConverter extends GenericConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
```
matches:用于判断sourceType和targetType类型之间能否进行类型转换。
示例:如org.springframework.core.convert.support.ArrayToCollectionConverter和CollectionToArrayConverter用于在数组和集合间进行转换的ConditionalGenericConverter实现,如在String[]<---->List<String>、String[]<---->List<PhoneNumberModel>等之间进行类型转换。
对于我们大部分用户来说一般不需要自定义GenericConverter, 如果需要可以参考内置的GenericConverter来实现自己的。
**(3、ConverterFactory:**工厂模式的实现,用于选择将一种S源类型转换为R类型的子类型T的转换器的工厂接口。
```
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
```
S:源类型;R目标类型的父类型;T:目标类型,且是R类型的子类型;
getConverter:得到目标类型的对应的转换器。
示例:如org.springframework.core.convert.support.NumberToNumberConverterFactory用于在Number类型子类型之间进行转换,如Integer--->Double, Byte---->Integer, Float--->Double等。
对于我们大部分用户来说一般不需要自定义ConverterFactory,如果需要可以参考内置的ConverterFactory来实现自己的。
**2、类型转换器注册器、类型转换服务:**提供类型转换器注册支持,运行时类型转换API支持。
![](https://box.kancloud.cn/2016-05-13_5735472509463.jpg)
一共有如下两种接口:
**(1、ConverterRegistry:**类型转换器注册支持,可以注册/删除相应的类型转换器。
```
package org.springframework.core.convert.converter;
public interface ConverterRegistry {
void addConverter(Converter<?, ?> converter);
void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);
void addConverter(GenericConverter converter);
void addConverterFactory(ConverterFactory<?, ?> converterFactory);
void removeConvertible(Class<?> sourceType, Class<?> targetType);
}
```
可以注册:Converter实现,GenericConverter实现,ConverterFactory实现。
**(2、ConversionService:**运行时类型转换服务接口,提供运行期类型转换的支持。
```
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
<T> T convert(Object source, Class<T> targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
```
convert:将源对象转换为目标类型的目标对象。
Spring提供了两个默认实现(其都实现了ConverterRegistry**、**ConversionService接口):
DefaultConversionService:默认的类型转换服务实现;
DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可。
#### 7.2.2.2、Spring内建的类型转换器如下所示:
| **类名** | **说明** |
| --- | --- | --- |
| **第一组:标量转换器** |
| StringToBooleanConverter | String----->Booleantrue:true/on/yes/1; false:false/off/no/0 |
| ObjectToStringConverter | Object----->String调用toString方法转换 |
| StringToNumberConverterFactory | String----->Number(如Integer、Long等) |
| NumberToNumberConverterFactory | Number子类型(Integer、Long、Double等)<——> Number子类型(Integer、Long、Double等) |
| StringToCharacterConverter | String----->java.lang.Character取字符串第一个字符 |
| NumberToCharacterConverter | Number子类型(Integer、Long、Double等)——> java.lang.Character |
| CharacterToNumberFactory | java.lang.Character ——>Number子类型(Integer、Long、Double等) |
| StringToEnumConverterFactory | String----->enum类型通过Enum.valueOf将字符串转换为需要的enum类型 |
| EnumToStringConverter | enum类型----->String返回enum对象的name()值 |
| StringToLocaleConverter | String----->java.util.Local |
| PropertiesToStringConverter | java.util.Properties----->String默认通过ISO-8859-1解码 |
| StringToPropertiesConverter | String----->java.util.Properties默认使用ISO-8859-1编码 |
| **第二组:集合、数组相关转换器** |
| ArrayToCollectionConverter | 任意S数组---->任意T集合(List、Set) |
| CollectionToArrayConverter | 任意T集合(List、Set)---->任意S数组 |
| ArrayToArrayConverter | 任意S数组<---->任意T数组 |
| CollectionToCollectionConverter | 任意T集合(List、Set)<---->任意T集合(List、Set)即集合之间的类型转换 |
| MapToMapConverter | Map<---->Map之间的转换 |
| ArrayToStringConverter | 任意S数组---->String类型 |
| StringToArrayConverter | String----->数组默认通过“,”分割,且去除字符串的两边空格(trim) |
| ArrayToObjectConverter | 任意S数组---->任意Object的转换(如果目标类型和源类型兼容,直接返回源对象;否则返回S数组的第一个元素并进行类型转换) |
| ObjectToArrayConverter | Object----->单元素数组 |
| CollectionToStringConverter | 任意T集合(List、Set)---->String类型 |
| StringToCollectionConverter | String----->集合(List、Set)默认通过“,”分割,且去除字符串的两边空格(trim) |
| CollectionToObjectConverter | 任意T集合---->任意Object的转换(如果目标类型和源类型兼容,直接返回源对象;否则返回S数组的第一个元素并进行类型转换) |
| ObjectToCollectionConverter | Object----->单元素集合 |
| **第三组:默认(fallback)转换器:**之前的转换器不能转换时调用 |
| ObjectToObjectConverter | Object(S)----->Object(T)首先尝试valueOf进行转换、没有则尝试new 构造器(S) |
| IdToEntityConverter | Id(S)----->Entity(T)查找并调用public static T find[EntityName](S)获取目标对象,EntityName是T类型的简单类型 |
| FallbackObjectToStringConverter | Object----->StringConversionService作为恢复使用,即其他转换器不能转换时调用(执行对象的toString()方法) |
S:代表源类型,T:代表目标类型
如上的转换器在使用转换服务实现DefaultConversionService和DefaultFormattingConversionService时会自动注册。
#### 7.2.2.3、示例
(1、自定义String----->PhoneNumberModel的转换器
```
package cn.javass.chapter7.web.controller.support.converter;
//省略import
public class StringToPhoneNumberConverter implements Converter<String, PhoneNumberModel> {
Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");
@Override
public PhoneNumberModel convert(String source) {
if(!StringUtils.hasLength(source)) {
//①如果source为空 返回null
return null;
}
Matcher matcher = pattern.matcher(source);
if(matcher.matches()) {
//②如果匹配 进行转换
PhoneNumberModel phoneNumber = new PhoneNumberModel();
phoneNumber.setAreaCode(matcher.group(1));
phoneNumber.setPhoneNumber(matcher.group(2));
return phoneNumber;
} else {
//③如果不匹配 转换失败
throw new IllegalArgumentException(String.format("类型转换失败,需要格式[010-12345678],但格式是[%s]", source));
}
}
}
```
String转换为Date的类型转换器,请参考cn.javass.chapter7.web.controller.support.converter.StringToDateConverter。
(2、测试用例(cn.javass.chapter7.web.controller.support.converter.ConverterTest)
```
@Test
public void testStringToPhoneNumberConvert() {
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToPhoneNumberConverter());
String phoneNumberStr = "010-12345678";
PhoneNumberModel phoneNumber = conversionService.convert(phoneNumberStr, PhoneNumberModel.class);
Assert.assertEquals("010", phoneNumber.getAreaCode());
}
```
类似于PhoneNumberEditor将字符串“010-12345678”转换为PhoneNumberModel。
```
@Test
public void testOtherConvert() {
DefaultConversionService conversionService = new DefaultConversionService();
//"1"--->true(字符串“1”可以转换为布尔值true)
Assert.assertEquals(Boolean.valueOf(true), conversionService.convert("1", Boolean.class));
//"1,2,3,4"--->List(转换完毕的集合大小为4)
Assert.assertEquals(4, conversionService.convert("1,2,3,4", List.class).size());
}
```
其他类型转换器使用也是类似的,此处不再重复。
#### 7.2.2.4、集成到Spring Web MVC环境
(1、注册ConversionService实现和自定义的类型转换器
```
<!-- ①注册ConversionService -->
<bean id="conversionService" class="org.springframework.format.support.
FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="cn.javass.chapter7.web.controller.support.
converter.StringToPhoneNumberConverter"/>
<bean class="cn.javass.chapter7.web.controller.support.
converter.StringToDateConverter">
<constructor-arg value="yyyy-MM-dd"/>
</bean>
</list>
</property>
</bean>
```
FormattingConversionServiceFactoryBean:是FactoryBean实现,默认使用DefaultFormattingConversionService转换器服务实现;
converters:注册我们自定义的类型转换器,此处注册了String--->PhoneNumberModel和String--->Date的类型转换器。
(2、通过ConfigurableWebBindingInitializer注册ConversionService
```
<!-- ②使用ConfigurableWebBindingInitializer注册conversionService -->
<bean id="webBindingInitializer" class="org.springframework.web.bind.support.
ConfigurableWebBindingInitializer">
<property name="conversionService" ref="conversionService"/>
</bean>
```
此处我们通过ConfigurableWebBindingInitializer绑定初始化器进行ConversionService的注册;
3、注册ConfigurableWebBindingInitializer到RequestMappingHandlerAdapter
```
<bean class="org.springframework.web.servlet.mvc.method.annotation.
RequestMappingHandlerAdapter">
<property name="webBindingInitializer" ref="webBindingInitializer"/>
</bean>
```
通过如上配置,我们就完成了Spring3.0的类型转换系统与Spring Web MVC的集成。此时可以启动服务器输入之前的URL测试了。
此时可能有人会问,如果我同时使用PropertyEditor和ConversionService,执行顺序是什么呢?内部首先查找PropertyEditor进行类型转换,如果没有找到相应的PropertyEditor再通过ConversionService进行转换。
如上集成过程看起来比较麻烦,后边我们会介绍<mvc:annotation-driven>和@EnableWebMvc,ConversionService会自动注册,后续章节再详细介绍。
- 跟我学 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