spring(6) 渲染web视图

【0】README
1)本文部分文字描述转自:“Spring In Action(中/英文版)”,旨在review  “spring(6) 渲染web视图” 的相关知识;
 
【1】 理解视图解析
【1.1】视图解析的基础知识以及spring 提供的其他视图解析器
1)spring mvc 定义了一个名为 ViewResolver的接口,如下
public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale)
    throws Exception;
} 
2)当给 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;
3)spring自带了13个视图解析器

对上表的分析(Analysis):
A1)InternalResourceViewResolver: 用于 JSP, TilesViewResolver用于Apache Tiles 视图, FreeMarkerViewResolver and VelocityViewResolver 分别用于 FreeMaker 和 Velocity模板视图;
A2)Thymeleaf: 是一种用来替代JSP 的新兴技术,spring提供了与 Thymeleaf 的原生模板协作的视图解析器;这种模板之所以得到这样的称呼是因为它更像最终产生的HTML,而不是驱动它们的java 代码;

 
【2】创建JSP视图
1)spring提供了两种支持 JSP 的视图(type)
type1)InternalResourceViewResolver 会将视图名解析为 jsp 文件;如果在jsp页面中使用了 JSP 标准标签库(java server page standard tag library)的话,InternalResourceViewResolver 能够将视图名解析为 JstlView 形式的jsp 文件,从而将JSTL 本地化和资源 bundle变量暴露给 JSTL 的格式化和信息标签;
type2)spring提供了两个JSP 标签库: 一个用于表单到模型的绑定,另一个提供了通用的工具类特性;
【2.1】配置适用于JSP的视图解析器
1)看个荔枝:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages={"com.spring.chapter5.spittr.web",
   "com.spring.chapter5.spittr.data"})
public class WebConfig extends WebMvcConfigurerAdapter {
 
  @Bean
  public ViewResolver viewResolver() { // highlight line.
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    resolver.setExposeContextBeansAsAttributes(true);
    return resolver;
  }
 
  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
  }
} 
【2.1.1】解析JSTL 视图(jsp standard tag lib-java标准库标签)
1)intro: InternalResourceViewResolver 最终会将逻辑视图名解析为 InternalResourceView 实例,这个实例会引用jsp文件;
2)如果想让InternalResourceViewResolver  将视图解析为 JstlView,而不是 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;
 }
3)同样,在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" /> <span style="font-family: verdana, Arial, Helvetica, sans-serif; font-size: 14px; background-color: rgb(255, 255, 255);"> </span>

【2.2】使用 spring 的 jsp库
1)标签库的作用: 能够避免在脚本块中直接编写 java 代码;(干货——标签库的作用)
2)spring提供了两个JSP 标签库:一个用于表单到模型的绑定,另一个提供了通用的工具类特性;
 
【2.2.1】将表单绑定到模型上
1)intro:spring 的表单绑定jsp 标签库包含了14个标签,他们中的大多数都用用来渲染HTML 中的表单标签;
2)为了使用表单标签库,需要在jsp 页面上对其声明;
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>


 3)在注册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> 

代码分析(Analysis): <sf:form>标签 会渲染为一个 HTML<form> 标签,通过 commandName 属性构建针对某个模型对象的上下文信息;这就需要我们必须要有一个key==spitter的对象;

 
4)我们可以指定email 域:
Email: <sf:input path="email" type="email" /><br/> 
4.1)得到的 HTML 如下所示:
Email:  
    <input id="email" name="email" type="email" value="jack"/><br/> 

5)为了矫正用户输入错误,需要使用 标签 <sf:errors>
 
【2.2.2】展现错误
1)将 <sf:errors> 用到 registerForm.jsp 中的代码片段中;
<sf:form method="POST" commandName="spitter">
    First Name: <sf:input path="firstName" />
    <sf:errors path="firstName" /><br/>
    ...
</sf:form> 
代码分析(Analysis): 这里path 属性设置为 firstName,即指定了要显示 Spitter 模型对象中哪个属性的的错误;
 
看个荔枝)如果用户提交字母”J“的话,那么如下的HTML 片段就是针对 firstName 输入域所显示的内容:
First Name: <input id="firstName"
                    name="firstName" type="text" value="J"/>
<span id="firstName.errors">size must be between 2 and 30</span> 
2)进一步修改错误的样式,使其更加突出显示;设置 cssClass属性:
<sf:form method="POST" commandName="spitter">
    First Name: <sf:input path="firstName" />
  <sf:errors path="firstName" cssClass="error" />
  <br />
...
</sf:form>
2.1)再定义一个css样式:
span.error {
    color: red;
} 

3)problem+solution:
3.1)problem:在输入域的旁边展现错误信息是一种很好的方式,但是会带来布局问题;
3.2)solution:将所有的错误信息都 在同一个地方进行显示;我们可以移除每个输入域上的 <sf:errors> 元素,并将其放到表单的顶部,如下所示:
<sf:form method="POST" commandName="spitter" >
    <sf:errors path="*" element="div" cssClass="errors" />
        ...
</sf:form> 
对以上代码分析(Analysis):这里的paht被设置为了 “*”,这是一个通配符选择器;会告诉 <sf: errors> 展现所有属性的所有错误;

 
4)通过为每个输入域设置 cssErrorClass 属性,显示需要修正的输入域;
<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> 
对以上代码的分析(Analysis):
A1)<sf:label> 标签:使用path 来指定它属于模型对象中的 哪个属性;在本例中,将其设置为 firstName,它会绑定 spitter对象的 firstName属性;
A2)如果校验错误的话,渲染得到 的<label>元素中,class属性将会被设置为 error:
<label for="firstName" class="error">First Name</label> 
A3)还可以设置一些css 属性  
label.error {
    color: red;
}
input.error {
   
} 

5)如何让错误信息更加易读。可以在校验注解上设置 message属性,使其引用对用户更为友好的信息,这些信息定义在属性文件中;并将该属性文件放在根目录下;
// ValidationMessages.properties 文件内容如下(放置在根类路径下,即 project/src/ 目录下):
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. 

 
【2.2.3】spring 通用的标签库
1)要使用spring通用的标签库,在页面上对其进行声明;
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> 
2)spring的jsp 标签库提供了多个便利的标签;
 

【2.2.4】展现国际化消息
1)考虑首页info:
<h1>Welcome to Spittr!</h1>  
1.1)考虑将其国际化,<s:message>标签就是一个很好地方案;
<h1><s:message code="spittr.welcome" /></h1> 
对以上代码的分析(Analysis):该标签将会工具  key=spittr.welcome 的信息源来渲染文本;
1.2)spring有多个信息源类,都实现了 MessageSource接口,最为常见的是ResourceBundleMessageSource.
1.3)intro to ResourceBundleMessageSource:它会从属性文件中加载信息,该属性文件的名称根据基础名称(base name)衍生而来的。如下的 @Bean 方法配置了 ResourceBundleMessageSource:
@Bean
 public MessageSource messageSource() {
  ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
  messageSource.setBasename("messages"); // 核心在于设置basename属性值.
  return messageSource;
 } 
对以上代码的分析(Analysis):将basename属性值设置为 messages后,SourceBundleMessageSource就会视图在根路径的属性文件中解析信息,这些属性文件的名称是根据这个基础名称衍生得到的;

1.4)还可以使用ReloadableResourceBundleMessageSource,它能够重新加载信息属性,而不必重新编译或重启应用,其配置如下:
@Bean
 public MessageSource messageSource() {
  ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
  messageSource.setBasename("file:///etc/spittr/messages");
  messageSource.setCacheSeconds(10);
  return messageSource;
 } 
对以上代码的分析(Analysis):
A1)关键区别在于 basename 属性设置为在应用的外部查找(而不是像 ResourceBundleMessageSource 在类路径下查找);
A2)basename的属性可以设置在类路径下,文件系统中或web 应用的根路径下查找属性;

