基于Freemarker模板引擎的Java代码生成器

目录

一、前言

二、FreeMarker简介

三、实现原理

(一)编写模板文件

(二)配置FreeMarker

(三)统一文件生成工具

(四)数据库操作

(五)封装填充数据


一、前言

对于Java开发人员来说,在做好框架整合进入开发阶段后,依然存在大量的重复工作,比如在Spring、SpringMVC、Mybatis架构下,我们需要构建Entity、Mapper、dao、service、controller等,这些重复性的工作会大大降低工作效率,这时我们就需要一个代码生成工具来提高我们的工作效率。

二、FreeMarker简介

FreeMarker是一款模板引擎:即一种基于模板和动态数据,用于输出文本的通用工具。

FreeMarker模板使用FreeMarker Template Language(FTL)编写,它是一种简单的、专用的语言。

FreeMarker的使用方式如下图:

注:以上内容摘自FreeMarker官方文档,地址:FreeMarker在线手册

三、实现原理

代码生成器的实现原理十分简单,就是根据数据库的某一个或多个业务表的结构,生成对应的Entity.java、Dao.java、Service.java、Controller.java、Mapper.xml文件,具体如下:

(一)编写模板文件

关于模板文件的编写就不详细说了,我们需要根据项目的架构编写相应的模板文件,下面直接给出Entity.ftl模板文件的例子:

package ${package};
import java.io.Serializable;

/**
* Author ${author}
* Date  ${date}
*/
public class ${className} implements Serializable {
private static final long serialVersionUID = 1L;
${properties}

public ${className}(){
}

${methods}
}

(二)配置FreeMarker

FreeMarker官方建议通过单例模式进行配置,这里采用了懒汉式单例,配置了FreeMarker的模板文件路径和编码方式,代码如下:

public class FreemarketConfigUtils {
    private final static String PATH_DEBUG = "src/main/resources/template/";
    private final static String PATH_RELEASE = "resources/template/";
    private static String path;
    public final static int TYPE_ENTITY = 0;
    public final static int TYPE_DAO = 1;
    public final static int TYPE_SERVICE = 2;
    public final static int TYPE_CONTROLLER = 3;
    public final static int TYPE_MAPPER = 4;
    private static Configuration configuration;

    static {
        if (Common.isDebug) {
            path = PATH_DEBUG;
        } else {
            path = PATH_RELEASE;
        }
    }

    public static synchronized Configuration getInstance() {
        if (null == configuration) {
            configuration = new Configuration(Configuration.VERSION_2_3_23);
            try {
                configuration.setDirectoryForTemplateLoading(new File(path));
            } catch (IOException e) {
                e.printStackTrace();
            }
            configuration.setEncoding(Locale.CHINA, "utf-8");
        }
        return configuration;
    }
}

(三)统一文件生成工具

配置好模板引擎和文件后,我们就可以使用这个工具来生成文件了。在使用过程中,我们通常需要生成Entity.java、Dao.java、Service.java、Controller.java、Mapper.xml等文件,这样一来我们就需要封装一个统一的文件生成工具类,如下所示:

public class FileUtil {
    private final static String PATH_DEBUG = FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath().replace("\\", "/") + "/";
    private final static String PATH_RELEASE = "code/";
    private static String path;

    static {
        if (Common.isDebug) {
            path = PATH_DEBUG;
        } else {
            path = PATH_RELEASE;
        }
    }

    /**
     * @param type 使用模板类型
     * @param data 填充数据
     * @param fileName 输出文件名
     * @throws IOException
     * @throws TemplateException
     */
    public static void generateToJava(int type, Object data, String fileName) throws IOException, TemplateException {
        Template tpl = getTemplate(type); // 获取模板文件
        // 填充数据
        StringWriter writer = new StringWriter();
        tpl.process(data, writer);
        writer.flush();
        // 写入文件
        FileOutputStream fos = new FileOutputStream(path + fileName);
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
        BufferedWriter bw = new BufferedWriter(osw, 1024);
        tpl.process(data, bw);
        fos.close();
    }

    /**
     * 获取模板
     * @param type 模板类型
     * @return
     * @throws IOException
     */
    private static Template getTemplate(int type) throws IOException {
        switch (type) {
            case FreemarketConfigUtils.TYPE_ENTITY:
                return FreemarketConfigUtils.getInstance().getTemplate("Entity.ftl");
            case FreemarketConfigUtils.TYPE_DAO:
                return FreemarketConfigUtils.getInstance().getTemplate("Dao.ftl");
            case FreemarketConfigUtils.TYPE_SERVICE:
                return FreemarketConfigUtils.getInstance().getTemplate("Service.ftl");
            case FreemarketConfigUtils.TYPE_CONTROLLER:
                return FreemarketConfigUtils.getInstance().getTemplate("Controller.ftl");
            case FreemarketConfigUtils.TYPE_MAPPER:
                return FreemarketConfigUtils.getInstance().getTemplate("Mapper.ftl");
            default:
                return null;
        }
    }

}

(四)数据库操作

前面已经提到,代码生成器的原理是根据业务表的结构来生成相应代码的,因此我们需要封装一个数据库操作的工具类,主要用于获取业务表的结构,如下所示:

public class ConnectionUtil {
    private final static String DRIVER = "com.mysql.jdbc.Driver";
    private Connection connection;
    private Statement statement;
    private ResultSet resultSet;

    /**
     * 初始化数据库连接
     * @param database 数据库名
     * @param username 用户名
     * @param password 密码
     * @return
     */
    public boolean initConnection(String database, String username, String password) {
        try {
            Class.forName(DRIVER);
            String url = "jdbc:mysql://localhost:3306/" + database + "?useUnicode=true&characterEncoding=UTF-8";
            connection = DriverManager.getConnection(url, username, password);
            return true;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 获取表结构数据
     * @param tableName 表名
     * @return 包含表结构数据的列表
     */
    public List<ColumnInfo> getMetaData(String tableName) {
        List<ColumnInfo> columnInfos = new ArrayList<>();
        try {
            statement = connection.createStatement();
            String sql = "SELECT * FROM " + tableName.toUpperCase() + " WHERE 1 != 1";
            resultSet = statement.executeQuery(sql);
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                ColumnInfo info = new ColumnInfo(metaData.getColumnName(i), metaData.getColumnType(i), metaData.getColumnTypeName(i), metaData.getTableName(i));
                columnInfos.add(info);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                statement.close();
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return columnInfos;
    }

    public boolean isClosed() {
        try {
            return connection.isClosed();
        } catch (SQLException e) {
            e.printStackTrace();
            return true;
        }
    }

    public void close() {
        if (!isClosed()) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

(五)封装填充数据

由于不同的文件对应不同的模板,所以所需要的填充数据也是不完全相同的,因此我们需要对各类填充数据进行封装。

在业务中经常需要进行多表级联查询,因此在代码生成器中共支持两种方式:其一,单表代码生成;其二,多表代码生成。这两种生成方式的思想都是一样的,但多表情况下生成填充数据要稍稍复杂一些,当然也是非常简单的。

如下是生成XXEntity.java的数据封装类:

public class EntityTask extends BaseTask {
    protected String childClassName;
    protected String foreignKey;
    protected List<ColumnInfo> mainInfos;

    /**
     * 1.单表生成  2.多表时生成子表实体
     */
    public EntityTask(String packageName, String className, List<ColumnInfo> infos) {
        this(packageName, className, null, null, infos);
    }

    /**
     * 多表时生成主表实体
     */
    public EntityTask(String packageName, String mainClassName, String childClassName, String foreignKey, List<ColumnInfo> mainInfos) {
        this.packageName = packageName;
        this.mainClassName = mainClassName;
        this.childClassName = childClassName;
        this.foreignKey = foreignKey;
        this.mainInfos = mainInfos;
    }

    @Override
    public void run() throws IOException, TemplateException {
        // 生成Entity填充数据
        System.out.println("Generating " + mainClassName + ".java");
        Map<String, String> entityData = new HashMap<>();
        entityData.put("package", packageName + ".entity");
        entityData.put("author", "GreedyStar");
        entityData.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
        entityData.put("className", mainClassName);
        if (!StringUtil.isBlank(childClassName)) { // 多表 主表实体
            entityData.put("properties", StringUtil.generateEntityProperties(childClassName, mainInfos, foreignKey));
            entityData.put("methods", StringUtil.generateEntityMethods(childClassName, mainInfos, foreignKey));
        } else { // 1.多表:子表实体 2.单表实体
            entityData.put("properties", StringUtil.generateEntityProperties(mainInfos));
            entityData.put("methods", StringUtil.generateEntityMethods(mainInfos));
        }
        // 生成Entity文件
        FileUtil.generateToJava(FreemarketConfigUtils.TYPE_ENTITY, entityData, mainClassName + ".java");
    }
}

以上就是生成器的核心代码了。

源码地址:Java代码生成器

代码粗糙,各位看官如发现问题烦请与我联系

猜你喜欢

转载自blog.csdn.net/greedystar/article/details/81043407