SpringInAction笔记(六)—— 渲染Web视图(上)

1、理解视图解析      

       将控制器中请求处理的逻辑和视图中的渲染实现解耦是Spring MVC的 一个重要特性。如果控制器中的方法直接负责产生HTML的话,就很难在不影响请求处理逻辑的前提下,维护和更新视图。控制器方法和视图的实现会在模型内容上达成一致,这是两者的最大关联,除此之 外,两者应该保持足够的距离。

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

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

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

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

       View接口的任务就是接受模型以及Servlet的request和response对象,并将输出结果渲染到response中。这看起来非常简单。我们所需要做的就是编写ViewResolver和 View的实现,将要渲染的内容放到response中,进而展现到用户的浏览器中。
        但是一般来讲,我们并不需要关心这些接口。Spring提供了多个内置的实现,如表6.1所示,它们能够适应大多数的场景。

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

Spring 4和Spring 3.2支持表6.1中的所有视图解析器。Spring 3.1支持除 Tiles 3 TilesViewResolver之外的所有视图解析器。 
       对于表6.1中的大部分视图解析器来讲,每一项都对应Java Web应用中 特定的某种视图技术。InternalResourceViewResolver一般会 用于JSP,TilesViewResolver用于Apache Tiles视图, 而FreeMarkerViewResolver和VelocityViewResolver分别 对应FreeMarker和Velocity模板视图。 

 

2、创建JSP视图

2.2 使用Spring的JSP库
       Spring提供了两个JSP标签库,用来帮助定义 Spring MVC Web的视图。其中一个标签库会用来渲染HTML表单标签,这些标签可以绑定model中的某个属性。另外一个标签库包含了 一些工具类标签,我们随时都可以非常便利地使用它们。

将表单绑定到model
       Spring的表单绑定JSP标签库共有14种,与原生HTML标签不同的是,它们可以将一个对象绑定在model,并且可以从model对象的属性中获取填充值。标签库同时可以用来与用户交互错误信息。
       要使用表单绑定标签库,需要在JSP页面中进行声明:    

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

注意,这里使用了sf作为前缀。

在注册JSP中使用这些标签后,所得到的程序如下:
registerForm.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
<%@ page session="false" %>
<html>
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" 
          href="http://blog.163.com/yetong1985@126/blog/<c:url value="/resources/style.css" />" >
  </head>
  <body>
    <h1>Register</h1>

    <sf:form method="POST" commandName="spitter" >
      First Name: <sf:input path="firstName" /><br/>
      Last Name: <sf:input path="lastName" /><br/>
      Email: <sf:input type="email" path="email" value="jack@qq.com" /><br/>
      Username: <sf:input type="text" path="username" /><br/>
      Password: <sf:input type="password" path="password" /><br/>
      <input type="submit" value="Register" />
    </sf:form>
  </body>
</html>

使用Spring的form标签主要有两个作用,第一是它会自动的绑定来自Model中的一个属性值到当前form对应的实体对象,默认是command 属性,这样我们就可以在form表单体里面方便的使用该对象的属性了;第二是它支持我们在提交表单的时候使用除GET和POST之外的其他方法进行提交, 包括DELETE和PUT等。
       <sf:form>会渲染会一个HTML <form>标签,但它也会通过 commandName属性构建针对某个模型对象的上下文信息。在其他的表单绑定标签中,会引用这个模型对象的属性。 这里我们将commandName属性设置为spitter。因此,在模型中必须要有一个key为spitter的对象,否则的话,表单不能正常渲染(会出现JSP错误)。这意味着我们需要修改一 下SpitterController,以确保模型中存在以spitter为key的 Spitter对象:

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

