2012年11月7日星期三

构建自己的代码生成器之二 velocity的使用实例

构建自己的代码生成器之二 velocity的使用实例

我想如果要来生成代码,那么一定要使用一种模板的方式来进行生成,这样的代码可维护性高,代码的规范性比较强。所以就找到了apache的velocity这一个开源的模板引擎工具。下面是一些学习的实例,和思考。

首先我在网站上下载一个velocity的包,附下载地址:http://velocity.apache.org/download.cgi。然后解压到本地,可以从目录结构来观察一下,看看是不是有对我们帮助的东西,哈哈。

2012-11-06_224533

哈哈,你看到这个目录是不是很高兴,你看到了有文档 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: