SpringMvc in Action——渲染Web视图

理解视图解析

将控制器中的请求处理的逻辑和视图的渲染解耦合是Spring MVC的一个重要特性。如果控制器直接负责产生HTML,那么就很难不影响处理逻辑的前提下,维护和更新视图。控制器方法和视图的实现会在模型内容上达成一致,这是两者最大的关联。除此之外,应该两者应该保持足够的距离实现解耦合。
在之前,我们使用了名为InternalResourceViewResolver的视图解析器,在它的配置中,我们使用“/WEB-INF/views”和".jsp"的前后缀来确定JSP的物理位置。现在,我们回过来看一下视图解析的基础知识,以及Spring提供的其他视图解析器。

SpringMVC定义了一个名为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提供了多个内置实现:
在这里插入图片描述
在这里插入图片描述

创建JSP视图

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

	@Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

解析JSTL视图
到目前为止,我们对InternalResourceViewResolver 的配置都很基础和简单,它最终会将逻辑视图名解析为InternalResourceView的实例,这个实例会引用jsp文件。但如果这些jsp使用JSTL标签来处理格式化和信息的话,我们会希望InternalResourceViewResolver 将视图解析为JstlView。
如果想让InternalResourceViewResolver 将视图解析为JstView而不是InternalResourceView,我们只需设置它的viewClass属性即可。

	@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;
    }

使用Spring的JSP库
为了避免在脚本块中直接编写java代码,Spring提供了两个JSP标签库,用来帮助定义Spring MVC Web的视图。其中一个标签库会用来渲染HTML表单标签,这些标签可以绑定model中的某个属性。另外一个标签包含了一些工具类标签。

  1. 将表单绑定到模型上
    Spring的表单绑定JSP标签库包含了14个标签,为了使用表单绑定库,需要在JSP页面中对其进行声明。
    <%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
    我们也可以选择其他前缀,如"form"等等,但是sf很简洁,易于输入。
    这样,我们就可以使用14个相关的标签了:
    在这里插入图片描述
    所以我们这样写:
    修改前:
<form method="POST">
    First Name: <input type="text" name="firstName" /><br/>
    Last Name: <input type="text" name="lastName" /><br/>
    Email: <input type="email" name="email" /><br/>
    Username: <input type="text" name="username" /><br/>
    Password: <input type="password" name="password" /><br/>
    <input type="submit" value="Register" />
</form>

修改后:

<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>替代了<form>,它会通过commandName属性构建针对某个模型model对象的上下文信息。commandName设置为spitter,就是model中必须要有一个key为spitter的对象,否则表单不能正常渲染,会出现jsp错误。为此,我们必须修改刚刚进入这个界面的内容:
修改前:

    @RequestMapping(value = "/register", method = RequestMethod.GET)
    public String showRegitrationForm() {
        return "registerForm";
    }

修改后:

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

修改后的方法中,新增了一个Spitter实例到模型中。模型的key是根据对象类型推断得到的,也就是spitter。
再回到表单,我们不仅修改了sf那么简单,我们还将<input>修改为<sf:input>,并且将type属性修改为text,并且path属性替代了value,path属性表示模型对象中属性的值。
而在password处,我们使用了<sf:password>,为的是不用明文显示。

你可能会问,这样做了有啥好处?
因为我们在代码里,有@Valid注解,如果不符合规范,又会调回原处,但是跳回原处后,你会惊奇,我输入的内容一个不剩了!那就GG了。
我们这里用Model保存,并且用flash属性保存了重定向的值。
最后提交错误的结果就是这样的:
在这里插入图片描述
你会发现,我们的值还在,不过可能你会疑惑,我到底哪里错了?
那么现在我们写入错误提示:使用<sf:errors>

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

在这里插入图片描述
当然,这个字显得有些丑,我们可以添加cssClass属性来设定css样式:

<style type="text/css">
        span.errors{
            color:red;
        }
</style>

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

最后的样式:
在这里插入图片描述
然而,这样可能还是不太好,我们希望框也变红色,并且能自定义显示的错误的文字,而不是“个数必须在5和16之前”

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" session="false"%>
<html>
<head>
    <title>Spitter</title>
    <style type="text/css">
        input.errors{
            background-color: #ffcccc ;
            border: 2px solid red;
            color: darkred;
        }
        span.errors{
            color: red;
        }
    </style>
</head>
<body>
<h1>Register</h1>

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

如果我们要定义message:

package spittr.bean;
public class Spitter {

    private Long id;

    @NotNull
    @Size(min=5, max=16,message = "wrong,please enter the name between 5-16 size")
    private String username;

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

    @NotNull
    @Size(min=2, max=30,message = "${wrong_name}")
    private String firstName;

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

    @NotNull
    @Email(message = "not a email")
    private String email;
	...
}

最后的效果:
在这里插入图片描述

Spring的通用标签库

除了表单绑定标签库之外,Spring还提供了更为通用的JSP标签库。
<%@ taglib prefix="s" uri="http://www.springframework.org/tags" %>
你可以把前缀prefix设置为任意值,不过我们这里还是写为s,更为约定俗成。
在这里插入图片描述
这里我们不会介绍每个标签,而是介绍几个比较有用的。
到现在为止,我们的jsp还是包含了很多硬编码信息,比如<h1>Bounjour Mondo</h1>。我们希望完成国际化,我们可以使用<s:message>

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

按照这里的方式,这个标签会根据Key为spittr.welcom的信息源来渲染文本。
我们需要配置这样的信息源:
暂略,实在用不上。

<s:url>是一个很小的标签,它是<c:url>的替代者:

<a href="<s:url href="/spitter/register"/>">Register</a>

这样如果应用的Servlet上下文名为spittr,那么在相应中将会渲染如下的html:

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

当然它还可以变为模版值:

<s:url href="spitter/register" var="regURL"/>
<a href="${regURL}">Register</a>

还有,更重要的功能:

<s:url href="spitter/register" var="regURL">
	<s:param name="max" value="60">
</s:url>
或者
<s:url href="spitter/{username}"
var="findUser">
	<s:param name="username" value="jbauer">
</s:url>

这样的url分别是:
spitter/register?max=60以及spitter/jbauer

使用Thymeleaf

JSP的副作用是他和HTML太相似了,在视觉上不容易区分。而Thymeleaf模版是原生的,不依赖于标签库,它能够在接受原始HTML的地方进行编辑和渲染。因为它没有与Servlet规范耦合。因此Thtmeleaf模版能够进入JSP所无法涉及的领域。

配置Thymeleaf视图解析器
为了要在Spring中使用Thymeleaf,我们需要配置三个启动Thymeleaf与Spring集成的bean。

  • ThemeleafViewResolver:将逻辑视图名解析为Thymeleaf模版视图。
  • SpringTemplateEngine:处理模版并渲染结果。
  • TemplateResolver:加载Thymeleaf模版。

  @Bean
    public ITemplateResolver templateResolver() {
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setTemplateMode("HTML5");
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setCharacterEncoding("utf-8");
        templateResolver.setOrder(1);

        templateResolver.setCacheable(false);
        return templateResolver;
    }


    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        return templateEngine;
    }


    @Bean
//    public ViewResolver  viewResolver() {
    public ThymeleafViewResolver  viewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        viewResolver.setCharacterEncoding("utf-8");
        return viewResolver;
    }

定义Thymeleaf模版
Thymeleaf很大程度上就是HTML文件。它之所以有自己的作用,是通过自定义的命名空间,为标注的HTML标签集合添加Thymeleaf属性。如下程序清单展现了home.html。

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" 
          type="text/css" 
          th:href="@{/resources/style.css}"></link>
  </head>
  <body>
    <div id="header" th:include="page :: header"></div>
  
    <div id="content">
      <h1>Welcome to Spitter</h1>
  
      <a th:href="@{/spittles}">Spittles</a> | 
      <a th:href="@{/spitter/register}">Register</a>
      
      <br/>
      
      View: <span th:text="${view}">unknown</span>
    </div>
    <div id="footer" th:include="page :: copy"></div>
  </body>
</html>

通过<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">来声明命名空间。
th:href属性,与原生HTML的href属性类似,用来计算动态的url。@{ }表达式中的计算值就是url的路径。
这样就不用使用<s:url>或者<a href="xxx.jsp">的奇怪用法了。

借助Thymeleaf实现表单绑定
为了和之前的表单对比,我们看看registrationForm.jsp:

<sf:lable path="firstName" cssErrorClass="error">FirstName</sf:lable>
<sf:input path="firstName" cssErrorClass="error"/><br/>

现在引入thymeleaf:

<lable th:class="${#fields.hasErrors('firstName')?'error'}">FirstName</lable>
<input type="text" th:field="*{firstName}"
	th:class="${#fields.hasErrors{'firstName'}}?'error'"/><br/>

这里我们不再使用Spring JSP标签中的cssClassName属性,而是在标准的HTML标签上使用th:class属性。它会根据给定值决定,检查firstName域有没有错误,如果没有错误,将不会渲染class属性。
th:field属性用来引用后端对象的firstName域。
以下是完整的表单页面:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" 
          th:href="@{/resources/style.css}"></link>
  </head>
  <body>
    <div id="header" th:include="page :: header"></div>

    <div id="content">
      <h1>Register</h1>
  
      <form method="POST" th:object="${spitter}">
        <div class="errors" th:if="${#fields.hasErrors('*')}">
          <ul>
            <li th:each="err : ${#fields.errors('*')}" 
                th:text="${err}">Input is incorrect</li>
          </ul>
        </div>
        <label th:class="${#fields.hasErrors('firstName')}? 'error'">First Name</label>: 
          <input type="text" th:field="*{firstName}"  
                 th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('lastName')}? 'error'">Last Name</label>: 
          <input type="text" th:field="*{lastName}"
                 th:class="${#fields.hasErrors('lastName')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('email')}? 'error'">Email</label>: 
          <input type="text" th:field="*{email}"
                 th:class="${#fields.hasErrors('email')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('username')}? 'error'">Username</label>: 
          <input type="text" th:field="*{username}"
                 th:class="${#fields.hasErrors('username')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('password')}? 'error'">Password</label>: 
          <input type="password" th:field="*{password}"  
                 th:class="${#fields.hasErrors('password')}? 'error'" /><br/>
        <input type="submit" value="Register" />
      
      
      </form>
    </div>
    <div id="footer" th:include="page :: copy"></div>
  </body>
</html>

你可能会想知道“${}”和"*{}"有什么区别,前一个是变量表达式,而后一个是选择式

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值