15、渲染Web视图(1)(Spring笔记)

一、理解视图解析

在之前的讲解中,我们使用名为InternalResourceViewResolver的视图解析器。在它的配置中,为了得到视图的名字,会使用“/WEB-INF/views/”前缀和“.jsp”后缀,从而确定来渲染模型的JSP文件的物理位置。现在回过头来看看视图解析的基础知识以及Spring提供的其他视图解析器。

Spring MVC定义了一个名为ViewResolver的接口:

public interface ViewResolver{
    View resolveViewName(String viewName, Locale locale) throw Exception;
}

此方法传入一个视图名和Locale对象时,它会返回一个View实例。View是另外一个接口:

public interface View{
    String getContentType();
    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throw Exception;
}

说明:View接口的任务就是接受模型以及Servletrequestresponse对象,并将输出结果渲染到response中。看起来非常简单,我们只需要编写上述两个接口的实现即可,有时候也需要这样做,但是一般来讲,并不需要,Spring提供了多个内置的实现。

Spring自带了十三个视图解析器,能够将逻辑视图名转换为物理实现。

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

说明:Spring 4Spring 3.2支持表中所有的视图解析器。Spring 3.1支持除了Tiles 3 TilesViewResolver之外的所有视图解析器。这里InternalResourceViewResolver一般会用于JSPTilesViewResolver用于Apache Tiles视图,而FreeMarkerViewResolverVelocityViewResolver分别对应FreeMarkerVelocity模版视图。

二、创建JSP视图

Spring提供了两种支持JSP视图的方式:

  • InternalResourceViewResolver会将视图名解析为JSP文件。另外,如果在JSP页面中使用了JSTL,此解析器会将视图名解析为JstlView形式的JSP文件,从而将JSTL本地化好资源bundle变量暴露给JSTL的格式化和信息标签。

  • Spring提供了两个JSP标签库,一个用于表单到模型的绑定,另一个提供了通用的工具类特性。

2.1 配置适用于JSP的视图解析器

有一些视图解析器,如ResourceBundleViewResovler会直接将逻辑视图名映射为特定的View接口实现,而InternalResourceViewResolver所采取的方式并不那么直接。它遵循一种约定,会在视图名上添加前缀和后缀,进而确定一个Web应用中视图资源的物理路径(前面一节已经说明,这里不再细说)。

在之前的13讲中的WebConfig配置类中是这样配置视图解析器的:

@Bean
public ViewResolver viewResolver() {
  //配置JSP视图解析器
  InternalResourceViewResolver resolver = new InternalResourceViewResolver();
  resolver.setPrefix("/WEB-INF/views/");
  resolver.setSuffix(".jsp");
  return resolver;
}

说明:当然我们也可以使用XML进行配置:

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
    p:prefix="/WEB-INF/views/" p:suffix=".jsp" />

说明:解析器配置好之后,它就会将逻辑视图名解析为JSP文件,如下所示:

  • home将会解析为“/WEB-INF/views/home.jsp”
  • productList将会解析为“/WEB-INF/views/productList.jsp”
  • books/detail将会解析为“/WEB-INF/views/books/detail.jsp”

2.1.1 解析JSTL视图

上述对视图解析器的配置都很基础。如果一些JSP使用JSTL标签来处理格式化和信息的话,那么我们会希望InternalResourceViewResolver将视图解析为JstlViewJSTL的格式化标签需要一个Locale对象,以便于恰当地格式化地域相关的值,如日期和货币。信息标签可以借助Spring的信息资源和Locale,从而选择适当的信息渲染到HTML中。

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

@Bean
public ViewResolver viewResolver() {
  //配置JSP视图解析器
  InternalResourceViewResolver resolver = new InternalResourceViewResolver();
  resolver.setPrefix("/WEB-INF/views/");
  resolver.setSuffix(".jsp");
  resolver.setViewClass(org.springframework.web.servlet.view.JstlView.class);
  return resolver;
}

同样,我们也可以使用XML完成这一项内容:

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
    p:prefix="/WEB-INF/views/" p:suffix=".jsp" p:viewClass="org.springframework.web.servlet.view.JstlView"/>

2.2 使用 Spring 的 JSP 库

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

2.2.1 将表单绑定到模型上

Spring的表单绑定JSP标签库包含了十四个标签,它们中的大多数都用来渲染HTML中的表单标签。但是,它们与原生的HTML标签的区别在于它们会绑定模型中的一个对象,能够根据模型中对象的属性填充值。还包含了一个为用户展现错误的标签,它会将错误信息渲染到最终的HTML之中。

为了使用表单绑定,需要在JSP页面中对其进行声明:

<%@ talib uri="http://www.springframework.org/tags/form" prefix="sf" %>

借助Spring表单绑定标签库中所包含的标签,能够将模型对象绑定到渲染后的HTML

JSP标签 描述
<sf:checkbox> 渲染成一个HTML <input>标签,其中type属性设置为checkbox
<sf:checkboxes> 渲染成多个HTML <input>标签,其中type属性设置为checkbox
<sf:errors> 渲染成一个HTML <span>中渲染输入域的错误
<sf:form> 渲染成一个HTML <form>标签,并为其内部标签暴露绑定路径,用于数据绑定
<sf:hidden> 渲染成一个HTML <input>标签,其中type属性设置为hidden
<sf:input> 渲染成一个HTML <input>标签,其中type属性设置为text
<sf:label> 渲染成一个HTML <label>标签
<sf:option> 渲染成一个HTML <option>标签,其中selected属性根据所绑定的值进行设置
<sf:options> 按照绑定的集合、数组或Map,渲染成一个HTML <option>标签的列表
<sf:password> 渲染成一个HTML <input>标签,其中type属性设置为password
<sf:radiobutton> 渲染成一个HTML <input>标签,其中type属性设置为radio
<sf:radiobuttons> 渲染成多个HTML <input>标签,其中type属性设置为radio
<sf:select> 渲染成一个HTML <select>标签
<sf:textarea> 渲染成一个HTML <textarea>标签

