Spring 学习笔记(九)渲染 Web 视图 (JSP)

2018-12-28  本文已影响9人  Theodore的技术站

理解视图解析

我们知道 Spring MVC 控制器中,请求处理的逻辑和视图中的渲染是解耦的,这是 Spring 的重要特性。如果控制器只通过逻辑视图名来了解视图,Spring 视图解析器会决定用哪一个视图实现来渲染模型。

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

我们平时其实不是很关心这些接口,只是为了了解内部是如何工作。
Spring 提供了多个内置的实现,能适应大部分场景:


Capture.PNG

表中每一项都对应Java Web应用中特定的某种视图技术。InternalResourceViewResolver一般会用于JSP,TilesViewResolver用于Apache Tiles视图,而FreeMarkerViewResolver和VelocityViewResolver分别对应FreeMarker和Velocity模板视图。

创建JSP视图

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

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

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

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

作为样例,考虑一个简单的场景,假设逻辑视图名为home。通用的实践是将JSP文件放到Web应用的WEB-INF目录下,防止对它的直接访问。如果我们将所有的JSP文件都放在“/WEB-INF/views/”目录下,并且home页的JSP名为home.jsp,那么我们可以确定物理视图的路径就是逻辑视图名home再加上“/WEB-INF/views/”前缀和“.jsp”后缀。如图6.1所示。

image.png
当使用@Bean注解的时候,我们可以按照如下的方式配置Internal-ResourceView Resolver,使其在解析视图时,遵循上述的约定。
@Bean
public ViewResolver viewResolver(){
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views");
    resolver.setSuffix(".jsp");
    return resolver;
}

作为替代方案,如果你更喜欢使用基于XML的Spring配置,那么可以按照如下的方式配置InternalResourceViewResolver:

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

解析JSTL视图

到目前为止,我们对InternalResourceViewResolver的配置都很基础和简单。它最终会将逻辑视图名解析为InternalResourceView实例,这个实例会引用JSP文件。但是如果这些JSP使用JSTL标签来处理格式化和信息的话,那么我们会希望InternalResourceViewResolver将视图解析为JstlView。

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

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