2)创建属性文件
2.1)创建默认的属性文件,名为 messages.properties:它要么位于根路径下(使用ResourceBundleMessageSource),要么位于pathname属性指定的路径下 (使用ReloadableResourceBundleMessageSource);
2.2)对于 spittr.welcome 信息来讲,需要如下条目:
spittr.welcome=Welcome to Spittr!
2.3)problem+solution:
2.3.1)problem:如果你不再创建其他信息文件的话,我们所做的事情就是将jsp中硬编码的信息抽取到了属性文件中,仍然是硬编码;
2.3.2)solution:如果想要设置西班牙语的话,需要创建另外一个 名为 message_es.properties 的属性文件
spittr.welcome=Bienvenidos a Spittr!
 

【2.2.5】创建URL
1)intro:<s:url>的主要任务就是创建 URL,然后将其赋值给一个变量或渲染到相应中, 它是 JSTL 中 <c:url> 标签的替代者;(干货——<s:url> 是 JSTL 中 <c:url> 标签的替代者)
2)看几个荔枝:
2.1)<a href="<s:url href="/spitter/register" />">Register</a>,如果应用的servlet上下文名为 spittr,则 <s:url> 将会渲染为:
<a href="/spittr/spitter/register">Register</a> 
2.2)将<s:url> 进行赋值,并使用:  
<s:url href="/spitter/register" var="registerUrl" />
<a href="${registerUrl}">Register</a> 

2.3)default case, URL是在页面作用域内创建的。但通过设置scope 属性,可以让 <s:url> 在应用作用域内,会话作用域内或请求作用域内创建URL:(干货——default case,URL是在页面作用域内创建的)
<s:url href="/spitter/register" var="registerUrl" scope="request" /> 
2.4)如果希望添加参数的话,使用 <s:param>标签;
<s:url href="/spittles" var="spittlesUrl">
    <s:param name="max" value="60" />
    <s:param name="count" value="20" />
</s:url> 
3)各种需求组团袭来
3.1)假设我们要为特定用户的基本信息页面创建一个URL,<s:param> 标签可以承担此任; (干货——这里用到了占位符{username})
<s:url href="/spitter/{username}" var="spitterUrl">
    <s:param name="username" value="jbauer" />
</s:url> 
3.2)解决URL 的转义需求:希望将渲染得到的URL 内容展现在 web 页面上(而不是作为超链接),这就要求 <s:url> 进行HTML 转义,将 htmlEscape属性设置为 true;
<s:url value="/spittles" htmlEscape="true">
    <s:param name="max" value="60" />
    <s:param name="count" value="20" />
</s:url>
所渲染得到的结果为:
/spitter/spittles?max=60&count=20 
3.3)解决在 javascript中使用URL的话,应该将 javascriptEscape 属性设置为 true;
<s:url value="/spittles" var="spittlesJSUrl" javaScriptEscape="true">
    <s:param name="max" value="60" />
    <s:param name="count" value="20" />
</s:url>
<script>
    var spittlesUrl = "${spittlesJSUrl}"
</script>
 
渲染得到的结果为:
<script>
    var spittlesUrl = "\/spitter\/spittles?max=60&count=20"
</script> 
【2.2.6】转义内容(<s:escapeBody> 标签)
1)intro:<s:escapeBody> 标签是一个转义标签;
2)任务来啦:
2.1)任务一:需要将  "<" and ">" 字符替换为 &lt, &gt;<s:escapeBody> 可以做到这一点;
<s:escapeBody htmlEscape="true">
    <h1>Hello</h1>
</s:escapeBody>
所渲染得到的结果为:
&lt;h1&gt;Hello&lt;/h1&gt; 

2.2)通过设置 javaScriptEscape属性,<s:escapeBody> 标签还支持 JavaScript 转义:
<s:escapeBody javaScriptEscape="true">
    <h1>Hello</h1>
</s:escapeBody> 
【3】使用 Apache Tiles视图定义布局
1)intro:为了更好的定义视图布局,引入了布局引擎,如Apache Tiles;(干货——引入了布局引擎);
 
【3.1】配置 Tiles 视图解析器
1)intro:为了在spring 中使用 Tiles,需要配置几个 bean
1.1)需要一个 TilesConfigurer bean:负责定位和加载 Tile 定义并协调生成 Tiles;
1.2)还需要TilesViewResolver bean: 将逻辑视图名称解析为 Tile定义;
2)设置的具体操作:
2.1)需要一个 TilesConfigurer bean:负责定位和加载 Tile 定义并协调生成 Tiles;
@Bean
 public TilesConfigurer tilesConfigurer() {
  TilesConfigurer tiles = new TilesConfigurer();
  tiles.setDefinitions(new String[] {
    "/WEB-INF/layout/tiles.xml"
  });
  tiles.setCheckRefresh(true);
  return tiles;
 }
对以上代码的分析(Analysis):
A1)所要设置的最重要的属性是 definitions:这个属性接收一个 string 类型的 数组,其中每个条目都指定一个 Tile定义的 xml 文件;
A2)我们可以在数组中指定多个文件,并使用通配符;
tiles.setDefinitions(new String[] {
    "/WEB-INF/**/tiles.xml"
}); 
对以上代码的分析(Analysis): 
A1)我们要求 TilesConfigurer  加载 /WEB-INF/ 目录下的所有名字为 tiles.xml 的文件;
A2)上述代码使用了 Ant 风格的通配符(**);  

2.2)还需要TilesViewResolver bean: 将逻辑视图名称解析为 Tile定义;
@Bean
public ViewResolver viewResolver() {
    return new TilesViewResolver();
} 
通过XML 配置的代码如下:
<bean id="tilesConfigurer"
 class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
 <property name="definitions">
  <list>
   <value>/WEB-INF/layout/tiles.xml.xml</value>
   <value>/WEB-INF/views/**/tiles.xml</value>
  </list>
 </property>
</bean>
<bean id="viewResolver"
 class="org.springframework.web.servlet.view.tiles3.TilesViewResolver" /> 
Conclusion)
C1)TilesConfigurer 会加载 Tile定义并与 Apache Tiles协作;
C2)而 TilesViewResolver 会将逻辑视图名称解析为引用 Tile 定义的视图。它是通过查找与 逻辑视图名称相匹配的Tile 定义实现该功能的;
 
【3.1.1】 定义Tiles
1)intro:Apache Tiles 提供了一个文档类型定义(document type definition, DTD),用来在 XML文件中指定Tile的定义。每个定义中需要包含一个 <definition> 元素,这个元素会有一个或多个 <put-attribute>元素;
2)看个荔枝:如下的xml 文档为 Spittr 应用定义了几个 Tile:
<?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">  // hightlight line: 这里用到了继承,碉堡了.
    <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> 
对以上代码的分析(Analysis):
A1)每个 <definition> 元素都定义了一个 Tile,它最终引用的是一个 JSP 模板;
A2)某个 Tile 可能还引用其他的JSP模板,对于base Tile 来讲,引用的是一个头部 jsp 模板 和 一个底部 jsp 模板;
 
3)base Tile 所引用的page.jap 模板如下面程序清单所示:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="t" %>
 <%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<%@ page session="false" %>
<html>
  <head>
    <title>Spittr</title>
    <link rel="stylesheet" 
          type="text/css" 
          href="<s:url value="/resources/style.css" />" >
  </head>
  <body>
    <div id="header">
      <t:insertAttribute name="header" />
    </div>
    <div id="content">
      <t:insertAttribute name="body" />
    </div>
    <div id="footer">
      <t:insertAttribute name="footer" />
    </div>
  </body>
</html> 
对以上代码的分析(Analysis): 重点在于 使用 Tile标签库中的 <t:insertAttribute> jsp 标签来插入其他的模板;插入了 header, body,  footer 的模板;
 

【4】 使用Thymeleaf 模板
1)problem+solution:
1.1)problem:jsp最明显的问题:在于它看起来像 HTML 或 XML,但它其实并不是。大多数的jsp 模板都采用了HTML 的形式,但 又掺杂了各种jsp 标签库的标签,使其变得很是混乱;
1.2)solution:Thymelead模板:能在接收原始HTML 的地方进行编辑和渲染。因为它没有和 servlet规范耦合,因此 Thymeleaf 模板能够进入jsp 所无法涉足的领域;
 
【4.1】配置 Thymeleaf 视图解析器
1)intro:为了要在spring中 使用 Thymeleaf,我们要配置三个启用 Thymeleaf 与 spring集成的bean;
1.1)ThymeleafViewResolver:将逻辑视图名解析为 Thymeleaf 模板视图;
1.2)SpringTemplateEngine:处理模板并渲染结果;
1.3)TemplateResolver: 加载 Thymeleaf模板;

2)如下是声明这些bean 的 java配置;
@Bean
 public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
  ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
  viewResolver.setTemplateEngine(templateEngine);
  return viewResolver;
 }
 
 @Bean
 public TemplateEngine templateEngine(TemplateResolver templateResolver) {
  SpringTemplateEngine templateEngine = new SpringTemplateEngine();
  templateEngine.setTemplateResolver(templateResolver);
  return templateEngine;
 }
 
 @Bean
 public TemplateResolver templateResolver() {
  TemplateResolver templateResolver = new ServletContextTemplateResolver();
  templateResolver.setPrefix("/WEB-INF/templates/");
  templateResolver.setSuffix(".html");
  templateResolver.setTemplateMode("HTML5");
  return templateResolver;
 } 
2.1)通过XML 声明这些bean 的java配置
<bean id="viewResolver" class="org.thymeleaf.spring3.view.ThymeleafViewResolver"
 p:templateEngine-ref="templateEngine" />
<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine"
 p:templateResolver-ref="templateResolver" />
<bean id="templateResolver"
 class="org.thymeleaf.templateresolver.ServletContextTemplateResolver"
 p:prefix="/WEB-INF/templates/" p:suffix=".html" p:templateMode="HTML5" />
Conclusion)不管采用何种配置,Thymeleaf都可以将响应中的模板渲染到 spring mvc 控制器所处理的请求中;
 
3)ThymeleafViewResolver
3.1)intro:ThymeleafViewResolver 是 spring mvc 中 ViewResolver的一个实现类;
3.2)需要注意的是:ThymeleafViewResolver bean 中注入了一个对  SpringTemplateEngine bean 的引用;
3.2)SpringTemplateEngine 会在spring中启用 Thymeleaf引擎,用来解析模板,并基于这些模板渲染结果;
3.4)TemplateResolver会最终定位和查找模板,它也使用了prefix 和 suffix 属性。前缀和后缀将会与 逻辑视图名组合使用,进而定位 Thymeleaf 引擎,它的templateMode属性被设置为 HTML5,这表明我们预期要解析的模板会渲染成 HTML5 输出;
 
【4.2】定义 Thymeleaf 模板
1)intro: Thymeleaf之所以能够发挥作用,是因为它通过自定义的命名空间,为标准的HTML 标签集合添加 Thymeleaf属性;
2)使用 Thymeleaf模板的 home.html源码
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Spittr</title>
    <link rel="stylesheet" type="text/css" th:href="@{/resources/style.css}"></link>
</head>
<body>
    <h1>Welcome to Spittr</h1>
    <a th:href="@{/spittles}">Spittles</a> |
    <a th:href="@{/spitter/register}">Register</a>
</body>
</html> 
 

【4.2.1】借助Thymeleaf实现表单绑定
1)review 表单绑定
1.1) 下面展现了在 registration.jsp 中的firname 输入域:
<sf:label path="firstName"
    cssErrorClass="error">First Name</sf:label>:
<sf:input path="firstName" cssErrorClass="error" /><br/>

2)考虑如下的 Thymeleaf模板片段,它会渲染first name输入域:

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

对以上代码分析(Analysis):

A1)不再使用jsp 标签中的 cssClassName 属性,而是在标准的HTML 标签中上使用 th:class 属性;th:class 属性会渲染成为一个 class属性;
A2)<input> 标签使用了 th:field 属性,用来引用后端对象的 firstname域;
3)以下代码暂时了完整的注册表单模板
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<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>
Listing 6.7 Registration page, using Thymeleaf to bind a form to a
command object Display errors 192 CHAPTER 6 Rendering web views
</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>
</body>
</html>

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值