Velocity初入门:
前言:
Velocity
是一个Java的模板引擎, 所以在现如今的一部分项目中还是沿用这类模板, 所以Big-man的朋友也在沿用这部分模板, 也就是需要Big-man进行分析一下。- 在现今的软件开发过程中,软件开发人员将更多的精力投入在了重复的相似劳动中。特别是在如今特别流行的
MVC
架构模式中(2014年左右的中华民族),软件各个层次的功能更加独立,同时代码的相似度也更加高。所以我们需要寻找一种来减少软件开发人员重复劳动的方法,让程序员将更多的精力放在业务逻辑以及其他更加具有创造力的工作上。Velocity
这个模板引擎就可以在一定程度上解决这个问题。
Velocity
的介绍:
Velocity
是一个Java的模板引擎, 这个Big-man在前言当中已经提到过;Velocity
通过特定的语法, 可以获取到在java语言中定义的对象, 从而实现界面和java代码的真正分离, 这意味着可以使用velocity
替代jsp
的开发模式了(实际上Big-man的朋友的公司就是采用的这种模式开发的);Velocity
这种开发模式是的前端开发人员可以和Java程序员开发人员同步开发一个遵循MVC
架构的Web
站点, 在实际应用中,velocity
还可以应用于很多其他的场景,这里阐述的是它较为重要的一个作用;
Velocity
的使用介绍:
Velocity
是一个基于Java的模板引擎, 其提供了一个Context
容器, 在java
代码里面Big-man可以往容器中存值, 然后在vm
文件中使用特定的语法获取, 这是velocity
基本的用法, 其与jsp
,fressmarker
并称为三大试图展现技术, 相对于jsp
而言,velocity
对前后端的分离更加彻底: 在vm
文件中不允许出现java
代码, 而jsp
文件中却可以。- 作为一个模板引擎, 除了作为前后端分离的
MVC
展现层,Velocity
还有一些其他用途, 比如源代码生成, 自动email
和转换xml
等;
Velocity
的基本用法:
Velocity
是一个基于 Java 的模板引擎框架,提供的模板语言可以使用在Java中定义的对象和变量上。Velocity
是 Apache 基金会的项目,开发的目标是分离MVC
模式中的持久化层和业务层。但是在实际应用过程中,Velocity
不仅仅被用在了 MVC 的架构中,还可以被用在以下一些场景中。
- 1、Web应用: 开发者在不使用JSP的情况下, 可以用
Velocity
让HTML
具有的动态内容的特性; - 2、源代码生成: 可以用
Velocity
自动生成Java
代码、SQL
或者PostScript
。有很多开源和商业化的软件使用Velocity
引擎模板来进行开发。 - 3、自动Email: 很多软件的用户注册、密码提醒或者报表都是使用
Velocity
来自动生成的。使用Velocity
可以在文本文件里面生成邮件内容, 而不是在Java
代码中拼接字符串。 - 4、转换xml:
Velocity
提供一个叫Anakia
的ant
任务, 可以读取XML
文件并让它能够被Velocity
模板读取。一个比较普遍的应用是将xdoc
文档转换成带样式的HTML
文件。
- 1、Web应用: 开发者在不使用JSP的情况下, 可以用
Velocity
的测试:
- 和学习所有新的语言或者框架的顺序一样,Big-man需要从最初的部署进行开发做起。首先在 Velocity 的官网上下载最新的发布包,之后使用 Eclipse 建立普通的 Java 项目。引入解压包中的 velocity-1.7.jar 和 lib 文件夹下面的 jar 包。这样我们就可以在项目中使用 Velocity 了。
- 但是Big-man采用的是
Maven
下写的, 代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jackdan.velocityTest</groupId>
<artifactId>velocityTestModel</artifactId>
<version>1.0-SNAPSHOT</version>
<!--velocity-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
</project>
- 然后再
reimport
一下就可以了。
书写测试代码:
- 以上的搭建引入只是一个热身, 接下来就是用
Velocity
给各位观众打招呼了, 比如:
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Created by JackDan9 on 2017/10/14.
*/
public class HelloVelocity {
public static void main(String[] args) {
VelocityEngine ve = new VelocityEngine();
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
try {
ve.init();
} catch (Exception e) {
System.out.println("Exception encountered: " + e);
}
VelocityContext ctx = new VelocityContext();
ctx.put("name", "Velocity");
List list = new ArrayList();
list.add("1");
list.add("2");
ctx.put("list", list);
StringWriter sw = new StringWriter();
try {
Template t = ve.getTemplate("hellovelocity.vm");
t.merge(ctx, sw);
} catch (IOException exc) {
System.out.println("Exception encountered: " + exc);
} catch (Exception e) {
System.out.println("Exception encountered: " + e);
}
System.out.println(sw.toString());
}
}
- 在
HelloVelocity
的代码中,首先new
了一个VelocityEngine
类; VelocityEngine
设置了Velocity
使用的一些配置, 相当于初始化;- 在初始化引擎之后就可以读取
hellovelocity.vm
这个模板生成的Template
这个类; - 之后的
VelocityContext
类是配置Velocity
模板读取的内容; - 这个
context
可以存入任意类型的对象或者变量,让template
来读取; - 这个操作就像是在使用
JSP
开发时,往request
里面放入key-value
,让JSP
读取一样; - 接下来就是写
hellovelocity.vm
文件了,这个文件实际定义了Velocity
的输出内容和格式。hellovelocity.vm
的内容如下:
#set($greet = 'hello')
$greet $name
#foreach($i in $list)
$i
#end
$greet
- 对应的输出结果如下:
hello Velocity
1
2
- 在输出结果中我们可以看到,
$name、$greet
都被替换成了在HelloVelocity.java
里面定义的变量,在foreach
语句里面遍历了list
的每一个元素,并打印出来。而$greet
则是在页面中使用#set
定义的变量。
基本模板语言语法使用:
- 在
hellovelocity.vm
里面可以看到很多以#
和$
符开头的内容(在Python语言中#
号是注释符号,但是在这里不是),这些都是Velocity
的语法。 - 在
Velocity
中所有的关键字都是以#
开头的,而所有的变量则是以$
开头。 Velocity
的语法类似于JSP
中的JSTL
,甚至可以定义类似于函数的宏。
变量:
- 和我们所熟知的其他编程语言一样,
Velocity
也可以在模板文件中有变量的概念。 - 1、变量定义:
#set($name ="velocity")
- 等号后面的字符串
Velocity
引擎将重新解析,例如出现以$
开始的字符串时,将做变量的替换。
#set($hello ="hello $name")
- 上面的这个等式将会给
$hello
赋值为"hello velocity"
。
变量的使用:
- 在模板文件中使用
$name
或者${name}
来使用定义的变量。 - Big-man在这里推荐推荐使用
${name}
这种格式,因为在模板中同时可能定义了类似$name
和$names
的两个变量,如果不选用大括号的话,引擎就没有办法正确识别$names
这个变量。 - 对于一个复杂对象类型的变量,例如
$person
,可以使用${person.name}
(JS中的数据语法就是类似的)来访问person
的name
属性。 - 值得注意的是,这里的
${person.name}
并不是直接访问person
的name
属性,而是访问person
的getName()
方法(这个就和JS中的语法不一致了, 底部的实现),所以${person.name}
和${person.getName()}
是一样的。
变量赋值:
- 在第一小点中,定义了一个变量,同时给这个变量赋了值。
- 对于
Velocity
来说,变量是弱数据类型的,可以在赋了一个String
给变量之后再赋一个数字或者数组给它。 - 可以将以下六种数据类型赋给一个
Velocity
变量: 变量引用, 字面字符串, 属性引用, 方法引用, 字面数字, 数组列表。
#set($foo = $bar)
#set($foo =“hello”)
#set($foo.name = $bar.name)
#set($foo.name = $bar.getName($arg))
#set($foo = 123)
#set($foo = [“foo”,$bar])
循环:
- 在 Velocity 中循环语句的语法结构如下:
#foreach($element in $list)
This is $element
$velocityCount
#end
Velocity
引擎会将list
中的值循环赋给element
变量,同时会创建一个$velocityCount
的变量作为计数,从1
开始,每次循环都会加1
。
条件语句:
- 条件语句的语法如下:
#if(condition)
...
#elseif(condition)
…
#else
…
#end
关系操作符:
- Velocity 引擎提供了
AND
、OR
和NOT
操作符,分别对应&&
、||
和!
例如:
#if($foo && $bar)
#end
宏:
Velocity
中的宏可以理解为函数定义。定义的语法如下:
#macro(macroName arg1 arg2 …)
...
#end
- 调用这个宏的语法是:
#macroName(arg1 arg2 …)
- 这里的参数之间使用空格隔开,下面是定义和使用 Velocity 宏的例子:
#macro(sayHello $name)
hello $name
#end
#sayHello(“velocity”)
- 输出的结果为
hello velocity
;
#parse
和#include
:
#parse
和#include
指令的功能都是在外部引用文件,而两者的区别是,#parse
会将引用的内容当成类似于源码文件,会将内容在引入的地方进行解析,#include
是将引入文件当成资源文件,会将引入内容原封不动地以文本输出。分别看以下例子:foo.vm
文件:
#set($name ="velocity")
parse.vm
#parse("foo.vm")
- 输出结果为:
velocity
include.vm
:#include("foo.vm")
- 输出结果为:
#set($name =“velocity”)
自动生成代码的例子:
- 在上个例子中我们可以生成任意的字符串并且打印出来,那为什么我们不能生成一些按照既定格式定义的代码并且写入文件呢。
- 在这里我们以一个实际的
demo
来完成这部分内容。相关内容的源码可以参照附件。 - 这个
demo
的功能是要实现一个学生和老师的管理,实际上都是单张表的维护。 - 我们希望能够只定义
model
层,来生成MVC
的所有代码。 - 在这个
demo
中,只自动生成action
和JSP
的内容,因为现在有很多工具都可以帮助我们自动生成这两个包的代码。 - 首先在
eclipse
中建立一个Java web
工程,在例子中为了方便管理jar
包,使用的是maven
来建立和管理工程。
目录分析:
Java Resource
中放的是Java
源码以及资源文件;Deployed Resources
中放的是 web 相关的文件。- 在
Java
文件中使用了类似Spring
的@Component
和@Autowired
的注解来实现IoC
,使用 @Action 这样的注解实现 MVC,而在 JSP 中则使用了 JSTL 来输出页面。 - 在上图所示的目录中,
annotation
、filter
、framework
和util
这四个package
是作为这个项目框架的,跟业务没有关系,类似于 spring 和 struts 的功能。 - 在实际的项目中我们当然希望能够一开始就编写一个通用的模板文件,然后一下子生成所有的代码,但是很多时候这样做是不可能的,或者说比较困难。
- 为了解决这个问题,我们可以在编写
Velocity
模板文件之前先按照原本的流程编写代码,暂时先忘掉Velocity
。 - 编写的代码应该能够在一个功能上完整的调通涉及
MVC
中所有层次的内容。 - 在这个例子中,先编写好
StudentAction.java
文件,以及上图中webapp
目录中所示的文件。 - 在写好以上代码,同时也能顺利运行之后,我们可以参照之前编写的代码来写模板文件。
- 这里我们来分别看一个
Java
文件和JSP
的例子。
实际代码分析:
ActionTemplate.vm
:
#parse ("macro.vm")
@Action("${classNameLowCase}Action")
public class ${classNameUpCase}Action extends BaseAction{
@Autowired
public ${classNameUpCase}Dao ${classNameLowCase}Dao;
private List<${classNameUpCase}> ${classNameLowCase}s;
private ${classNameUpCase} ${classNameLowCase};
#foreach ($attr in ${attrs})
private ${attr[0]} ${attr[1]};
#end
public String ${classNameLowCase}List() {
${classNameLowCase}s = ${classNameLowCase}Dao.retrieveAll${classNameUpCase}s();
return "${classNameLowCase}List.jsp";
}
...
}
macro.vm
文件中定义了一些使用的宏。JSP
的改造相对于 Java 文件来说稍微有点复杂,因为 JSP 中使用JSTL
取request
中的值也是使用${name}
这样的语法,所以想要输出${name}
这样的字符串而不是被模板引擎所替换,则需要使用转义字符,就像这样:\${name}
。- 为了能够让这个文件中的
table
得到复用,我们将这个文件中的表格单独拿出来,使用#parse
命令来包含。下面是ListJspTemplate.vm
和ListTableTemplate.vm
的内容: ListJspTemplate.vm
:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<%@ include file="includeJS.jsp" %>
<script type="text/javascript">
var pageConfig = {
"list" : {
"action" : "${classNameLowCase}Action!${classNameLowCase}List.action"
}
...
"idName" : "${classNameLowCase}Id"
};
</script>
<script type="text/javascript" src="common.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>${classNameUpCase} List</title>
</head>
<body>
<h1>${classNameUpCase} List</h1>
<div><button id="addButton">Add</button></div>
#parse ("ListTableTemplate.vm")
<div id="modifyDiv"></div>
<div id="addDiv"></div>
</body>
</html>
ListTableTemplate.vm
:
#parse ("macro.vm")
#set($plus = "status.index+1")
<table border="1" style="width: 100%">
<thead>
<tr><th>No.</th>#generateTH($attrs)</tr>
</thead>
<tbody>
<c:forEach var="${classNameLowCase}" items="${${classNameLowCase}s }" varStatus="status" >
<tr ${classNameLowCase}Id="${${classNameLowCase}.id }">
<td>${${plus}}</td>#generateTD($classNameLowCase $attrs)<td>
<button class="modifyButton">Modify</button>
<button class="deleteButton">Delete</button></td></tr>
</c:forEach>
</tbody>
</table>
- 在定义好所有的模板文件之后,需要做的是读取这些文件,然后根据这些文件将
model
的数据类型以及名称设置到context
中,最后将解析出来的内容写到相应的目录中去。这些工作我们放在了一个叫做VelocityGenerator
的类中来做,它的源码如下: TemplateGenerator.java
:
public class VelocityGenerator {
public static void main(String[] args) {
VelocityEngine ve = new VelocityEngine();
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
ve.init();
Template actionTpt = ve.getTemplate("ActionTemplate.vm");
Template listJspTpt = ve.getTemplate("ListJspTemplate.vm");
Template addTpt = ve.getTemplate("AddTemplate.vm");
Template modifyTpt = ve.getTemplate("ModifyTemplate.vm");
VelocityContext ctx = new VelocityContext();
ctx.put("classNameLowCase", "teacher");
ctx.put("classNameUpCase", "Teacher");
String[][] attrs = {
{"Integer","id"},
{"String","name"},
{"String","serializeNo"},
{"String","titile"},
{"String","subject"}
};
ctx.put("attrs", attrs);
String rootPath = VelocityGenerator.class.getClassLoader().getResource("").getFile() + "../../src/main";
merge(actionTpt,ctx,rootPath+"/java/com/liuxiang/velocity/action/TeacherAction.java");
merge(listJspTpt,ctx,rootPath+"/webapp/teacherList.jsp");
merge(addTpt,ctx,rootPath+"/webapp/teacherAdd.jsp");
merge(modifyTpt,ctx,rootPath+"/webapp/teacherModify.jsp");
System.out.println("success...");
}
private static void merge(Template template, VelocityContext ctx, String path) {
PrintWriter writer = null;
try {
writer = new PrintWriter(path);
template.merge(ctx, writer);
writer.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
writer.close();
}
}
}
- 在运行以上代码之后,项目文件夹中将会出现与
Teacher
相关的代码文件。 - 在实际项目中可能不会出现很多这种单张表维护的情况,而且业务逻辑和系统架构会更加复杂,编写模板文件就更加不容易。但是无论多复杂的系统,不同的业务逻辑之间一定或多或少会有相似的代码,特别是在
JSP
和JS
显示端文件中,因为我们在一个系统中要求显示风格、操作方式一致的时候就免不了会有相似内容的代码出现。 - 在总结这些相似性之后我们还是可以使用
Velocity
来帮助我们生成部分内容的代码,而且即使有一些非共性的内容,我们也可以在生成的代码中继续修改。使用 Velocity 的另外一个好处是生成出来的代码更好维护,风格更加统一。
Jackdan9 Thinking