spring 4.0 原生支持13种视图解析器

spring in action 4 第6章 视图分发_action

从spring3.2版本开始,spring支持上述13种解析器,但是spring 3.1不支持Tiles 3 TilesViewResolver,其它的12种都支持.

视图解析器所在的配置文件

@Configuration
@EnableWebMvc
@ComponentScan("_5BuildingSpringwebapplications")
public class WebConfig extends WebMvcConfigurerAdapter {
	@Bean
	public ViewResolver viewResolver() {
		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();
	}
}

配置视图解析器核心代码

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

对应在xml中配置时,如下示,配置在spring应用上下文中

 	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">     
 		<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver" />     
 		<property name="prefix" value="/WEB-INF/views/"/>     
 		<property name="suffix" value=".jsp"/>     
 	</bean>

下面是示例的逻辑视图名与物理文件的对应关系,注意最后的示例与前两个示例的区别

home resolves to /WEB-INF/views/home.jsp
productList resolves to /WEB-INF/views/productList.jsp
books/detail resolves to /WEB-INF/views/books/detail.jsp

通常在使用jsp的时候都使用JSTL,但是InternalResourceViewResolver的功能相对比较简单,因此需要将其替换为JstlView,如下示,

在使用jstlview之前,需要在项目里引入jstl相关的jar包

build.gradle配置

compile "javax.servlet:jstl:$jstlVersion"
gradle.properties配置
jstlVersion = 1.2

InternalResourceViewResolver被JstlView替换

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

对应的xml配置

 	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 
 		<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> 
 		<property name="prefix" value="/WEB-INF/views/"/> 
 		<property name="suffix" value=".jsp"/> 
 	</bean>

spring提供两种jsp标签库.

One tag library renders HTML
form tags that are bound to a model attribute. The other has a hodgepodge of utility
tags that come in handy from time to time.

第一种标签库,即表单绑定的jsp标签库包含14个标签,这些标签与html原生标签是有区别的,spring的标签可以获取model对象的属性值.其中一个标签可以将error信息反馈到结果页面中,即下文中提到的errors.

使用这些Spring标签的方法就是在jsp页面中声明.如下示

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

在上述的声明语句中,prefix="sf"里的"sf"由用户自定义,用于与原生的html标签做区别,比如也可以定义成这样,

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

这14个标签如下图示,注意标签前的前缀,这里的前缀是sf,因为之前在spring标签声明时定义了prefix="sf",如果声明时定义的不是sf,在使用时就不能照搬下面的用法了.

spring in action 4 第6章 视图分发_in_02

很难在一个页面中把上述所有标签的使用都示例出来.下面将结合书中的示例系统说明<sf:form>, <sf:input>, and <sf:password>这三个标签的使用方法.

在第5章中写过一个用户注册页面,当时的写法如下所示 

<body>    
	<h1>Register</h1>    
	<form method="POST">    
		First Name: <input type="text" name="firstName" /><br /> 
		Last Name: <input  type="text" name="lastName" /><br /> 
		Username: <input type="text"   name="username" /><br /> 
		Password: <input type="password"  name="password" /><br /> <input type="submit" value="Register" />    
	</form>    
</body>

下面是使用了spring标签的写法

<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 path="email" /><br />
        Username: <sf:input path="username" /><br />
        Password: <sf:password path="password" /><br />
	<input type="submit" value="Register" />
	</sf:form>
</body>

<sf:form> 标签与HTML <form> 标签的功能类似,但是它指定了commandName属性,该属性绑定了一个model对象.这要求逻辑视图包含一个同名的model对象,因此需要对原来的控制器做相应的修改

原contrller SpitterController.java中的showRegistrationForm方法

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

修改后的代码,可以看到修改后,该方法在返回逻辑视图前在逻辑视图中增加了一个名为spitter的model属性,其实可以不用明确指定对象名,因为jsp页面中的对象名与类名一致,这与spring指定的默认对象名相同(类名首字母小写).因此添加属性的代码可写成model.addAttribute(new Spitter())

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

回到使用的spring标签的jsp页面中,First Name: <sf:input path="firstName" />表示该输入框绑定的是new Spitter()类的firstName成员变量.注意,该成员变量名JSP页面中不能写错,如果写成First Name: <sf:input path="firstNamexxxx" />,那么运行时会报错.依此类推,

Last Name: <sf:input path="lastName" />表示该输入框对应的是new Spitter()类的lastName成员变量..等等.再次声明,成员变量名在JSP页面中不要写错.

假如在输入框中输入如下内容

spring in action 4 第6章 视图分发_in_03

那么该表单提交后,将向后台传递一个firstName为11,lastName为22,email为11@22.com,username为1122的Spitter的对象.

在SpitterController.java的processRegistration方法中增加了一行输出可以用来验证对象的成员变量是否是我们在jsp页面的输入

spring in action 4 第6章 视图分发_action_04

输出结果

spring in action 4 第6章 视图分发_in_05

注意,如果在域对象spitter的定义中加入了校验,上述的结果是不可能成功的,因为我们在jsp页面中的输入并不满足校验的条件,因此需要将spitter定义中的校验取消掉,如下图示

spring in action 4 第6章 视图分发_action_06补充一个第5章中的知识点.回到上述的JSP页面中,可以看到该form表单中并没有写明action属性,如果没有写明的话,提交该表单时将默认返回打开该页面时使用逻辑视图名,但是与打开该页面时视图对应的get方法不同,提交表单时要求逻辑视图对应post方法.下图示

spring in action 4 第6章 视图分发_srping_07

回到校验的话题,如果在上述实验中我们没有取消在域对象spitter定义时给予的校验,那么在校验失败时,如何在jsp页面中向用户展示错误信息呢?

下面先还原在spitter对象定义中给予的检验,如下图示

package _5BuildingSpringwebapplications.domain;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hibernate.validator.constraints.Email;

public class Spitter {
	private Long id;

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

	public Spitter() {
	}

	public Spitter(String username, String password, String firstName, String lastName, String email) {
		this(null, username, password, firstName, lastName, email);
	}

	public Spitter(Long id, String username, String password, String firstName, String lastName, String email) {
		this.id = id;
		this.username = username;
		this.password = password;
		this.firstName = firstName;
		this.lastName = lastName;
		this.email = email;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	@Override
	public boolean equals(Object that) {
		return EqualsBuilder.reflectionEquals(this, that, "firstName", "lastName", "username", "password", "email");
	}

	@Override
	public int hashCode() {
		return HashCodeBuilder.reflectionHashCode(this, "firstName", "lastName", "username", "password", "email");
	}
}

在classpath根目录下新建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.

registerForm.jsp

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ 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>Spittr</title>
<link rel="stylesheet" type="text/css"
	href="<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" />
<!--             	<span id="firstName.errors">size must be between 2 and 30</span> -->
            <br />
            Last Name: <sf:input path="lastName" />
<!--             <span id="lastName.errors">size must be between 2 and 30</span> -->
            <br />
            Email: <sf:input path="email" />
<!--             <span id="email.errors">size must be between 2 and 30</span> -->
            <br />
            Username: <sf:input path="username" />
<!--             <span id="username.errors">size must be between 2 and 30</span> -->
            <br />
            Password: <sf:password path="password" />
<!--             <span id="password.errors">size must be between 2 and 30</span> -->
            <br />
	<input type="submit" value="Register" />
	</sf:form>
</body>
</html>

结果

spring in action 4 第6章 视图分发_srping_08

除了表单绑定的标签库外,spring还提供另一种JSP标签库.该标签库是spring最早包含的标签库.

引用方法,在JSP页面中添加

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

与表单绑定的标签库在引用方法上的区别在于uri上,

通用标签库的uri是

uri="http://www.springframework.org/tags"

而表单绑定的标签库的引用uri是

uri="http://www.springframework.org/tags/form"

标签描述

spring in action 4 第6章 视图分发_action_09

上图中的某些标签在表单绑定的标签库中已经被淘汰了.<s:bind>比如.