回到registerForm.jsp,<sf:input>这个标签会渲染成一个HTML <input>标签,并 且type属性将会设置为text。我们在这里设置了path属 性,<input>标签的value属性值将会设置为模型对象中path属性所 对应的值。例如,如果在模型中Spitter对象的firstName属性值 为Jack,那么<sf:input path="firstName"/>所渲染的 <input>标签中,会存在value="Jack"。 
       值得注意的是,从Spring 3.1开始,<sf:input>标签能够允许我们指 定type属性,这样的话,除了其他可选的类型外,还能指定HTML 5 特定类型的文本域,如date、range和email。
       相对于标准的HTML标签,使用Spring的表单绑定标签能够带来一定 的功能提升,在校验失败后,表单中会预先填充之前输入的值。但是,这依然没有告诉用户错在什么地方。为了指导用户矫正错误,我们需要使用<sf:errors>。 

结果:

 

展现错误
       如果存在校验错误的话,请求中会包含错误的详细信息,这些信息是与模型数据放到一起的。我们所需要做的就是到模型中将这些数据抽取出来,并展现给用户。<sf:errors>能够让这项任务变得很简单。 

 First Name: <sf:input path="firstName" />
        <sf:errors path="firstName" /><br/>

在这里, 。<sf:errors>的path属性设置成了firstName,也就是指定了要显示Spitter 模型对象中哪个属性的错误。如果firstName属性没有错误的话,那么<sf:errors>不会渲染任何内容。但如果有校验错误的话,那么它将会在一个HTML <span>标签中显示错误信息。
Spitter类的firstName是@Size注解需要增加校验错误的提示信息message:

    @NotNull
    @Size(min=2, max=30, message="size must between 2 and 30")
    private String firstName;

例如,如果用户提交字母“J”作为名字的话,那么如下的HTML片段就 是针对First Name输入域所显示的内容:

 First Name: <input id="firstName" name="firstName" 
      type="text" value="J" />
 <span id="firstName.errors">size must between 2 and 30</span>

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

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

现在errors的<span>会有一个值为error的class属性。剩下需 要做的就是为这个类定义CSS样式。在工程WebRoot目录下创建resources文件夹(注意不能把资源放在/WEB-INF目录下,WEB-INF下的文件前端不能直接访问),并新建一个style.css文件,它会将错误信息渲染为红色:

span.error {
	color: red;
}

运行效果如下:

        另外一种处理校验错误方式就是将所有的错误信息在同一个地方进行显示。为了做到这一点,我们可以移除每个输入域上的<sf:errors>元素,并将其放到表单的顶部,如下所示:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
<%@ page session="false" %>
<html>
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" 
          href="http://blog.163.com/yetong1985@126/blog/<c:url value="/resources/style.css" />" >
  </head>
  <body>
    <h1>Register</h1>

    <sf:form method="POST" commandName="spitter" >
      <sf:errors path="*" element="div" cssClass="errors" />
      First Name: <sf:input path="firstName" /><br/>
      Last Name: <sf:input path="lastName" /><br/>
      Email: <sf:input type="email" path="email" value="jack@qq.com" /><br/>
      Username: <sf:input type="text" path="username" /><br/>
      Password: <sf:input type="password" path="password" /><br/>
      <input type="submit" value="Register" />
    </sf:form>
  </body>
</html>

这个<sf:errors>与之前相比,值得注意的不同之处在于它的path被设置成了“*”。这是一个通配符选择器,会告诉<sf:errors>展现 所有属性的所有错误。 同样需要注意的是,我们将element属性设置成了div。默认情况 下,错误都会渲染在一个HTML <span>标签中,如果只显示一个错误的话,这是不错的选择。但是,如果要渲染所有输入域的错误的 话,很可能要展现不止一个错误,这时候使用<span>标签(行内元 素)就不合适了。像<div>这样的块级元素会更为合适。因此,我们可以将element属性设置为div,这样的话,错误就会渲染在一个 <div>标签中。 

style.css,它具有红色的边框和浅 红色的背景:

span.error {
	color: red;
}

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

运行结果:

 但是,我们还没有着重显示需要修正的输入域。通过为每 个输入域设置cssErrorClass属性,这个问题很容易解决。我们也 可以将每个label都替换为<sf: label>,并设置它的 cssErrorClass属性。如下就是做完必要修改后的First Name输入域:

      <sf:label path="firstName" 
          cssErrorClass="error">First Name</sf:label>: 
          <sf:input path="firstName" /><br/>

 <sf: label>标签像其他的表单绑定标签一样,使用path来指定它属于模型对象中的哪个属性。在本例中,我们将其设置 为firstName,因此它会绑定Spitter对象的firstName属性。假设没有校验错误的话,它将会渲染为如下的HTML<label>元素:

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

设置<sf:label>的path属性并没有完成太多的功能,但是,我们还同时设置了cssErrorClass属性。如果它所绑定 的属性有任何错误的话,在渲染得到的<label>元素中,class属性将会被设置为error,如下所示: 

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

如下的CSS会将文本标记渲染为红色,并将输入域设置为浅红色背景:

label.error {
	color: red;
}

input.error {
	background-color: #ffcccc;
}

运行效果如下:

重新看一 下Spitter类,我们可以在校验注解上设置message属性,使其引 用对用户更为友好的信息,而这些信息可以定义在属性文件中: 

    //非空,5到16个字符
    @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
    private String email;

对于上面每个域,我们都将其@Size注解的message设置为一个字 符串,这个字符串是用大括号括起来的。如果没有大括号的话,message中的值将会作为展现给用户的错误信息。但是使用了大括号之后,我们使用的就是属性文件中的某一个属性,该属性包含了实际的信息。
       接下来需要做的就是创建一个名为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.

ValidationMessages.properties文件中每条信息的key值对应于注解中message属性占位符的值。同时,最小和最大长度没有硬编码在 ValidationMessages.properties文件中,在这个用户友好的信息中也有自己的占位符——{min}和{max}——它们会引用@Size注解上所设 置的min和max属性。 当用户提交的注册表单校验失败的话,他们在浏览器中应该可以看到 图6.3所示的界面。

Spring的通用标签库

展现国际化信息
       到现在为止,我们的JSP模板包含了很多硬编码的文本。这其实也算 不上什么大问题,但是如果你要修改这些文本的话,就不那么容易了。而且,没有办法根据用户的语言设置国际化这些文本。
       例如,考虑首页中的欢迎信息:
       对于渲染文本来说,是很好的方案,文本能够位于一个或多个属性文 件中。借助<s:message>,我们可以将硬编码的欢迎信息替换为如 下的形式:

<h1><s:message code="spitter.welcome" text="Welcome" /></h1>

<s:message>将会根据key为spittr.welcome 的信息源来渲染文本。因此,如果我们希望<s:message>能够正常 完成任务的话,就需要配置一个这样的信息源。 Spring有多个信息源的类,它们都实现了MessageSource接口。在这些类中,更为常见和有用的 是ResourceBundleMessageSource。它会从一个属性文件中加 载信息,这个属性文件的名称是根据基础名称(base name)衍生而来 的。如下的@Bean方法配置了 ResourceBundleMessageSource:

@Bean	  
public MessageSource messageSource() {
	ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
	//在根路径的属性文件messages.properties中解析信息
	messageSource.setBasename("messages");
	return messageSource;
}

在这个bean声明中,核心在于设置basename属性。你可以将其设置 为任意你喜欢的值,在这里,将其设置为message。将其设置为message后,ResourceBundle-MessageSource就会试图在根路径的属性文件中解析信息,这些属性文件的名称是根据这个基础名称衍生得到的。
       另外的可选方案是使 用ReloadableResourceBundleMessageSource,它的工作方式与ResourceBundleMessageSource非常类似,但是它能够重新加载信息属性,而不必重新编译或重启应用。如下是配 置ReloadableResourceBundle-MessageSource的样例:

@Bean	  
public MessageSource messageSource() {
	ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
	//在web应用的根路径下
	messageSource.setBasename("/resources/messages");
	messageSource.setCacheSeconds(10);
	return messageSource;	  
}

basename属性可以设置为在类路径下(以“classpath:”作 为前缀)、文件系统中(以“file:”作为前缀)或Web应用的根路径下(没有前缀)查找属性。在这里,我将其配置为在Web应用根路径的resources目录下的属性文件中查找信息,并且基础的文件名为“message”。
/spittr/WebRoot/resources/messages.properties:

spitter.welcome=Welcome to Spitter!

创建URL
       <s:url>会接受一个相对于Servlet上下文的 URL,并在渲染的时候,预先添加上Servlet上下文路径。例如,考虑如下<s:url>的基本用法:

<a href="http://blog.163.com/yetong1985@126/blog/<s:url href="/spitter/register" />">Register</a>

如果应用的Servlet上下文名为spittr,那么在响应中将会渲染如下 的HTML:

 <a href="/spittr/spitter/register">Register</a>

这样,我们在创建URL的时候,就不必再担心Servlet上下文路径是什么了,<s:url>将会负责这件事。 另外,我们还可以使用<s:url>创建URL,并将其赋值给一个变量供模板在稍后使用:

<s:url value="/spitter/register" var="registerUrl" /> 
<a href="http://blog.163.com/yetong1985@126/blog/${registerUrl}">Register</a>

默认情况下,URL是在页面作用域内创建的。但是通过设置scope属 性,我们可以让<s:url>在应用作用域内、会话作用域内或请求作 用域内创建URL:

<s:url value="/spitter/register" var="registerUrl" scope="request"/>

如果希望在URL上添加参数的话,那么你可以使用<s:param>标 签。比如,如下的<s:url>使用两个内嵌的<s:param>标签,来设置“/spittles”的max和count参数:

<s:url value="/spitter/register" var="registerUrl" scope="request">
  <s:param name="max" value="60' />
  <s:param name="count" value="20' />
</s:url>

如果我们需要创建带有路径 (path)参数的URL该怎么办呢?我们该如何设置href属性,使其 具有路径变量的占位符呢?
例如,假设我们需要为特定用户的基本信息页面创建一个URL。那没 有问题,<s:param>标签可以承担此任:

<s:url value="/spitter/{username}" var="registerUrl">
  <s:param name="username" value="jbauer' />
</s:url>

当href属性中的占位符匹配<s:param>中所指定的参数时,这个参 数将会插入到占位符的位置中。如果<s:param>参数无法匹配href 中的任何占位符,那么这个参数将会作为查询参数。 
      <s:url>标签还可以解决URL的转义需求。例如,如果你希望将渲染 得到的URL内容展现在Web页面上(而不是作为超链接),那么你应 该要求<s:url>进行HTML转义,这需要将htmlEscape属性设置 为true。例如,如下的<s:url>将会渲染HTML转义后的URL

<s:url value="/spittes" htmlEscape="true">
  <s:param name="max" value="60' />
  <s:param name="count" value="20' />
</s:url>

所渲染的URL结果如下所示:
/spitter/spittles?max=60&amp;count=20

 

导入Tiles相关的jar包:
  tiles-api-3.0.8.jar
  tiles-core-3.0.8.jar
  tiles-jsp-3.0.8.jar
  tiles-servlet-3.0.8.jar
  tiles-template-3.0.8.jar
       slf4j-api-1.7.25  (java.lang.ClassNotFoundException: org.slf4j.LoggerFactory)
       commons-digester-2.1 (java.lang.ClassNotFoundException: org.apache.commons.digester.Rule)
       commons-beanutils-1.9.3 (ClassNotFoundException: org.apache.commons.beanutils.MethodUtils)
       tiles-request-api-1.0.7.(java.lang.NoClassDefFoundError: org/apache/tiles/request/render/Renderer)
       tiles-request-servlet-1.0.7.(ClassNotFoundException: org.apache.tiles.request.servlet.ServletApplicationContext)
       tiles-request-jsp-1.0.7 (ClassNotFoundException: org.apache.tiles.request.jsp.autotag.JspAutotagRuntime)
       tiles-autotag-core-runtime-1.2 (ClassNotFoundException: org.apache.tiles.autotag.core.runtime.AutotagRuntime)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值