构建自己的代码生成器之二 velocity的使用实例
我想如果要来生成代码,那么一定要使用一种模板的方式来进行生成,这样的代码可维护性高,代码的规范性比较强。所以就找到了apache的velocity这一个开源的模板引擎工具。下面是一些学习的实例,和思考。
首先我在网站上下载一个velocity的包,附下载地址:http://velocity.apache.org/download.cgi。然后解压到本地,可以从目录结构来观察一下,看看是不是有对我们帮助的东西,哈哈。
哈哈,你看到这个目录是不是很高兴,你看到了有文档 docs目录、实例文档examples目录、src源文件目录,其他的就不重要了。我们都是先学会怎么用在来简单的理解一下它的原理
那么接下来就到我们的examples目录下快速学习吧,哈哈
看下我写的实例,其实也就是按照examples的例子来的。
package com.anduo.test.velocity;import java.io.BufferedWriter;import java.io.FileInputStream;import java.io.FileWriter;import java.io.IOException;import java.io.OutputStreamWriter;import java.util.ArrayList;import java.util.Properties;import org.apache.velocity.Template;import org.apache.velocity.VelocityContext;import org.apache.velocity.app.Velocity;public class VelocityTest { public VelocityTest(String templateFile) { /*Properties prop = new Properties(); FileInputStream fis = null; try { fis = new FileInputStream( "test/com/anduo/test/velocity/velocity.properties"); prop.load(fis); } catch (IOException e1) { e1.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } */ /*****注意啦,这里很重要的步骤哦****/ // 初始化一个模板引擎 Velocity.init(); // 将需要的数据加载到模板引擎的上下文中 VelocityContext context = new VelocityContext(); context.put("list", getData()); Template template = null; // 获取模板文件 template = Velocity.getTemplate(templateFile); BufferedWriter writer = null; FileWriter fWriter = null; try { writer = new BufferedWriter(new OutputStreamWriter(System.out)); fWriter = new FileWriter( "test/com/anduo/test/velocity/velocity_test.rs"); if (template != null) { template.merge(context, writer); template.merge(context, fWriter); } } catch (IOException e1) { e1.printStackTrace(); } finally { try { writer.flush(); writer.close(); fWriter.flush(); fWriter.close(); } catch (IOException e) { e.printStackTrace(); } } /* * flush and cleanup */ } /** * 构造数据 * @return */ private ArrayList<String> getData() { ArrayList<String> list = new ArrayList<String>(); list.add("ArrayList element 1"); list.add("ArrayList element 2"); list.add("ArrayList element 3"); list.add("ArrayList element 4"); return list; } public static void main(String[] args) { VelocityTest t = new VelocityTest("test/com/anduo/test/velocity/example.vm"); }}
这运行之前呢,你要保证你必须把需要的文件已经放到了文件的路径下。其实也就一个必要 example.vm的模板文件
那我们来看看这个模板文件是咋个写的呢:
#set( $this = "Velocity")$this is great!#foreach( $name in $list ) $name is great!#end#set( $condition = true)#if ($condition) The condition is true!#else The condition is false!#end
先不要着急去看懂上面代码的语法知识(其实用用就知道了),大概的意思就是 set就是定义某个变量,而foreach呢,就是去循环变量某个值了。
而 $list 我们可以猜猜,应该是从什么地方传值过来的?
运行一下试试~~~
然后F5刷新一下目录,呀,居然多了个文件出来,打开看看的
Velocity is great! ArrayList element 1 is great! ArrayList element 2 is great! ArrayList element 3 is great! ArrayList element 4 is great! The condition is true!
有木有发现跟我们的模板很像吗?
是不是很神奇呢?
让Eadgar来带你解惑,到底这我们执行的过程中发生了什么过程呢?为什么会多出来一个文件,而且里面的内容是这个样子的呢?
从代码来看velocity的使用的流程大概是这个样子的。
1,初始化一个Velocity的对象实例出来,这是java的惯例啊,先把实例整出来,后边才会调用实例的方法来处理啊,是不是?
2,既然实例已经有了,那么我们需要一个模板定义文件啊,是不是?所以这一步很正常的就是加载一个模板文件进来啦。
3,有了模板,然后我们是不是应该弄些数据来供模板使用,然后生成我们想要的文件、流?因此这一步,我们需要把我们的数据文件加载到实例的上下文中
4,有了模板文件,那么我们一定需要一个输出流的东西,是文件、终端都可以的;那么这一步就需要我们创建一个输出流,来接受模板的处理后的输出结果
5,有了这些后,那么我们就需要一个方法来让这个实例为我们工作啦,喂,velocity,叫你呢,你开始工作啊,把我给你的数据通过模板给我输出到我的文件中啊。于是我们要调用某个方法啦。这一步应该就是生产输出流,中间的过程大概是 用我们的数据去替换模板中的变量,然后输出某个数据流。
其实前面的4步都好理解,那么后边的一步是什么样子的呢?是不是发现在咱们的java代码中有那么一行
if (template != null) { template.merge(context, writer); template.merge(context, fWriter); }
看到木有这个merge方法,它是干什么的呢?看看源码啊,看源码可是万能的钥匙啊。
public void merge( Context context, Writer writer, List macroLibraries) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException { /* * we shouldn't have to do this, as if there is an error condition, * the application code should never get a reference to the * Template */ if (errorCondition != null) { throw errorCondition; } // 检查一下数据是不是空的,是空的,俺就不干啦。吼吼~~ if( data != null) { /* * create an InternalContextAdapter to carry the user Context down * into the rendering engine. Set the template name and render() */ // 我要有个从数据中取数据啊,先把上下文给我吧,哈哈。。 InternalContextAdapterImpl ica = new InternalContextAdapterImpl( context ); /** * Set the macro libraries */
ica.setMacroLibraries(macroLibraries); if (macroLibraries != null) { for (int i = 0; i < macroLibraries.size(); i++) { /** * Build the macro library */ try { //找到这个模板的 rsvc.getTemplate((String) macroLibraries.get(i)); } catch (ResourceNotFoundException re) { /* * the macro lib wasn't found. Note it and throw */ rsvc.getLog().error("template.merge(): " + "cannot find template " + (String) macroLibraries.get(i)); throw re; } catch (ParseErrorException pe) { /* * the macro lib was found, but didn't parse - syntax error * note it and throw */ rsvc.getLog().error("template.merge(): " + "syntax error in template " + (String) macroLibraries.get(i) + "."); throw pe; } catch (Exception e) { throw new RuntimeException("Template.merge(): parse failed in template " + (String) macroLibraries.get(i) + ".", e); } } } if (provideScope) { // ica.put(scopeName, new Scope(this, ica.get(scopeName))); } try { ica.pushCurrentTemplateName( name ); ica.setCurrentResource( this ); // 这步应该是关键了,把我的数据附加到流里面去了。 ( (SimpleNode) data ).render( ica, writer); } catch (StopCommand stop) { if (!stop.isFor(this)) { throw stop; } else if (rsvc.getLog().isDebugEnabled()) { rsvc.getLog().debug(stop.getMessage()); } } catch (IOException e) { throw new VelocityException("IO Error rendering template '"+ name + "'", e); } finally { /* * lets make sure that we always clean up the context */ ica.popCurrentTemplateName(); ica.setCurrentResource( null ); if (provideScope) { Object obj = ica.get(scopeName); if (obj instanceof Scope) { Scope scope = (Scope)obj; if (scope.getParent() != null) { ica.put(scopeName, scope.getParent()); } else if (scope.getReplaced() != null) { ica.put(scopeName, scope.getReplaced()); } else { ica.remove(scopeName); } } } } } else { /* * this shouldn't happen either, but just in case. */ String msg = "Template.merge() failure. The document is null, " + "most likely due to parsing error."; throw new RuntimeException(msg); } }
其实这里也看不出来个什么,但是我们找到了最关键的一步是不是:
( (SimpleNode) data ).render( ica, writer);
看来这步就是什么替换什么的啦,应该很很复杂吧?是这样的吗?不要怕,接着往下看:
按住ctrl 点 render 跳到了个方法,看看:
public boolean render( InternalContextAdapter context, Writer writer) throws IOException, MethodInvocationException, ParseErrorException, ResourceNotFoundException { int i, k = jjtGetNumChildren(); for (i = 0; i < k; i++) jjtGetChild(i).render(context, writer); return true; }
好吧,到这儿因该是好,这个方法将根据模板来替换我给他的数据,哈哈。
OK。
理解基本思想来吧,其实Velocity就是通过读去模板文件,然后根据规则,将我们给他的数据替换掉以前模板中的某些变量。哈哈
TAG: