您的位置:首页 > 编程语言

基于Ant+Velocity的简单代码生成器的思路与实现

2007-10-14 19:04 543 查看
基于Ant+Velocity的简单代码生成器的思路与实现





(原文:http://www.javaeye.com/topic/30893
在SSH项目中,我们应用了service layer模式,所以针对一个模块,它就存在pojo、dao、daoImpl、service、serviceImpl,再到struts中的action、form。假设设计是面向数据库的,针对一个数据库表,那么就要产生7个java文件,如果还要做异常处理,那么就是8个java文件。如果数据库有50个表,那么就是50*8=400个java文件。工程不小。

至于为什么要用service layer模式,论坛上已有讨论http://www.javaeye.com/topic/29867

然而我们都知道,web中出现最多的操作是CURD,这400个java文件中有多少代码是重复的?几乎占了80%甚至更多。编写这样重复的代码是很枯燥无味的,而且如果是由不同人负责不同的模块的分工方式,程序员编码的风格是各不相同(虽然可能有规范约束,但是最后出来的东西还是避免不了的带有程序员个人风格的)。

所以为了节省时间和精力,便做一个程序来生成程序。
只要配置好你的项目名,你的模块名,模块路径,就可以在几秒之内完成一个模块的CURD代码,同时你可以自定义模板。

这是工具的大概设计思路:



由ant处理编译、生成目录的工作,velocity处理程序模板,contentEngine为核心处理程序。

产生的目录结构和代码路径:
模块名
--子模块1
----model
------businessobject
------dao
--------hibernate
----service
------impl
----view
------action
------form
----Exception
--子模块2
...
其中model/businessobject中是pojo和hbm.xml,这个由hibernate工具根据数据库表产生。

我们假设模块名为course,子模块名为table,类名为CourseMember。因篇幅问题,我们只看一个daoImpl的例子。

首先我们利用建立一个daoImpl的模板
ObjectDaoHibernateImpl.vm

代码

${package_Hibernate}

${import_SQLException}

${import_List}

${import_HibernateCallback}

${import_HibernateObjectRetrievalFailureException}

${import_HibernateDaoSupport}

${import_HibernateException}

${import_Query}

${import_Session}

${import_ObjectNameDao}

${import_ObjectName}

${import_Finder}

${import_Page}

${import_Criteria}

${import_Projections}

/**

* The Hibernate implementation of the <code>${ObjectName}Dao</code>.

*

* @author ${Author}

* @see ${ObjectName}Dao

*/

public class ${ObjectName}DaoHibernateImpl extends HibernateDaoSupport implements ${ObjectName}Dao {

/**

* Default constructor.

*/

public ${ObjectName}DaoHibernateImpl() {

super();

}

/**

* @see ${ObjectName}Dao#save${ObjectName}(${ObjectName})

*/

public ${ObjectName} save${ObjectName}(${ObjectName} ${objectname}) {

this.getHibernateTemplate().save(${objectname});

return ${objectname};

}

/**

* @see ${ObjectName}Dao#get${ObjectName}(String)

*/

public ${ObjectName} get${ObjectName}(String id) {

return (${ObjectName})this.getHibernateTemplate().load(${ObjectName}.class, id);

}

/**

* @see ${ObjectName}Dao#update${ObjectName}(${ObjectName})

*/

public void update${ObjectName}(${ObjectName} ${objectname}) {

this.getHibernateTemplate().update(${objectname});

}

/**

* @see ${ObjectName}Dao#delete${ObjectName}(${ObjectName})

*/

public void delete${ObjectName}(${ObjectName} ${objectname}) {

this.getHibernateTemplate().delete(${objectname});

}

/**

* @see ${ObjectName}Dao#getAll${ObjectName}s()

*/

public List getAll${ObjectName}s() {

return getHibernateTemplate().executeFind(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

StringBuffer sb = new StringBuffer(100);

//sb.append("select distinct ${objectname} ");

sb.append("SELECT ${objectname} ");

sb.append("FROM ${ObjectName} ${objectname} ");

sb.append("order by ${objectname}.id");

Query query = session.createQuery(sb.toString());

List list = query.list() ;

return list;

}

});

}

public Object query(final ${ObjectName} ${objectname},

final int pageNo, final int maxResult) {

return getHibernateTemplate().execute(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

Criteria criteria=session.createCriteria(${ObjectName}.class);

Criteria anothercriteria=session.createCriteria(${ObjectName}.class);

criteria.setProjection(Projections.rowCount());

// if (!${objectname}.get${objectname}Name().equals("")

// && ${objectname}.get${objectname}Name() != null) {

// criteria.add(Expression.ilike("contactName","%"+customerContactForm.getContactName()+"%"));

// anothercriteria.add(Expression.ilike("contactName","%"+customerContactForm.getContactName()+"%"));

// }

Integer count=(Integer)criteria.uniqueResult();

List list=anothercriteria.setFirstResult((pageNo-1)*maxResult).setMaxResults(maxResult).list();

Page page=new Page(count.intValue(), maxResult, pageNo);

return new Finder(list, page);

}

});

}

public boolean deleteBybatch(final String[] chxSong) {

StringBuffer cusIdList = new StringBuffer(200);

cusIdList.append("delete from ${ObjectName} where ${objectName}No=");

for (int i = 0; i < chxSong.length; i++) {

if (i == 0)

cusIdList.append(chxSong[i]);

else

cusIdList.append(" or ${objectName}No=" + chxSong[i]);

}

this.getSession().createQuery(cusIdList.toString()).executeUpdate();

return true;

}

}

render_code();

声明:
1)其中${}是模板语言中的变量,变量的来源一是通过对应的.properties文件,另外是通过参数传递。
2)注释部分因是分页查询条件,这个涉及到具体字段,无法预知,所以需要在产生代码之后程序员根据查询条件自行修改。另外也涉及到个人项目的分页方法,这个根据具体情况自定义模板。

template.properties
公共属性文件,是所有template文件(.vm)的变量声明处,这个会在后面代码中进行设置。
对于属性文件,可有两种方式:
一是针对每一个template模板文件都建立一个属性文件,优点是在后面ant中设置的参数就少了,而且方便修改。缺点是模板文件数量增多,另外公共部分声明重复。
二是设定一个公共属性文件,将特定的变量交给参数传递。
我们这里先用公共属性文件的方式。

代码

Author = Cmas R&D Team

import_Arraylist = import java.util.ArrayList;

import_List = import java.util.List;

import_Set = import java.util.Set;

import_FacesException = import javax.faces.FacesException;

import_BeanUtils = import org.apache.commons.beanutils.BeanUtils;

import_Log = import org.apache.commons.logging.Log;

import_LogFactory = import org.apache.commons.logging.LogFactory;

import_SQLException = import java.sql.SQLException;

import_HibernateCallback = import org.springframework.orm.hibernate3.HibernateCallback;

import_HibernateObjectRetrievalFailureException = import org.springframework.orm.hibernate3.HibernateObjectRetrievalFailureException;

import_HibernateDaoSupport = import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import_HibernateException = import org.hibernate.HibernateException;

import_Query = import org.hibernate.Query;

import_Session = import org.hibernate.Session;

import_Map = import java.util.Map;

import_HashMap = import java.util.HashMap;

import_Iterator = import java.util.Iterator;

import_Criteria=import org.hibernate.Criteria;

import_Projections=import org.hibernate.criterion.Projections;

import_DispatchActionSupport=import org.springframework.web.struts.DispatchActionSupport;

import_Action=import org.apache.struts.action.*;

import_HttpServletRequest=import javax.servlet.http.HttpServletRequest;

import_HttpServletResponse=import javax.servlet.http.HttpServletResponse;

import_BeanUtils=import org.apache.commons.beanutils.BeanUtils;

import_DataIntegrity=import org.springframework.dao.DataIntegrityViolationException;

接下来是ant部分,我们编写build.xml

build.xml

代码

<?xml version="1.0" encoding="UTF-8"?>

<project name="cmas" basedir="../" default="all">

<!-- Project settings -->

<property name="project.distname" value="cmas" /><!-- 设定项目名 -->

<property name="project/operationName" value="course/table" /><!-- 设定模块名,如果有多层以“/”方式扩充,此为目录结构变量设定 -->

<property name="project.operationName" value="course.table" /><!-- 设定模块名,如果有多层以“.”方式扩充,此为包结构变量设定 -->

<property name="ObjectName" value="CourseMember" /><!-- 模块名类名,大写 -->

<property name="objectName" value="courseMember" /><!-- 模块名变量名,小写 -->

<!-- Local system paths -->

<property file="${basedir}/ant/build.properties" /><!-- 设定ant的一些属性,这里我们没有额外的设置,使用默认 -->

<property file="${basedir}/${webroot.dir}/template/build.properties" />

<!--Save_path-->

<!-- 建立目录结构 -->

<mkdir dir="${basedir}/JavaSource/com/bnu/${project.distname}/${project/operationName}/model" />

<mkdir dir="${basedir}/JavaSource/com/bnu/${project.distname}/${project/operationName}/service" />

<mkdir dir="${basedir}/JavaSource/com/bnu/${project.distname}/${project/operationName}/view" />

<!-- 声明目录结构变量 -->

<property name="model.src.dir" location="${basedir}/JavaSource/com/bnu/${project.distname}/${project/operationName}/model" />

<property name="service.src.dir" location="${basedir}/JavaSource/com/bnu/${project.distname}/${project/operationName}/service" />

<property name="view.src.dir" location="${basedir}/JavaSource/com/bnu/${project.distname}/${project/operationName}/view" />

<property name="overwrite" value="false" />

<property name="debug" value="true" />

<property name="webroot.dir" value="${basedir}/WebContent" />

<property name="webinf.dir" value="${webroot.dir}/WEB-INF" />

<property name="build.dir" value="build" />

<!-- 模板文件的声明,这里暂时只写ObjectDaoHibernateImpl -->

<property name="template.dir" value="${webroot.dir}/template" />

<property name="ObjectDaoHibernateImpl.template" value="./ObjectDaoHibernateImpl.vm" />

<property name="template.properties" value="${template.dir}/template.properties" />

<!--设定classpath,这些包不能少-->

<property name="classpath" value="${webinf.dir}/classes/" />

<!-- classpath for JSF 1.1.01 -->

<path id="compile.classpath">

<pathelement path="${webinf.dir}/lib/hibernate3.jar" />

<pathelement path="${webinf.dir}/lib/log4j-1.2.9.jar" />

<pathelement path="${webinf.dir}/lib/commons-beanutils.jar" />

<pathelement path="${webinf.dir}/lib/commons-collections.jar" />

<pathelement path="${webinf.dir}/lib/commons-digester.jar" />

<pathelement path="${webinf.dir}/lib/commons-logging.jar" />

<pathelement path="${webinf.dir}/lib/jsf-api.jar" />

<pathelement path="${webinf.dir}/lib/jsf-impl.jar" />

<pathelement path="${webinf.dir}/lib/jstl.jar" />

<pathelement path="${webinf.dir}/lib/standard.jar" />

<pathelement path="${webinf.dir}/lib/log4j.jar" />

<pathelement path="${webinf.dir}/lib/velocity-1.4.jar" />

<pathelement path="${webinf.dir}/lib/velocity-1.4-dev.jar" />

<pathelement path="${webinf.dir}/classes" />

<pathelement path="${classpath.external}" />

<pathelement path="${classpath}" />

</path>

<!--*****************Build_Dao_Hibernate_Impl*开始创建daoImpl**********************-->

<!-- define your folder for deployment -->

<property name="build_daoimpl.dir" value="build_daoimpl" />

<!-- Check timestamp on files -->

<target name="build_daoimpl_prepare">

<tstamp />

</target>

<!-- Copy any resource or configuration files -->

<target name="build_daoimpl_resources">

<copy todir="${webinf.dir}/classes" includeEmptyDirs="no">

<fileset dir="JavaSource">

<patternset>

<include name="**/*.conf" />

<include name="**/*.properties" />

<include name="**/*.xml" />

</patternset>

</fileset>

</copy>

</target>

<target name="build_daoimpl_init">

<!-- Create the time stamp -->

<tstamp />

<!-- Create the build directory structure used by compile -->

<mkdir dir="${model.src.dir}/dao/hibernate" />

</target>

<!-- Normal build of application -->

<target name="build_daoimpl_compile" depends="build_daoimpl_prepare,build_daoimpl_resources,build_daoimpl_init">

<javac srcdir="${basedir}/JavaSource/com/bnu/exception/" destdir="${webinf.dir}/classes/">

<classpath refid="compile.classpath" />

</javac>

<!--编译核心java文件contentEngine,这个路径根据具体情况设定,也可以在前面对其进行统一声明-->

<javac srcdir="${basedir}/JavaSource/com/bnu/tools" destdir="${webinf.dir}/classes/">

<classpath refid="compile.classpath" />

</javac>

<!--运行contentEngine,参数设定-->

<java classname="com.bnu.tools.ContentEngine">

<classpath refid="compile.classpath" />

<arg value="DaoImpl" />

<arg value="${template.dir}" />

<arg value="${template.properties}" />

<arg value="${ObjectDaoHibernateImpl.template}" />

<arg value="package com.bnu.${project.distname}.${project.operationName}.model.dao.hibernate;" />

<arg value="import com.bnu.${project.distname}.${project.operationName}.model.dao.${ObjectName}Dao;" />

<arg value="import com.bnu.${project.distname}.${project.operationName}.model.businessobject.${ObjectName};" />

<arg value="${objectName}" />

<arg value="${ObjectName}" />

<arg value="${model.src.dir}/dao/hibernate" />

<arg value="${ObjectName}DaoHibernateImpl.java" />

</java>

</target>

<!-- Remove classes directory for clean build -->

<target name="build_daoimpl_clean" description="Prepare for clean build">

<delete dir="${webinf.dir}/classes" />

<mkdir dir="${webinf.dir}/classes" />

</target>

<!-- Build entire project -->

<target name="build_daoimpl_build" depends="build_daoimpl_prepare,build_daoimpl_compile" />

<target name="build_daoimpl_rebuild" depends="build_daoimpl_clean,build_daoimpl_prepare,build_daoimpl_compile" />

<target name="build_daoimpl" depends="build_daoimpl_build">

<delete file="${build_daoimpl.dir}/${project.distname}.war" />

<delete dir="${build_daoimpl.dir}/${project.distname}" />

</target>

<target name="clean" description="clean">

<delete dir="${build.dir}" />

<delete dir="${webinf.dir}/classes" />

<delete dir="${dist.dir}" />

</target>

<target name="all" description="build all" depends="clean,build_daoimpl">

</target>

</project>

render_code();

这里摘取了daoImpl的声明段,重要部分已经做了注释。

核心代码部分,contentEngine文件。

代码

package com.bnu.tools;

import org.apache.velocity.Template;

import org.apache.velocity.VelocityContext;

import org.apache.velocity.app.Velocity;

import org.apache.velocity.exception.ParseErrorException;

import org.apache.velocity.exception.ResourceNotFoundException;

import com.bnu.exception.AppException;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.PrintWriter;

import java.io.StringWriter;

import java.util.Iterator;

import java.util.Properties;

/**

*

* To change the template for this generated type comment go to

* Window>Preferences>Java>Code Generation>Code and Comments

*/

public class ContentEngine {

private VelocityContext context = null;

private Template template = null;

// private String properties = null ;

/**

*

* @param properties

* @throws Exception

*/

public void init(String properties) throws Exception {

if (properties != null && properties.trim().length() > 0) {

Velocity.init(properties);

} else {

Velocity.init();

}

context = new VelocityContext();

}

public void init(Properties properties) throws Exception {

Velocity.init(properties);

context = new VelocityContext();

}

/**

*

* @param key

* @param value

*/

public void put(String key, Object value) {

context.put(key, value);

}

/**

* 设置模版

*

* @param templateFile

* 模版文件

* @throws AppException

*/

public void setTemplate(String templateFile) throws AppException {

try {

template = Velocity.getTemplate(templateFile);

} catch (ResourceNotFoundException rnfe) {

rnfe.printStackTrace();

throw new AppException(" error : cannot find template "

+ templateFile);

} catch (ParseErrorException pee) {

throw new AppException(" Syntax error in template " + templateFile

+ ":" + pee);

} catch (Exception e) {

throw new AppException(e.toString());

}

}

/**

* 设置模版

*

* @param templateFile

* 模版文件

* @throws AppException

*/

public void setTemplate(String templateFile, String characterSet)

throws AppException {

try {

template = Velocity.getTemplate(templateFile, characterSet);

} catch (ResourceNotFoundException rnfe) {

rnfe.printStackTrace();

throw new AppException(" error : cannot find template "

+ templateFile);

} catch (ParseErrorException pee) {

throw new AppException(" Syntax error in template " + templateFile

+ ":" + pee);

} catch (Exception e) {

throw new AppException(e.toString());

}

}

/**

* 转换为文本文件

*/

public String toText() throws AppException {

StringWriter sw = new StringWriter();

try {

template.merge(context, sw);

} catch (Exception e) {

throw new AppException(e.toString());

}

return sw.toString();

}

/**

*

* @param fileName

*/

public void toFile(String fileName) throws AppException {

try {

StringWriter sw = new StringWriter();

template.merge(context, sw);

PrintWriter filewriter = new PrintWriter(new FileOutputStream(

fileName), true);

filewriter.println(sw.toString());

filewriter.close();

} catch (Exception e) {

throw new AppException(e.toString());

}

}

public static void main(String[] args) {

ContentEngine content = new ContentEngine();

try {

Properties p = new Properties();

Properties varp = new Properties();

String path = args[1];

p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path);

p.setProperty(Velocity.RUNTIME_LOG, path + "velocity.log");

content.init(p);

FileInputStream in = new FileInputStream(args[2]);

varp.load(in);

content.setTemplate(args[3], "gb2312");

Iterator it = varp.keySet().iterator();

String key = "";

String value = "";

while (it.hasNext()) {

key = (String) it.next();

value = varp.getProperty(key);

content.put(key, value);

}

if (args[0].equals("DaoImpl")) {

content.put("package_Hibernate", args[4]);

content.put("import_ObjectNameDao", args[5]);

content.put("import_ObjectName", args[6]);

content.put("objectname", args[7]);

content.put("ObjectName", args[8]);

content.toFile(args[9] + '/' + args[10]);//导出的路径,由参数传递。

}

//else 其他情况处理部分,这里省略。

} catch (AppException ae) {

ae.printStackTrace();

} catch (Exception e) {

e.printStackTrace();

}

}

}

render_code();

至此,这个简单的代码生成器的代码就结束了。很显然它还很弱小,充其量也只是半自动。离完善的代码生成器还差很远。拿出来希望对大家有点用处,另外也希望得到各位的指导,大家讨论一下代码生成器的话题。

代码生成的主要几种实现方式(来自Jack Herrington《Code Generation in action》)
1、模板技术:代码生成通常意味着创建具有复杂结构文本类型的文件。为了维护生成器的简单性和完整性,你可以使用文本模板工具,这样你可以把逻辑的定义和逻辑的代码格式相分离,这是最理想的情况。

模板一直以来都是CodeGeneration的主要实现手段。C++使用模板来实现泛型机制。而众多的建模工具和IDE工具都使用模板来重用代码。在Apache组织的旗下,有一个名为Velocity的项目,它的目的就是提供一种易用的模板技术。Velocity使用了一种脚本语言,叫做Velocity Template Language (VTL)。目前,Velocity主要用于生成Web站点的动态内容。此外,还有更出色的FreeMaker,它比Velocity更全面,可以说Velocity是轻量级的模板技术,而Freemaker是一个强大重量的模板引擎,相比velocity而言,其强大的过程调用、递归和闭包回调功能让freemaker可以完成几乎所有我们所想的功能。此外还有很多的模板技术,这里就不一一介绍了。

2、面向属性编程

英文缩写虽然同样都是AOP,但面向属性编程(Attribute-Oriented Programming )和面向切面编程(Aspect-Oriented Programming )可不是一回事。面向属性编程是通过在代码中添加元数据(属性)的方式来自动产生代码,添加功能。而这方面最优秀的软件莫过于xDoclet。

XDoclet 是一个通用的代码生成实用程序,是一个扩展的Javadoc Doclet引擎(现已与Javadoc Doclet独立),XDoclet是EJBDoclet的后继者,而EJBDoclet是由Rickard Oberg发起的。它允许您使用象 JavaDoc 标记之类的东西来向诸如类、方法和字段之类的语言特征添加元数据。随后,它利用这些额外的元数据来生成诸如部署描述符和源代码之类的相关文件。可以让你创建自己的javadoc @tags进而利用XDoclet中的Templet enging基于这些@tags生成源代码或其他文件。

3、MDA(Model Driven Architecture模型驱动架构)

MDA能够将特定的模型转换为特定平台的代码。AndraMDA就是其中的代表者。在具体实现上,AndraMDA主要采用了模板技术,同时,它也利用了xDoclet技术。所以,AndraMDA是在前两项技术的基础上实现CodeGeneration的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: