编写自己的代码生成工具五:代码生成实现

分类: 开源软件 1人评论 selfly 1年前发布

前面,我们已经准备好了一切,配置信息、任务信息、数据库查询出来的表信息等等都已经拿到手了,接下来就是根据这些信息来进行代码生成了。

代码生成说白了就是弄个模板,字符串的替换而已,无非就是怎么样才能实现的比较优雅,在这里我们使用了velocity,可以省下大量繁琐的事情。不多说了,看源码,源码的实现方式和数据库查询类似,首先也是定义一个接口:

/**
 * 代码生成接口
 *
 * User: liyd
 * Date: 13-11-28
 * Time: 下午5:35
 */
public interface EasyCodeGenerator {
    /**
     * 代码生成方法
     * @param table the table
     * @param task the task
     * @param context the context             
     */
    public void doGenerate(Table table, Task task, VelocityContext context);
}

实现接口的抽象类:

/**
 * 代码生成接口抽象实现
 * 
 * User: liyd
 * Date: 13-12-6
 * Time: 下午4:56
 */
public abstract class AbstractEasyCodeGenerator implements EasyCodeGenerator {
    /**
     * 代码生成方法
     *
     * @param table the table
     * @param task  the task
     * @param context the context
     */
    @Override
    public void doGenerate(Table table, Task task, VelocityContext context) {
        context.put("serialVersionUID", getSerialVersionUID() + "L");
        StringBuilder sbTemp = new StringBuilder(FileUtils.getTemplate(task.getTemplate()));
        //运行插件
        this.executePlugin(table, task, context, sbTemp);
        this.generate(table, task, context, sbTemp);
        String template = VelocityUtils.parseString(sbTemp.toString(), context);
        String targetDir = EasyCodeContext.getConstant("targetDir");
        targetDir = StringUtils.isBlank(targetDir) ? "" : targetDir + "/";
        String moduleDir = task.getModuleDir();
        moduleDir = StringUtils.isBlank(moduleDir) ? "" : moduleDir + "/";
        String srcDir = task.getSrcDir();
        srcDir = StringUtils.isBlank(srcDir) ? "" : srcDir + "/";
        String packageFileDir = task.getGeneratedFileName(table.getName());
        String filePath = targetDir + moduleDir + srcDir + packageFileDir;
        FileUtils.writeFile(filePath, template);
    }
    /**
     * 运行插件
     * 
     * @param table
     * @param task
     * @param context
     * @param sbTemp
     */
    private void executePlugin(Table table, Task task, VelocityContext context, StringBuilder sbTemp) {
        Map<String, EasyCodePlugin> pluginMap = task.getPluginMap();
        if (pluginMap == null || pluginMap.size() == 0) {
            return;
        }
        for (EasyCodePlugin easyCodePlugin : pluginMap.values()) {
            easyCodePlugin.execute(table, task, sbTemp, context);
        }
    }
    /**
     * 添加字段类型需要导入的包
     *
     * @param columns the columns
     * @return the columns import class
     */
    protected Set<String> getColumnsImportClass(List<Column> columns) {
        Set<String> importSet = new HashSet<String>();
        for (Column column : columns) {
            if (EasyCodeContext.getDataConvertType(column.getDbType()) != null) {
                addImportClass(importSet, EasyCodeContext.getDataConvertType(column.getDbType())
                    .getJavaClass());
            } else {
                addImportClass(importSet, column.getJavaClass());
            }
        }
        return importSet;
    }
    /**
     * 生成serialVersionUID
     *
     * @return
     */
    protected String getSerialVersionUID() {
        return String.valueOf(Math.abs(UUID.randomUUID().getMostSignificantBits()));
    }
    /**
     * 添加引用的类
     *
     * @param importSet
     * @param className
     */
    private void addImportClass(Set<String> importSet, String className) {
        if (StringUtils.startsWith(className, "java.lang")) {
            return;
        }
        importSet.add(className);
    }
    /**
     * 代码生成方法
     *
     * @param table the table
     * @param task the task
     * @param context the context
     * @param template the template
     */
    public abstract void generate(Table table, Task task, VelocityContext context,
                                  StringBuilder template);
}

在这个抽象实现类里,我们做了一些基本都要用到的操作:生成并放入serialVersionUID,调用插件,及最后的代码文件生成。并主要提供了一个getColumnsImportClass方法供子类调用,该方法的主要作用是引入生成类中属性的包,例如对应数据库的Date类型属性,则会引入java.util.Date。至于具体的代码内容则由子类去实现。

具体的代码构建子类:

/**
 * 默认代码生成实现类
 * 
 * User: liyd
 * Date: 13-12-16
 * Time: 下午4:28
 */
public class DefaultCodeGenerator extends AbstractEasyCodeGenerator {
    /**
     * 代码生成方法
     *
     * @param table
     * @param task
     * @param context
     * @param template
     */
    @Override
    public void generate(Table table, Task task, VelocityContext context, StringBuilder template) {
        context.put("date", new Date());
        context.put("table", table);
        context.put("task", task);
        context.put("packageName", task.getPackageName());
        String longClassKey = task.getName() + "GeneratedLongClassName";
        String shortClassKey = task.getName() + "GeneratedShotClassName";
        String firstLowerClassKey = task.getName() + "FirstLowerGeneratedClassName";
        String generatedShotClassName = task.getGeneratedShotClassName(table.getName());
        context.put(longClassKey, task.getGeneratedReferenceClassName(table.getName()));
        context.put(shortClassKey, generatedShotClassName);
        context.put(firstLowerClassKey, NameUtils.getFirstLowerName(generatedShotClassName));
        Set<String> importSet = new HashSet<String>();
        String tmp = template.toString();
        if (StringUtils.indexOf(tmp, "List<") != -1
            && StringUtils.indexOf(tmp, "java.util.List") == -1) {
            importSet.add("java.util.List");
        }
        if (StringUtils.indexOf(tmp, "Map<") != -1
            && StringUtils.indexOf(tmp, "java.util.Map") == -1) {
            importSet.add("java.util.Map");
        }
        if (StringUtils.indexOf(tmp, "@Repository") != -1
            && StringUtils.indexOf(tmp, "org.springframework.stereotype.Repository") == -1) {
            importSet.add("org.springframework.stereotype.Repository");
        }
        if (StringUtils.indexOf(tmp, "@Autowired") != -1
            && StringUtils.indexOf(tmp, "org.springframework.beans.factory.annotation.Autowired") == -1) {
            importSet.add("org.springframework.beans.factory.annotation.Autowired");
        }
        if (StringUtils.indexOf(tmp, "@Component") != -1
            && StringUtils.indexOf(tmp, "org.springframework.stereotype.Component") == -1) {
            importSet.add("org.springframework.stereotype.Component");
        }
        if (StringUtils.indexOf(tmp, "${modelGeneratedShotClassName}") != -1
            && StringUtils.indexOf(tmp, "${modelGeneratedLongClassName}") == -1
            && !StringUtils.equals(task.getName(), "model")) {
            Object modelGeneratedLongClassName = context.get("modelGeneratedLongClassName");
            importSet.add(modelGeneratedLongClassName == null ? "" : modelGeneratedLongClassName
                .toString());
        }
        if (StringUtils.indexOf(tmp, "${modelVoGeneratedShotClassName}") != -1
                && StringUtils.indexOf(tmp, "${modelVoGeneratedLongClassName}") == -1
                && !StringUtils.equals(task.getName(), "modelVo")) {
            Object modelGeneratedLongClassName = context.get("modelVoGeneratedLongClassName");
            importSet.add(modelGeneratedLongClassName == null ? "" : modelGeneratedLongClassName
                    .toString());
        }
        if (StringUtils.indexOf(tmp, "${javaMapperGeneratedShotClassName}") != -1
            && StringUtils.indexOf(tmp, "${javaMapperGeneratedLongClassName}") == -1
            && !StringUtils.equals(task.getName(), "javaMapper")) {
            Object mapperGeneratedLongClassName = context.get("javaMapperGeneratedLongClassName");
            importSet.add(mapperGeneratedLongClassName == null ? "" : mapperGeneratedLongClassName
                .toString());
        }
        if (StringUtils.indexOf(tmp, "${daoGeneratedShotClassName}") != -1
            && StringUtils.indexOf(tmp, "${daoGeneratedLongClassName}") == -1
            && !StringUtils.equals(task.getName(), "dao")) {
            Object daoGeneratedLongClassName = context.get("daoGeneratedLongClassName");
            importSet.add(daoGeneratedLongClassName == null ? "" : daoGeneratedLongClassName
                .toString());
        }
        if (StringUtils.indexOf(tmp, "${serviceGeneratedShotClassName}") != -1
            && StringUtils.indexOf(tmp, "${serviceGeneratedLongClassName}") == -1
            && !StringUtils.equals(task.getName(), "service")) {
            Object daoGeneratedLongClassName = context.get("serviceGeneratedLongClassName");
            importSet.add(daoGeneratedLongClassName == null ? "" : daoGeneratedLongClassName
                .toString());
        }
        if (StringUtils.equalsIgnoreCase("model", task.getName())
            || StringUtils.equalsIgnoreCase("modelVo", task.getName())) {
            importSet.addAll(super.getColumnsImportClass(table.getColumns()));
        }
        context.put("importList", importSet);
    }
}

这里构建内容,其实主要就是把一些代码需要的信息放入到VelocityContext中,代Velocity在解析模板时使用。需要一提的是,这里把当前任务生成的代码信息也放了进去,可以供后面的生成任务使用,这样做的缘由是你可能会需要用到前面生成的Java类。比如在生成dao和service时,必然要依赖用到生成的model实体类,这样你就可以方便的取用。

下面的代码对常用的一些类,List,Map,spring的注解和前面任务生成的一些类做了一下特别处理,使得当包含这些对象时,包可以自动导入。这是我在实际生成时,发现每次都要在模板里面写多有不更而临时加上去的,可以去掉。

另外附上实体类生成的模板,其它模板类似,依样画葫芦编写即可:

package ${packageName};
import com.mincoder.kit.page.PagingOrder;
#foreach($im in ${importList})
import ${im};
#end
/**
 * ${table.desc}
 *
 * User: ${constant_creator}
 * Date: ${date}
 */
public class ${modelGeneratedShotClassName} extends PagingOrder{
    /** serialVersionUID */
    private static final long serialVersionUID = ${serialVersionUID};
#foreach($column in ${table.columns})
#if(${column.comment})
    /** ${column.comment} */
#end
    private ${column.javaType} ${column.camelName};
#end
#foreach($column in ${table.columns})
    public ${column.javaType} get${column.firstUpperName}() {
        return ${column.camelName};
    }
    public void set${column.firstUpperName}(${column.javaType} ${column.camelName}) {
        this.${column.camelName} = ${column.camelName};
    }
#end
}

到这里,代码生成的基本操作算是全部完成了,接下去将讲如何实现自定义的生成(例如在生成代码的同时生成一份表信息的Excel)。

上一篇:
下一篇:
1 条评论
xiehui · 1年前
历害!平时就为这个烦恼。可以提供源码吗?邮箱:67468631@qq.com

回复