整合SpringMVC
SpringMVC是时下非常流行的表现层MVC框架,它具有简单易用、开发效率高的特点。SpringMVC也支持使用Velocity作为视图模板,这里我们使用Spring 3.2.17版本作为例子,由于本教程重点讲解的是Velocity,因此对于Spring相关的内容并不会做过多解释,具体用法读者可以参阅Spring的官方文档。
引入相关依赖
首先新建一个Maven工程,这里我们使用maven-archetype-webapp模板,并在pom.xml中添加以下依赖。
<!--velocity-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<version>2.0</version>
</dependency>
<!--spring framework-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.17.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>3.2.17.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.2.17.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.17.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.2.17.RELEASE</version>
</dependency>
<!--javaBean验证API-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
</dependency>
由于我们将使用注解驱动的代码开发方式,这里我们直接将Maven的Java构建和运行JDK设为了最新的1.8版本。
<build>
<finalName>springMVCDemo</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
相关配置文件
在WEB-INF/web.xml内配置以下信息。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>springMVCDemo</display-name>
<!--加载spring Ioc容器上下文-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--spring提供的设置编码的过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置spring mvc核心转发Servlet-->
<servlet>
<servlet-name>demo</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>demo</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
在WEB-INF下建立demo-servlet.xml,注意文件名与web.xml中核心转发Servlet中配置的<servlet-name>应保持一直,即<servlet-name>xxx</servlet-name>和xxx-serlvet.xml保持一致。
demo-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<mvc:annotation-driven />
<mvc:resources location="/resources/" mapping="/resources/**"/>
<context:component-scan base-package="com.gacfox.web.controller" />
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/views/"/>
<property name="configLocation" value="/WEB-INF/velocity.properties"/>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="cache" value="true"/>
<property name="prefix" value=""/>
<property name="suffix" value=".vm"/>
<property name="contentType" value="text/html;charset=utf-8" />
</bean>
</beans>
如上述代码,我们在Spring容器中配置了两个类,分别是Spring提供的Velocity配置器和视图处理器。回忆我们在直接使用Servlet时,配置的是velocity-tool提供的VelocityViewServlet用于加载模板引擎和拦截请求,这里则是用Spring集成的视图模板配置器和处理器来实现,配置内容也基本一致,这里我们同样把视图模板放在WEB-INF/views下,velocity.properties放在WEB-INF下。
此外我们还需要在WEB-INF下创建Spring的配置文件applicationContext.xml。本示例并没有用到该配置文件,但实际项目则一定会用到,这里留空即可。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd">
</beans>
最后我们在WEB-INF下创建velocity.properties,用于设置文本编码。
input.encoding=UTF-8
output.encoding=UTF-8
代码例子
工程搭建和配置文件已经准备就绪,现在我们编写一个SpringMVC控制器。
package com.gacfox.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class TestController {
@RequestMapping(method = RequestMethod.GET, value = "/test")
public String getPage(Model model) {
model.addAttribute("txt", "你好, spring 和 velocity!");
return "test";
}
}
在WEB-INF/views下编写test.vm。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Test Velocity</title>
</head>
<body>
$txt
</body>
</html>
运行结果如下图所示。

实现表单视图绑定
下面我们继续简单介绍一下SpringMVC和Velocity结合进行表单处理。
SpringMVC提供了表单的类型绑定,举例来说,如果获取页面时向request域传递一个User实例,表单提交给控制器时,SpringMVC会自动将表单的字段封装到User对象里,控制器只需要接收一个User参数即可。如果不这样做,虽然控制器也能获得表单提交的字段,但是一旦字段比较多,控制器的参数列表就会非常长,而且没有Bean Validation的注解验证支持。
在Velocity中,我们可以通过配置#springBind宏进行表单的参数绑定,一个简单的登陆例子如下。
login.vm
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Test Velocity</title>
</head>
<body>
<form action="/login" method="post">
<table>
<tr>
<td>
<label for="username">用户名</label>
</td>
<td>
#springBind("user.username")
<input type="text" value="$!{status.value}" name="${status.expression}"/>
#foreach($error in $status.errorMessages)<font color="#FF0000">$error<font/> #end
</td>
</tr>
<tr>
<td>
<label for="password">密码</label>
</td>
<td>
#springBind("user.password")
<input type="password" value="$!{status.value}" name="${status.expression}" />
#foreach($error in $status.errorMessages)<font color="#FF0000">$error<font/> #end
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交" />
</td>
</tr>
</table>
</form>
</body>
</html>
这里我们编写一个叫做User的Java类,并使用验证注解进行字段验证。
package com.gacfox.domain;
import org.springframework.stereotype.Component;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
@Component
public class User {
@Size(min = 5, max = 20, message = "用户名长度应在5-20字符之间")
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "用户名只应包含字母和数字")
private String username;
@Size(min = 6, max = 20, message = "密码长度应在6-20个字符之间")
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
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;
}
}
下面我们编写一个LoginController控制器。
com.gacfox.web.controller.LoginController
package com.gacfox.web.controller;
import com.gacfox.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
@RequestMapping(method = RequestMethod.GET, value = "/login")
public String getPage(Model model) {
User user = new User();
model.addAttribute("user", user);
return "login";
}
@RequestMapping(method = RequestMethod.POST, value = "/login")
public String login(@Validated User user, BindingResult bindingResult, HttpSession httpSession) {
if (bindingResult.hasErrors()) {
return "login";
} else {
httpSession.setAttribute("username", user.getUsername());
return "redirect:/index";
}
}
}
观察代码可知,该控制器接受表单传来的User对象,如果字段验证成功,将重定向到/index。在这个例子中,出于简单起见,我们没有写业务层和持久层,当User的字段符合要求时就算作登陆成功。
此外我们再编写一个IndexController控制器,用于处理对登陆后主页的访问。
package com.gacfox.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpSession;
@Controller
public class IndexController {
@RequestMapping(method = RequestMethod.GET, value = "/index")
public String getPage(HttpSession httpSession, Model model) {
String username = (String) httpSession.getAttribute("username");
if (username == null) {
return "redirect:/login";
} else {
model.addAttribute("username", username);
return "index";
}
}
}
这里有一点要说明,SpringMVC默认没有把session域中的对象绑定到Velocity视图。因此我们在视图模型中显示的添加了一个username属性,然后转发到视图模板。如果读者希望vm模板能直接访问session域,可以在初始化VelocityViewResolver时,加入<property name="exposeSessionAttributes" value="true" />开启session域自动绑定到视图模型。
最后编写index.vm作为主页。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Test Velocity</title>
</head>
<body>
<h1>欢迎你,${username}</h1>
</body>
</html>
当表单校验出错时,如下图所示。

当校验成功后,可以看到成功登陆主页,如下图所示。

后期补充:关于新版本的Spring5
随着时代的发展,在最新版本的Spring5/SpringBoot中官方已经移除了Velocity的API,也就是说,Spring未来不会再支持Velocity这个模板引擎了!说实话Velocity可能确实不适合作为网页的模板引擎,因为语法太过奇怪而且小众了,前端开发人员肯定是无法接受的,目前来看,Java能够集成的页面模板引擎中,Thymeleaf是最好的选择(JSP就把它丢进历史的博物馆吧)。
然而,这并不意味着Velocity就彻底废弃了,在其它需要模板引擎的领域,比如作为一个HTML邮件模板,Velocity还是无可替代的。