下面利用上述标签构建注册表单:

<sf:form method="POST" commandName="spitter">
  First Name: <sf:input path="firstName" /><br/>
  Last Name: <sf:input path="lastName" /><br/>
  Email: <sf:input path="email" /><br/>
  Username: <sf:input path="username" /><br/>
  Password: <sf:input path="password" /><br/>
  <input type="submit" value="Register" />
</sf:form>

说明:<sf:form>会渲染一个HTML <form>标签,但它也会通过commandName属性构建针对某个对象的上下文信息。在其他的表单绑定标签中,会引用这个模型对象的属性。这里我们将commandName属性设置为spitter,因此,在模型中必须要有一个keyspitter的对象,否则的话,表单不能正常渲染。下面看如何确保模型中存在keyspitter的对象(在控制器中设置):

@RequestMapping(value="/register", method=RequestMethod.GET)
public String showRegistrationForm(Model model) {
    model.addAttribute(new Spitter());
    return "registerForm";
}

说明:修改后,模型中的key是根据对象类型推断得到的,也就是spitter

使用Spring标签库后最终的<from>元素如下:

<form id="spitter" action="/spitter/spitter/register" method="POST">
    First Name:<input id="firstName" name="firstName" type="text" value="Jack" />
    ...
</form>

可以看到会自动帮我们填写相关属性。而从Spring 3.1开始,<sf:input>标签能够允许我们指定type属性,还能指定HTML 5特定类型的文本域,如date、rangeemail

Email:<sf:input path="email" type="email" />

这样所渲染得到的HTML如下:

Email:<input id="email" name="email" type="email" value="jack"/>

这里value指定默认值。

2.2.2 展现错误

如果存放在校验错误的话,请求中会包含错误的详细信息,这些信息是与模型数据放在一起的。这里使用<sf:errors>标签让错误抽取出来显示。如将此标签用到registerForm.jsp中的代码片段:

<sf:form method="POST" commandName="spitter">
  First Name: <sf:input path="firstName" />
  <sf:errors path="firstName" /><br />
...
</sf:form>

此时如果有校验错误的话,那么它将会在一个HTML <span>标签中显示错误信息。此时得到的渲染的HTML为:

<form id="spitter" action="/spitter/spitter/register" method="POST">
    First Name:<input id="firstName" name="firstName" type="text" value="Jack" />
    <span id="firstName.errors">size must be between 2 and 30</span>
</form>

可以更近一步,修改错误的样式,使其更加突出显示。为了做到这一点,可以设置cssClass属性:

<sf:form method="POST" commandName="spitter">
  First Name: <sf:input path="firstName" />
  <sf:errors path="firstName" cssClass="error" /><br />
...
</sf:form>

这样error<span>会有一个值为errorclass属性,于是可以为这个类定义CSS样式:

span.error{
  color: red;
}

这样相关提示或错误信息都在每个字段的后面显示,但这样会带来布局的问题。另一种处理校验错误方式就是将所有的错误信息在同一个地方进行显示。我们可以移除每个输入域上的<sf:errors>元素,并将其放到表单的顶部:

<sf:form method="POST" commandName="spitter">
  <sf:errors path="*" element="div" cssClass="error" />
...
</sf:form>

这里和之前不同在于path被设置成了“*”,这表示<sf:errors>展现所有属性的所有错误。同时,将element属性设置成了div,默认情况下是span标签。因为需要一下子显示多个错误信息,使用span标签(行内元素)就不合适了。此时设置样式如下:

div.errors{
  background-color: #ffcccc;
  border: 2px solid red;
}

现在,我们在表单的上方显示所有的错误,这样页面布局可能会更加容易一些。但是,我们还没有着重显示需要修正的输入域。通过为每个输入域设置cssErrorClass属性,这里可以将每个label都替换为<sf:label>,并设置它的cssErrorClass属性:

<sf:form method="POST" commandName="spitter">
    <sf:errors path="*" element="div" cssClass="error" />
    <sf:label path="firstName" cssErrorClass="error">
        First Name
    </sf:label>: 
    <sf:input path="firstName" cssErrorClass="error"/><br/>
...
</sf:form>

说明:<sf:label>标签像其他的表单绑定标签一样,使用path来指定它属于模型对象中的哪个属性。于是它会渲染为如下的HTML <label>元素:

<label for="firstName">First Name</label>

但是设置<sf:label>path属性并没有完成太多的功能,但是我们还设置了cssErrorClass属性。于是渲染如下:

<label for="firstName" class="error">First Name</label>

现在,我们还可以让错误信息更加易读,在Spitter类可以加上message属性:

@NotNull
@Size(min=5, max=16, message="{username.size}")
private String username;

@NotNull
@Size(min=5, max=25, message="{password.size}")
private String password;

@NotNull
@Size(min=2, max=30, message="{firstName.size}"
private String firstName;

@NotNull
@Size(min=2, max=30, message="{lastName.size}")
private String lastName;

@NotNull
@Email(message="{email.valid}")
private String email;

接下来需要做的就是创建一个名为ValidationMessages.properties的文件,并将其放在根类路径之下:

firstName.size=First name must be between {min} and {max} characters long.
lastName.size=Last name must be between {min} and {max} characters long.
username.size=Username must be between {min} and {max} characters long.
password.size=Password must be between {min} and {max} characters long.
email.valid=The email address must be valid.

这里的占位符{min}、{max}会引用@Size注解上所设置的minmax属性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容