SpringMvc-渲染Web视图

1.理解SpringMVC视图解析

上一篇讲到SpringMVC执行流程,DispatcherServlet会将模型和视图名交给视图解析器来解析生成响应视图

SpringMVC--小小牛博客

Spring MVC定义了一个名为ViewResolver的接口,它大致如下所示:

1
2
3
public interface ViewResolver{
View resolveViewName(String viewName, Locale locale) throws Exception;
}

当给resolveViewName()方法传入一个视图名和Locale对象时,
它会返回一个View实例。View是另外一个接口,如下所示:

Locale对象,以便于恰当地格式化地域
相关的值,如日期和货币。信息标签可以借助Spring的信息资源和
Locale,从而选择适当的信息渲染到HTML之中。通例如:JSTL能够获得Locale对象以及Spring中配置的信息资源

1
2
3
4
public interface View{
String getContentType();
void render(Map<String,?> model, HttpServletRequest request,HttpServletResponse response) throws Exception;
}

View接口的任务就是接受模型以及Servlet的request和response对象,
并将输出结果渲染到response中。

我们只需实现这两个接口就可以解析视图了,不过Spring自带了13个视图解析器,能够将逻辑视图名转换为物理实现。

视图解析器 描述
BeanNameViewResolver 将视图解析为Spring应用上下文中的bean,其中bean的ID与视图的名字相同
ContentNegotiatingViewResolver 通过考虑客户端需要的内容类型来解析视图,委托给另外一个能够产生对应内容类型的视图解析器
FreeMarkerViewResolver 将视图解析为FreeMarker模板
InternalResourceViewResolver 将视图解析为Web应用的内部资源(一般为JSP)
JasperReportsViewResolver 将视图解析为JasperReports定义
ResourceBundleViewResolver 将视图解析为资源bundle(一般为属性文件)
TilesViewResolver 将视图解析为Apache Tile定义,其中tile ID与视图名称相同。注意有两个不同的TilesViewResolver实现,分别对应于Tiles 2.0和Tiles 3.0
UrlBasedViewResolver 直接根据视图的名称解析视图,视图的名称会匹配一个物理视图的定义
VelocityLayoutViewResolver 将视图解析为Velocity布局,从不同的Velocity模板中组合页面
VelocityViewResolver 将视图解析为Velocity模板
XmlViewResolver 将视图解析为特定XML文件中的bean定义。类似于BeanName-ViewResolver
XsltViewResolver 将视图解析为XSLT转换后的结果

我们常用的也就那么几个,InternalResourceViewResolver一般会
用于JSP,TilesViewResolver用于Apache Tiles视图(布局),而FreeMarkerViewResolver和VelocityViewResolver分别
对应FreeMarker和Velocity模板视图

2.配置JSP视图解析器

InternalResourceViewResolver会将视图名解析为JSP文件。另外,如果在你的JSP页面中使用了JSP标准标签库(JavaServer Pages Standard Tag Library,JSTL)的话,InternalResourceViewResolver能够将视图名解析为JstlView形式的JSP文件,从而将JSTL本地化和资源bundle变量暴露给JSTL的格式化(formatting)和信息(message)标签

1
2
3
4
5
6
7
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}

当然如果喜欢xml配置方式也可以用xml配置,我个人喜欢JavaConf方式

支持JSTL

如果想让InternalResourceViewResolver将视图解析为JstlView,而不是InternalResourceView的话,
那么我们只需设置它的viewClass属性即可:

1
2
3
4
5
6
7
8
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(org.springframework.web.servlet.view.JstlView.class)
return resolver;
}

3.表单校验

大多数项目都会有表单的提交,比如注册、登录等,我们通常会在前台通过JS对用户的输入进行校验,也可后台进行数据校验,
前台校验的优点是不用发送请求,速度快,缺点是不安全,前端校验可以被轻易的绕开。后端的校验恰好相反。
平时我们大多会在两端都进行校验,前端初步校验提高用户友好性,后端校验提高安全性。
下面我们谈谈后端的校验。

最简单粗暴的方法是在后端获取参数后逐个写逻辑判断。这种方式会让我们的Controller很乱很臃肿。与其让校验逻辑弄乱我们的处理器方法,还不如使用Spring对Java校验API(Java Validation API,又称JSR-303)的支持。从Spring 3.0开始,在Spring MVC中提供了对Java校验API的支持。在Spring MVC中要使用Java校验API的话,并不需要什么额外的配置。只要保证在类路径下包含这个Java API的实现即可,比如Hibernate Validator。Java校验API定义了多个注解,这些注解可以放到属性上,从而限制这些属性的值。所有的注解都位于javax.validation.constraints包中

以下是JAVA校验API提供的一些注解:

注解 描述
@AssertFalse 所注解的元素必须是Boolean类型,并且值为false
@AssertTrue 所注解的元素必须是Boolean类型,并且值为true
@DecimalMax 所注解的元素必须是数字,并且它的值要小于或等于给定的BigDecimalString值
@DecimalMin 所注解的元素必须是数字,并且它的值要大于或等于给定的BigDecimalString值
@Digits 所注解的元素必须是数字,并且它的值必须有指定的位数
@Future 所注解的元素的值必须是一个将来的日期
@Max 所注解的元素必须是数字,并且它的值要小于或等于给定的值
@Min 所注解的元素必须是数字,并且它的值要大于或等于给定的值
@NotNull 所注解元素的值必须不能为null
@Null 所注解元素的值必须为null
@Past 所注解的元素的值必须是一个已过去的日期
@Pattern 所注解的元素的值必须匹配给定的正则表达式
@Size 所注解的元素的值必须是String、集合或数组,并且它的长度要符合给定的范围

例如:

实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package spittr;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hibernate.validator.constraints.Email;

public class Spitter {

private Long id;

@NotNull
@Size(min=5, max=16)
private String username;

@NotNull
@Size(min=5, max=25)
private String password;

@NotNull
@Size(min=2, max=30)
private String firstName;

@NotNull
@Size(min=2, max=30)
private String lastName;

@NotNull
@Email
private String email;
//get set 省略
}

Controller 方法

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping(value="/register", method=POST)
public String processRegistration(
@Valid Spitter spitter, //校验输入
Errors errors) {
if (errors.hasErrors()) {
return "registerForm"; //校验出现错误则返回表单
}

spitterRepository.save(spitter);
return "redirect:/spitter/" + spitter.getUsername();
}

如果有校验出现错误的话,那么这些错误可以通过Errors对象进行访问,现在这个对象已作为processRegistration()方法的参数。(很重要一点需要注意,Errors参数要紧跟在带有@Valid注解的参数后面,@Valid注解所标注的就是要检验的参数。)processRegistration()方法所做的第一件事就是调用Errors.hasErrors()来检查是否有错误。

但这时候返回表单页面会丢失之前填的数据

4.使用Spring的JSP库

使用这个标签库可以将错误信息友好的显示在页面,也会保留验证钱的数据(解决上面的问题),当然也可以自己写处理,方式很多,不局限这一种

当为JSP添加功能时,标签库是一种很强大的方式,能够避免在脚本块中直接编写Java代码。Spring提供了两个JSP标签库,用来帮助定义Spring MVC Web的视图。其中一个标签库会用来渲染HTML表单标签,这些标签可以绑定model中的某个属性。另外一个标签库包含了一些工具类标签,我们随时都可以非常便利地使用它们。

具体使用方法请参考Spring in Action 6.2.2

5.使用Apache Tiles视图定义布局

到现在为止,我们很少关心应用中Web页面的布局问题。每个JSP完全负责定义自身的布局,在这方面其实这些JSP也没有做太多工作。假设我们想为应用中的所有页面定义一个通用的头部和底部。最原始的方式就是查找每个JSP模板,并为其添加头部和底部的HTML。但是这种方法的扩展性并不好,也难以维护。为每个页面添加这些元素会有一些初始成本,而后续的每次变更都会耗费类似的成本。更好的方式是使用布局引擎,如Apache Tiles,定义适用于所有页面的通用页面布局。Spring MVC以视图解析器的形式为Apache Tiles提供了支持,这个视图解析器能够将逻辑视图名解析为Tile定义。

来感受下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>

<definition name="base" template="/WEB-INF/layout/page.jsp">
<put-attribute name="header" value="/WEB-INF/layout/header.jsp" />
<put-attribute name="footer" value="/WEB-INF/layout/footer.jsp" />
</definition>

<definition name="home" extends="base">
<put-attribute name="body" value="/WEB-INF/views/home.jsp" />
</definition>

<definition name="registerForm" extends="base">
<put-attribute name="body" value="/WEB-INF/views/registerForm.jsp" />
</definition>

<definition name="profile" extends="base">
<put-attribute name="body" value="/WEB-INF/views/profile.jsp" />
</definition>

<definition name="spittles" extends="base">
<put-attribute name="body" value="/WEB-INF/views/spittles.jsp" />
</definition>

<definition name="spittle" extends="base">
<put-attribute name="body" value="/WEB-INF/views/spittle.jsp" />
</definition>

</tiles-definitions>

具体使用方法请参考Spring in Action 6.3