@Bean
public ViewResolver viewResolver(){
    InternalResolverViewResolver resolver = new InternalResolverViewResolver();
    resolver.setPrefix("/WEB-INF/views");
    resolver.setSuffix(".jsp");
    resolver.setVIewClass(org.springfaremwork.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"/>

使用Spring的JSP库

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

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

为了使用表单绑定库,需要在JSP页面中对其进行声明:
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
需要注意,我将前缀指定为“sf”,在声明完表单绑定标签库之后,你就可以使用14个相关的标签了。

Capture1.PNG
要在一个样例中介绍所有的这些标签是很困难的,如果一定要这样做的话,肯定也会非常牵强。就Spittr样例来说,我们只会用到适合于Spittr应用中注册表单的标签。具体来讲,也就是<sf:form>、<sf:input>和<sf:password>。在注册JSP中使用这些标签后,所得到的程序如下所示:
<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:password path="password"/><br/>
    <input type="submit" value="Register"/>
</sf:form>

<sf:form>会渲染会一个HTML <form>标签,但它也会通过commandName属性构建针对某个模型对象的上下文信息。在其他的表单绑定标签中,会引用这个模型对象的属性。
在之前的代码中,我们将commandName属性设置为spitter。因此,在模型中必须要有一个key为spitter的对象,否则的话,表单不能正常渲染(会出现JSP错误)。这意味着我们需要修改一下SpitterController,以确保模型中存在以spitter为key的Spitter对象:

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

修改后的showRegistrationForm()方法中,新增了一个Spitter实例到模型中。模型中的key是根据对象类型推断得到的,也就是spitter,与我们所需要的完全一致。

回到这个表单中,前四个输入域将HTML <input>标签改成了<sf:input>。这个标签会渲染成一个HTML <input>标签,并且type属性将会设置为text。我们在这里设置了path属性,<input>标签的value属性值将会设置为模型对象中path属性所对应的值。例如,如果在模型中Spitter对象的firstName属性值为Jack,那么<sf:input path="firstName"/>所渲染的<input>标签中,会存在value="Jack"。
假设有个用户已经提交了表单,但值都是不合法的。校验失败后,用户会被重定向到注册表单,最终的HTML<form>元素如下所示:

<form id="spitter" action="/spitter/spitter/register" method="POST">
    First Name:<input id="firstName" name="firstName" type="text" value="J"/><br/>
    Last Name:<input id="lastName" name="lastName" type="text" value="B"/><br/>
    Email:<input id="email" name="email" type="text" value="jack"/><br/>
    Username:<input id="Username:" name="Username:" type="text" value="jack"/><br/>
    Password:<input id="Password:" name="Password:" type="Password:" value=""/><br/>
    <input type="submit" value="Register"/>
</form>

在校验失败后,表单中会预先填充之前输入的值。但是,这依然没有告诉用户错在什么地方。为了指导用户矫正错误,我们需要使用<sf:errors>。
展现错误
如果存在校验错误的话,请求中会包含错误的详细信息,这些信息是与模型数据放到一起的。我们所需要做的就是到模型中将这些数据抽取出来,并展现给用户。<sf:errors>能够让这项任务变得很简单。
例如,让我们看一下将<sf:errors>用到registerForm.jsp中的代码片段:

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

在这里,
它的path属性设置成了firstName,也就是指定了要显示Spitter模型对象中哪个属性的错误。如果firstName属性没有错误的话,那么<sf:errors>不会渲染任何内容。但如果有校验错误的话,那么它将会在一个HTML <span>标签中显示错误信息。
例如,如果用户提交字母“J”作为名字的话,那么如下的HTML片段就是针对First Name输入域所显示的内容:

First Name:<input id="firstName" name="firstName" type="text" value="J"/>
<span id="firstName.errors">size must be 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/>
...
</sf:form>
image.png

在输入域的旁边展现错误信息是一种很好的方式,这样能够引起用户的关注,提醒他们修正错误。但这样也会带来布局的问题。另外一种处理校验错误方式就是将所有的错误信息在同一个地方进行显示。为了做到这一点,我们可以移除每个输入域上的<sf:errors>元素,并将其放到表单的顶部,如下所示:

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

这个<sf:errors>与之前相比,值得注意的不同之处在于它的path被设置成了“*”。这是一个通配符选择器,会告诉<sf:errors>展现所有属性的所有错误。

同样需要注意的是,我们将element属性设置成了div。默认情况下,错误都会渲染在一个HTML <span>标签中,如果只显示一个错误的话,这是不错的选择。但是,如果要渲染所有输入域的错误的话,很可能要展现不止一个错误,这时候使用<span>标签(行内元素)就不合适了。像<div>这样的块级元素会更为合适。因此,我们可以将element属性设置为div,这样的话,错误就会渲染在一个<div>标签中。

现在,我们在表单的上方显示所有的错误,这样页面布局可能会更加容易一些。但是,我们还没有着重显示需要修正的输入域。通过为每个输入域设置cssErrorClass属性,这个问题很容易解决。我们也可以将每个label都替换为<sf: label>,并设置它的cssErrorClass属性。如下就是做完必要修改后的First Name输入域:

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

现在,我们有了很好的方式为用户展现错误信息。不过,我们还可以做另外一件事情,能够让这些错误信息更加易读。重新看一下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
@size(message="{email.valid}")
private String email;

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


image.png

效果显示:


image.png

Spring通用的标签库
要使用Spring通用的标签库,我们必须要在页面上对其进行声明:

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
Capture.PNG

展现国际化信息
因为全世界有多种语言,你不可能只让网站只支持一种语言,虽然可以通过修改 jsp 文件解决这个问题,但是需要修改大量的 jsp 文件是一件很烦的事情。
对于渲染文本来说,是很好的方案,文本能够位于一个或多个属性文件中。借助<s:message>,我们可以将硬编码的欢迎信息替换为如下的形式:

<h1><s:message code="spittr.welcome"/></h1>

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

@Bean
public MessageSource messageSource(){
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("message");
    return messageSource;
}

在这个bean声明中,核心在于设置basename属性。你可以将其设置为任意你喜欢的值,在这里,我将其设置为message。将其设置为message后,ResourceBundle-MessageSource就会试图在根路径的属性文件中解析信息,这些属性文件的名称是根据这个基础名称衍生得到的。

另外的可选方案是使
用ReloadableResourceBundleMessageSource,它的工作方式与ResourceBundleMessageSource非常类似,但是它能够重新加载信息属性,而不必重新编译或重启应用。如下是配置ReloadableResourceBundle-MessageSource的样例:

@Bean
public MessageSource messageSource(){
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasename("file:///etc/spittr/message");
    messageSource.setCacheSeconds(10);
    return messageSource;
}

要实现国际化就是需要修改属性文件,看你需要那种语言就加那种属性文件。

上一篇下一篇

猜你喜欢

热点阅读