在SpringMVC中,请求处理方法执行完成后,最终返回一个 ModelAndView对象;对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个ModelAndView 对象,它包含了逻辑名和模型对象的视图。
Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是
Excel、JFreeChart 等各种表现形式的视图。
对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦。
视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。
为了实现视图模型和具体实现技术的解耦,Spring 在org.springframework.web.servlet 包中定义了一个高度抽象的 View接口:
视图对象由视图解析器负责实例化;由于视图是无状态的,所以他们不会有线程安全的问题。
大类 | 视图类型 | 说明 |
URL资源视图 | InternalResourceView | 将JSP或其他资源封装成一个视图,是InternalResourceViewResolver默认使用的视图实现类 |
JstlView | 如果jsp文件中使用了JSTL国际化标签的功能,则需要使用该视图类 | |
文档视图 | AbstractExcelView | Excel文档视图的抽象类,该视图基于POI构造Excel文档 |
AbstractPdfView | PDF文档视图的抽象类,该视图基于iText构造PDF文档 | |
报表视图 | ConfigurableJsperReportsView | 几个使用JsperReports报表技术的视图 |
JsperReportsCsvView | ||
JsperReportsMultiFormatView | ||
JsperReportsHtmlView | ||
JsperReportsPdfView | ||
JsperReportsXlsView | ||
JSON视图 | MappingJacksonJsonView | 将模型数据通过JackSon开源框架的ObjectMapper以Json方式输出 |
SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并
指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。
视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。
所有的视图解析器都必须实现 ViewResolver 接口:
大类 | 视图类型 | 说明 |
解析为Bean的名字 | BeanNameViewResolver | 将逻辑视图名解析为一个Bean,Bean的id等于逻辑视图名 |
解析为URL文件 | InternalResourceViewResolver | 将视图名解析为一个URL文件,一般用该解析器将视图名映射为一个保存在WEB-INF目录下的程序文件(如JSP) |
JsperReportsViewResolver | JsperReports是一个基于Java的开源报表工具,该解析器将视图名解析为报表文件对应的URL | |
模板文件视图 | FreeMarkerViewResolver | 解析为FreeMarker模板技术的模板文件 |
VelocityViewResolver | 解析为Velocity模板技术的模板文件 | |
VelocityLayoutViewResolver |
可以选择一种视图解析器或混用多种视图解析器,每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高。
SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则将抛出ServletException 异常。
JSP 是最常见的视图技术,可以使用InternalResourceViewResolver 作为视图解析器,他的配置如下:
若项目中使用了 JSTL,则 SpringMVC 会自动把视图由InternalResourceView 转为 JstlView(需要导入 jstl.jar和standard.jar)
若使用 JSTL 的 fmt 标签则需要在 SpringMVC 的配置文件中配置国际化资源文件
1、导入jar包
2、在src下面创建三个国际化文件
i18n.properties:
i18n.username=Username
i18n.password=Password
i18n_zh_CN.properties:
i18n.username=用户名
i18n.password=密码
i18n_en_US.properties:
i18n.username=Username
i18n.password=Password
3、在springmvc.xml中配置国际化资源文件
4、jsp页面使用fmt标签
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--导入fmt国际化标签--%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
成功页面
上面从index.jsp跳转到success.jsp页面,都是经过了Handler进行转发的(因为success.jsp页面在WEB-INF下面,无法直接访问的),若希望直接响应通过 SpringMVC 渲染的页面,可以使用
此时,就可以在浏览器直接访问success路径,也会跳转到success.jsp页面。
上面的配置会有一个问题:此时之前的那个标签的href跳转都会报404错误了,要想解决这个问题,需要配置
:
若希望使用Excel 展示数据列表,仅需要扩展 SpringMVC 提供的 AbstractExcelView 或AbstractJExcelView 即可。实现 buildExcelDocument() 方法,在方法中使用模型数据对象构建 Excel 文档就可以了。
AbstractExcelView 基于 POI API,而AbstractJExcelView 是基于 JExcelAPI 的。
视图对象需要配置 IOC 容器中的一个 Bean,使用BeanNameViewResolver 作为视图解析器即可。
若希望直接在浏览器中直接下载Excel文档,则可以设置响应头 Content-Disposition 的值为attachment;filename=xxx.xls
package com.springmvc;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.View;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Map;
@Component("helloView")//把该视图实现类交给spring管理
public class HelloView implements View {
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
httpServletResponse.getWriter().println("hello view , time:"+new Date());
}
}
@RequestMapping("testHelloView")
public String testHelloView(){
System.out.println("testHelloView...");
return "helloView";
}
一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理;如果返回的字符串中带 forward: 或 redirect: 前缀
时,SpringMVC 会对他们进行特殊处理:将 forward: 和 redirect: 当成指示符,其后的字符串作为 URL 来处理。
redirect:success.jsp:会完成一个到 success.jsp 的重定向的操作
forward:success.jsp:会完成一个到 success.jsp 的转发操作
通过 SpringMVC 的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显。
使用SpringMVC的表单标签,需要引入:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
一般情况下,通过 GET 请求获取表单页面,而通过POST 请求提交表单页面,因此获取表单页面和提交表单
页面的 URL 是相同的。只要满足该最佳条件的契约,
可以通过 modelAttribute 属性指定绑定的模型属性,若没有指定该属性,则默认从 request 域对象中读取command 的表单 bean,如果该属性值也不存在,则会发生错误。
SpringMVC提供了多个表单组件标签;
1、
path:表单字段,对应 html 元素的 name 属性,支持级联属性
htmlEscape:是否对表单值的 HTML 特殊字符进行转换,默认值 为 true
cssClass:表单组件对应的 CSS – 样式类名
cssErrorClass:表单组件的数据存在错误时,采取的 CSS – 样式
2、form:input、form:password、form:hidden、form:textarea :对应 HTML 表单的 text、password、hidden、textarea
标签
3、form:radiobutton:单选框组件标签,当表单 bean 对应的属性值和 value 值相等时,单选框被选中。
4、form:radiobuttons:单选框组标签,用于构造多个单选框,属性如下:
items:可以是一个 List、String[] 或 Map
itemValue:指定 radio 的 value 值。可以是集合中 bean 的一个属性值
itemLabel:指定 radio 的 label值
delimiter:多个单选框可以通过 delimiter 指定分隔符
5、form:checkbox:•复选框组件。用于构造单个复选框
6、form:checkboxs:用于构造多个复选框。使用方式同form:radiobuttons 标签
7、form:select:用于构造下拉框组件。使用方式同form:radiobuttons 标签
8、form:option:下拉框选项组件标签。使用方式同form:radiobuttons 标签
9、form:errors:显示表单组件或数据校验所对应的错误
优雅的 REST 风格的资源URL 不希望带 .html 或 .do 等后缀;
若将 DispatcherServlet 请求映射配置为 /,则 Spring MVC 将捕获WEB 容器的所有请求,包括静态资源的请求, SpringMVC 会将他们当成一个普通请求处理,因找不到对应处理器将导致错误。
可以在 SpringMVC 的配置文件中配置
一般 WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的WEB 服务器的默认 Servlet 名称不是 default,则需要通过 default-servlet-name 属性显式指定。
1. Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
2. DataBinder 调用装配在 Spring MVC 上下文中的ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
3. 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData 对象
4. Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参。
Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,运行机制如下:
Spring MVC 上下文中内建了很多转换器,可完成大多数 Java 类型的转换工作。
ConversionService converters =
java.lang.Boolean -> java.lang.String : –
org.springframework.core.convert.support.ObjectToStringConverter@f874ca
java.lang.Character -> java.lang.Number : CharacterToNumberFactory@f004c9 –
java.lang.Character -> java.lang.String : ObjectToStringConverter@68a961 –
java.lang.Enum -> java.lang.String : EnumToStringConverter@12f060a –
java.lang.Number -> java.lang.Character : NumberToCharacterConverter@1482ac5 –
java.lang.Number -> java.lang.Number : NumberToNumberConverterFactory@126c6f –
java.lang.Number -> java.lang.String : ObjectToStringConverter@14888e8 –
java.lang.String -> java.lang.Boolean : StringToBooleanConverter@1ca6626 –
java.lang.String -> java.lang.Character : StringToCharacterConverter@1143800 –
java.lang.String -> java.lang.Enum : StringToEnumConverterFactory@1bba86e –
java.lang.String -> java.lang.Number : StringToNumberConverterFactory@18d2c12 –
java.lang.String -> java.util.Locale : StringToLocaleConverter@3598e1 –
java.lang.String -> java.util.Properties : StringToPropertiesConverter@c90828 –
java.lang.String -> java.util.UUID : StringToUUIDConverter@a42f23 –
java.util.Locale -> java.lang.String : ObjectToStringConverter@c7e20a –
java.util.Properties -> java.lang.String : PropertiesToStringConverter@367a7f –
java.util.UUID -> java.lang.String : ObjectToStringConverter@112b07f
ConversionService 是 Spring 类型转换体系的核心接口。
可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC容器中定义一个 ConversionService, Spring 将自动识别出IOC 容器中的 ConversionService,并在 Bean 属性配置及Spring MVC 处理方法入参绑定等场合使用它进行数据的转换
可通过 ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器
Spring 定义了 3 种类型的转换器接口,实现任意一个转换 器接口都可以作为自定义转换器注册到ConversionServiceFactroyBean 中:
1、Converter:将 S 类型对象转为 T 类型对象
2、ConverterFactory:将相同系列多个 “同质” Converter 封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将 String 转换为 Number 及 Number 子类(Integer、Long、Double 等)对象)可使用该转换器工厂类
3、GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换
需求:自定义个类型转换器,把类似于:101-lisi-23这样的字符串,转成一个User对象。
package com.springmvc;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component//交给Spring管理
public class UserConverter implements Converter {
@Override
public User convert(String source) {
if(!StringUtils.isEmpty(source)){
String[] values = source.split("-");
if(values.length>0){
Integer id = Integer.parseInt(values[0]);
String name = values[1];
Integer age = Integer.parseInt(values[2]);
User user = new User();
user.setId(id);
user.setName(name);
user.setAge(age);
System.out.println("User:"+user);
return user;
}
}
return null;
}
}
1、页面通过form表单提交一串字符串
2、后台方法接收
@RequestMapping("testConvert")
public String testConvert(@RequestParam("user") User user){
System.out.println("接受的User:"+user);
return "helloView";
}
3、测试
启动,在输入框输入:1001-lisi-33,点击submit提交,后台打印如下:
User:User{id=1001, name='lisi', age='33', department=null}
接受的User:User{id=1001, name='lisi', age='33', department=null}
还将提供以下支持:
支持使用 ConversionService 实例对表单参数进行类型转换
支持使用 @NumberFormat 注解、@DateTimeFormat 注解完成数据类型的格式化
支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
支持使用 @RequestBody 和 @ResponseBody 注解
由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用
于完成由表单字段到 JavaBean 属性的绑定
@InitBinder方法不能有返回值,它必须声明为void。
@InitBinder方法的参数通常是是 WebDataBinder
对属性对象的输入/输出进行格式化,从其本质上讲依然属于 “类型转换” 的范畴。
Spring 在格式化模块中定义了一个实现ConversionService 接口的FormattingConversionService 实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能。
FormattingConversionService 拥有一个FormattingConversionServiceFactroyBean 工厂类,后者用于在 Spring 上下文中构造前者
FormattingConversionServiceFactroyBean 内部已经注册了:
1、NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用 @NumberFormat 注解
2、JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解
装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动
了。
所以使用步骤:1、配置文件添加配置:
@DateTimeFormat 注解可对java.util.Date、java.util.Calendar、java.long.Long 时间类型的属性字段进行标注:
pattern 属性:类型为字符串。指定解析/格式化字段数据的模式, 如:”yyyy-MM-dd hh:mm:ss”
iso 属性:类型为 DateTimeFormat.ISO。指定解析/格式化字段数据 的ISO模式,包括四种:ISO.NONE(不使用) -- 默
认、ISO.DATE(yyyy-MM-dd) 、ISO.TIME(hh:mm:ss.SSSZ)、ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)
style 属性:字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式:S:短日期/时间格式、M:中日期/时间格式、L:长日期/时间格式、F:完整日期/时间格式、- 忽略日期或时间格式
@NumberFormat 可对类似数字类型的属性进行标注,它拥有两个互斥的属性:
style:类型为 NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、 Style.PERCENT(百分数类型)
pattern:类型为 String,自定义样式,如patter="#,###";
1、加入jackson的三个jar包:jackson-annotations-2.1.5.jar jackson-core-2.1.5.jar jackson-databind-2.1.5.jar
2、编写目标方法,使其返回 JSON 对应的对象或集合
3、如果是返回,就在方法上添加 @ResponseBody 注解,如果是接收json,就在方法入参前面加上@RequestBody。
HttpMessageConverter
类型为 T)输出为响应信息。
HttpMessageConverter
Boolean canRead(Class> clazz,MediaType mediaType):指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类型的对象,同时指定支持 MIME 类型(text/html,applaiction/json等)
Boolean canWrite(Class> clazz,MediaType mediaType):指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型在MediaType 中定义。
LIst
T read(Class extends T> clazz,HttpInputMessage inputMessage– ):将请求信息流转换为 T 类型的对象。
void write(T t,MediaType contnetType,HttpOutputMessgae outputMessage):将T类型的对象写到响应流中,同时指定相应的媒体类型为 contentType。
HttpMessageConverter
DispatcherServlet 默认装配RequestMappingHandlerAdapter ,而RequestMappingHandlerAdapter 默认装配如下HttpMessageConverter:
加入 jackson jar 包后, RequestMappingHandlerAdapter 装配的 HttpMessageConverter 如下:
使用 HttpMessageConverter
—使用 @RequestBody / @ResponseBody – 对处理方法进行标注
—使用 HttpEntity
当控制器处理方法使用到 @RequestBody/@ResponseBody 或 HttpEntity
@RequestBody 和 @ResponseBody 不需要成对出现
使用HttpEntity参数获取请求头和请求体
控制器方法:
@RequestMapping("testHttpEntity")
public String testHttpEntity(HttpEntity httpEntity){
System.out.println("请求头:" + httpEntity.getHeaders());
System.out.println("请求体:" + httpEntity.getBody());
return SUCCESS;
}
打印效果:
请求头:{host=[localhost:8080], connection=[keep-alive], content-length=[25], cache-control=[max-age=0], origin=[http://localhost:8080], upgrade-insecure-requests=[1], content-type=[application/x-www-form-urlencoded], user-agent=[Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36], accept=[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8], referer=[http://localhost:8080/Spring4MVC_01_war_exploded/], accept-encoding=[gzip, deflate, br], accept-language=[zh-CN,zh;q=0.9], cookie=[JSESSIONID=6BDC934074C10324ADF0E0682F52800B]}
请求体:username=aaa&password=111
//测试下载文件的效果
@RequestMapping("testResponseEntity")
public ResponseEntity testResponseEntity(HttpSession session) throws IOException {
ServletContext servletContext = session.getServletContext();
InputStream inputStream = servletContext.getResourceAsStream("/file/aaa.txt");
byte[] body = new byte[inputStream.available()];
inputStream.read(body);//把数据读取到body中
//获取需要下载的文件的数据类型
String mimeType = servletContext .getMimeType("/file/aaa.txt");
HttpHeaders headers = new HttpHeaders();
// 添加响应头,告诉客户端我回去的数据类型是什么
httpHeaders.add("Content-Type", mimeType);
headers.add("Content-Disposition", "attachment;filename=abc.txt");
HttpStatus statusCode = HttpStatus.OK;
// 第一个参数是你要返回的数据--我们要实现文件下载,就需要把下载的文件字节内容都放body中
// 第二个参数是 响应头
// 第三个参数是你要返回的响应状态码和响应 状态描述 符
ResponseEntity responseEntity = new ResponseEntity(body, headers, statusCode);
return responseEntity;
}
testResponseEntity
关于国际化的需求和对应的基本解决方案:
1、在页面上能够根据浏览器的语言设置的情况对文本(不是指内容),时间和数值进行本地化处理;
解决办法:使用JSTL的fmt标签
2、可以在Bean(控制器controller)中获取国际化资源文件 Locale 对应的消息
解决办法:在Bean(控制器controller)中注入 ResourceBundleMessageSource 的实例,使用其对应的 getMessage 方法即可
3、可以通过超链接切换 Locale,而不再依赖于浏览器的语言设置
解决办法:配置 LocalResolver 和LocaleChangeInterceptor
若项目中使用了 JSTL,则 SpringMVC 会自动把视图由InternalResourceView 转为 JstlView(需要导入 jstl.jar和standard.jar)
若使用 JSTL 的 fmt 标签则需要在 SpringMVC 的配置文件中配置国际化资源文件
1、导入jar包
2、在src下面创建三个国际化文件
i18n.properties:
i18n.username=Username
i18n.password=Password
i18n_zh_CN.properties:
i18n.username=用户名
i18n.password=密码
i18n_en_US.properties:
i18n.username=Username
i18n.password=Password
3、jsp页面使用fmt标签
i18n.jsp:该页面展示用户名,可以不经过控制器,直接跳转到i18n2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--导入fmt国际化标签--%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
i18n
I18N2 PAGE
i18n2.jsp:该页面展示密码,可以不经过控制器,直接跳转到i18n.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--导入fmt国际化标签--%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
i18n
I18N PAGE
4、在springmvc.xml中配置国际化资源文件
启动工程,可以直接浏览器访问:http://localhost:8080/Spring4MVC_01_war_exploded/i18n,切换浏览器语言可以看到效果
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--导入fmt国际化标签--%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
i18n
I18n Page
需要注入:ResourceBundleMessageSource
@Autowired
private ResourceBundleMessageSource messageSource;
@RequestMapping("i18n")
public String testI18n(Locale locale){
String username = messageSource.getMessage("i18n.username", null, locale);
System.out.println("username:"+username);
String password = messageSource.getMessage("i18n.password", null, locale);
System.out.println("password:"+password);
return "i18n";
}
测试控制台能根据浏览器的语言设置显示对应的语言信息。
SpringMVC 还允许装配一个动态更改本地化类型的拦截器,这样通过指定一个请求参数就可以控制单个请求的本地化类型。
AcceptHeaderLocaleResolver:根据 HTTP 请求头的Accept-Language 参数确定本地化类型,如果没有显式定义本地化解析器, SpringMVC 使用该解析器。
CookieLocaleResolver:根据指定的 Cookie 值确定本地化类型
SessionLocaleResolver:根据 Session 中特定的属性确定本地化类型
LocaleChangeInterceptor:从请求参数中获取本次请求对应的本地化类型。
SessionLocaleResolver & LocaleChangeInterceptor 工作原理:
点击切换为中文
点击切换为英文