動手搭建後端框架-Velocity模板引擎的應用

爲了提升開發效率,一般會想辦法把一些模式固定的重複性的勞動抽取出來,之後再使用的時候,拿來主義就能夠了。這樣既能夠提升開發效率,又下降了出錯的風險。前端

這一思想在咱們的平常工做中能夠說隨處可見,咱們完成一項複雜的工程,並不須要面面俱到什麼都本身寫,咱們徹底能夠利用第三方的jar包讓咱們達到事半功倍的效果,好比常常使用的apche的commons-lang3包。再好比java中的繼承、咱們本身封裝的工具類等等。 另一方面,對於源碼文件,若是公司有成熟的框架,咱們的開發都是遵循着框架制定的約定來進行開發的,咱們在建立某一個業務的控制層、業務層、持久層的時候,實際上有至關一部分的工做是重複的。java

那麼對於源碼文件的編寫咱們可否偷偷懶呢?答案確定是能夠的,咱們能夠利用模板引擎技術,將不變的部分寫在模板文件中,將可變的部分做爲變量傳遞到模板引擎的上下文中,最終生成咱們想要的源碼文件。git

模板引擎的產品有不少,好比前端模板artTemplate、後端模板Velocity、FreeMarker等 本文以Velocity爲例,總結一下它在實戰中的應用github

1.基礎知識

搭建過程涉及到的基礎知識包括:Maven、Velocity、工廠模式、建造者模式、單元測試
對於基礎不熟悉的同窗,建議看一下下面的兩篇文章
Velocity基礎
Velocity語法摘要web

2.搭建工程

2.1模塊目錄

代碼生成功能,在我設計的後臺框架中,做爲一個獨立的模塊存在,使用Maven構建。
builder目錄:建造者模式應用。因爲表明表結構的Table實體稍顯複雜,所以使用了建造者模式構建Table對象。其實不用也能夠,由於Table不是很複雜,只是爲了複習一下所學過的設計模式知識
factory目錄:工廠模式應用。在構建源碼文件的時候,因爲涉及到了Controller、Service、Dao、Domain這幾種類型的文件,所以針對不一樣類型的文件,要使用其對應的處理類,所以使用了工廠模式
handler目錄:生成源文件的核心代碼
model目錄:在生成domain的時候,因爲字段須要從數據庫中的表中讀取,所以構造了與表對應的實體類方便處理
utils目錄:工具類
Generator.java:程序主文件,調用入口
test目錄:單元測試spring

.
├── generator.iml
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── wt
    │   │           └── master
    │   │               └── generator
    │   │                   ├── Generator.java
    │   │                   ├── builder
    │   │                   │   ├── MySqlTableBuilder.java
    │   │                   │   └── TableBuilder.java
    │   │                   ├── factory
    │   │                   │   └── GeneratorFactory.java
    │   │                   ├── handler
    │   │                   │   ├── BaseGenerator.java
    │   │                   │   ├── ControllerGeneratorHandler.java
    │   │                   │   ├── DomainGeneratorHandler.java
    │   │                   │   ├── MapperGeneratorHandler.java
    │   │                   │   └── ServiceGeneratorHandler.java
    │   │                   ├── model
    │   │                   │   └── Table.java
    │   │                   └── util
    │   │                       ├── JdbcUtils.java
    │   │                       ├── SpringContextUtils.java
    │   │                       ├── TableColumnUtils.java
    │   │                       └── TableInfoUtils.java
    │   └── resources
    │       ├── config
    │       │   ├── applicationContext.xml
    │       │   └── db.properties
    │       └── template
    │           ├── controller.java.vm
    │           ├── dao.java.vm
    │           ├── domain.java.vm
    │           ├── service.java.vm
    │           └── serviceimpl.java.vm
    └── test
        └── com.wt.master.generator
            └── GeneratorTest.java

2.2引入依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>j2ee</artifactId>
        <groupId>com.wt.master</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../version/</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>generator</artifactId>
    <dependencies>
        <!-- 模板引擎 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>1.7</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.wt.master</groupId>
            <artifactId>core</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.4</version>
        </dependency>
    </dependencies>
</project>

3.核心代碼

3.1模板文件的定義

以controller層生成模板爲例
將不變的部分直接寫到.vm文件中
將模板文件中,有可能發生變化的部分,抽取爲變量,變量的值從VelocityContext中獲取
在Velocity架構中,有一個上下文的定義,經過上下文,程序將變量放入上下文對象中。而模板從上下文中獲取對應變量的值,獲取的方式是${變量名},關於Velocity模板文件中的語法,參見上文提到的兩篇文章sql

package ${packagePath}.controller;

import ${packagePath}.domain.${moduleName};
import ${packagePath}.service.${moduleName}Service;
import com.wt.master.core.base.BaseController;
import com.wt.master.core.helper.QueryHelper;
import com.wt.master.core.request.HttpResultEntity;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ${moduleNameCN}控制器
 *
 * @author
 * @date
 */
@Api(value = "${moduleNameCN}控制器", tags = "${moduleName}Controller", description = "${moduleNameCN}控制器" )
@RestController
@RequestMapping("/${moduleName}" )
@Slf4j
public class ${moduleName}Controller extends BaseController<${moduleName}, ${moduleName}Service> {
    @Autowired
    private ${moduleName}Service ${lowerModuleName}Service;

    @Override
    protected ${moduleName}Service getService() {
        return ${lowerModuleName}Service;
    }

}

3.2工廠類定義

根據源碼文件類型的不一樣,定義了不一樣的處理類,經過工廠模式返回對應的處理類數據庫

package com.wt.master.generator.factory;

import com.wt.master.generator.Generator;
import com.wt.master.generator.handler.*;

/**
 * 生成器工廠
 *
 * @author lichking2019@aliyun.com
 * @date Jun 18, 2019 at 4:02:23 PM
 */
public class GeneratorFactory {
    public static BaseGenerator create(Generator.GenerateItem item) {
        BaseGenerator baseGenerator = null;
        switch (item) {
            case service:
                baseGenerator = new ServiceGeneratorHandler();
                break;
            case controller:
                baseGenerator = new ControllerGeneratorHandler();
                break;
            case mapper:
                baseGenerator = new MapperGeneratorHandler();
                break;
            case domain:
                baseGenerator = new DomainGeneratorHandler();
                break;
            default:
                baseGenerator = new ControllerGeneratorHandler();
        }
        return baseGenerator;
    }
}

3.3源碼生成處理類定義

以controller處理類爲例
定義抽象類,做爲基類
定義了抽象方法generate,生成源碼文件的處理方法
定義了抽象方法getFilePath,獲取生成文件的路徑
方法的實現由具體的實現類來實現apache

package com.wt.master.generator.handler;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;

/**
 * 生成器抽象
 *
 * @author lichking2019@aliyun.com
 * @date May 12, 2019 at 10:44:53 AM
 */
public abstract class BaseGenerator {
    /**
     * 生成代碼
     *
     * @param tableName    表名
     * @param moduleName   模塊英文名
     * @param moduleNameCN 模塊中文名
     * @param packagePath  包路徑
     * @return
     */
    public abstract BaseGenerator generate(String tableName, String moduleName, String moduleNameCN,
                                           String packagePath);

    /**
     * 生成文件路徑
     * @param packagePath
     * @return
     */
    public abstract String getFilePath(String packagePath,String moduleName);

    /**
     * 獲取 模板
     *
     * @param templateName 模板文件名稱
     * @return
     */
    Template getTemplate(String templateName) {
        VelocityEngine ve = new VelocityEngine();
        ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath" );
        ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        ve.setProperty("input.encoding","utf-8");
        ve.setProperty("output.encoding","utf-8");
        ve.init();
        Template t = ve.getTemplate("/template/" + templateName);
        return t;
    }

    protected void merge(Template template, VelocityContext ctx, String path) {
        File file = new File(path);
        if(!file.exists()){
            new File(file.getParent()).mkdirs();
        }else{
            System.out.println("替換文件"+file.getAbsolutePath());
        }

        PrintWriter writer = null;
        try {
            writer = new PrintWriter(path);
            template.merge(ctx, writer);
            writer.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            writer.close();
        }
    }

    /**
     * 得到根目錄
     * @return
     */
    protected String getRootPath(){
        String rootPath = "";
        try {
            File file = new File(BaseGenerator.class.getResource("/").getFile());
            rootPath = file.getParent();
            rootPath = java.net.URLDecoder.decode(rootPath.substring(0, rootPath.indexOf("target") - 1), "utf-8");
            return rootPath+"/src/main/java";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return rootPath;
    }

    /**
     * 轉換包路徑爲文件路徑
     * @param packagePath
     * @return
     */
    protected String convertPackagePathToFilePath(String packagePath){
        StringBuilder path = new StringBuilder();
        path.append("/" );
        path.append(packagePath.replace(".", "/" ));
        path.append("/");
        return path.toString();
    }

}

3.4工具類的定義

該類主要是獲取表的信息及對應的字段信息後端

package com.wt.master.generator.util;

import com.wt.master.generator.model.Table;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 表操做類
 *
 * @author lichking2019@aliyun.com
 * @date Apr 23, 2019 at 11:36:30 PM
 */
public class TableInfoUtils {

    public static final String JDBC_TEMPLATE = "jdbcTemplate";

    public static Table getTableColumnList(String tableName) {
        JdbcTemplate jdbcTemplate = (JdbcTemplate) SpringContextUtils.getBean(JDBC_TEMPLATE);

        List<Map<String,Object>> tableInfo = jdbcTemplate.queryForList(getTableStructureSql(tableName));
        if (CollectionUtils.isEmpty(tableInfo)) {
            throw new RuntimeException("表:" + tableName + "不存在" );
        }

        List<Map<String,Object>> columns = jdbcTemplate.queryForList(getColumnStructureSql(tableName));

        return TableColumnUtils.convertToColumn(columns, tableInfo.get(0));
    }

    /**
     * 獲取查詢表字段屬性的SQL
     *
     * @param tableName 表名
     * @return
     */
    private static String getColumnStructureSql(String tableName) {
        StringBuilder sql = new StringBuilder();
        sql.append("select column_name, data_type,column_comment,column_key " );
        sql.append("from information_schema.columns " );
        sql.append("where table_name = '" + tableName + "'" );
        return sql.toString();
    }

    /**
     * 獲取表的信息
     * @param tableName
     * @return
     */
    private static String getTableStructureSql(String tableName) {
        StringBuilder sql = new StringBuilder();
        sql.append("select table_name,table_comment " );
        sql.append("from information_schema.tables " );
        sql.append("where table_name= '" + tableName + "'" );
        return sql.toString();
    }


}

3.5應用入口

代用create方法來生成源碼文件

package com.wt.master.generator;

import com.wt.master.generator.builder.MySqlTableBuilder;
import com.wt.master.generator.builder.TableBuilder;
import com.wt.master.generator.factory.GeneratorFactory;
import com.wt.master.generator.handler.BaseGenerator;
import com.wt.master.generator.model.Table;
import com.wt.master.generator.util.TableInfoUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.Assert;

import java.util.List;

/**
 * 代碼生成工具
 *
 * @author lichking2019@aliyun.com
 * @date Apr 23, 2019 at 10:41:51 PM
 */
public class Generator {

    /**
     * 生成代碼入口
     *
     * @param tableName    表名
     * @param moduleName   模塊英文名
     * @param moduleNameCN 模塊中文名
     * @param packagePath  打包路徑
     * @param item         生成項目
     */
    public static void create(String tableName, String moduleName, String moduleNameCN, String packagePath,
                              GenerateItem... item) {
        if (StringUtils.isBlank(tableName) || StringUtils.isBlank(moduleName) || StringUtils.isBlank(moduleNameCN) || StringUtils.isBlank(packagePath)) {
            throw new IllegalArgumentException("參數非法!" );
        }

        for (GenerateItem generateItem : item) {
            BaseGenerator baseGenerator = GeneratorFactory.create(generateItem);
            baseGenerator.generate(tableName, moduleName, moduleNameCN, packagePath);
        }
    }


    public enum GenerateItem {
        controller, service, mapper, domain
    }

}

4.單元測試

package com.wt.master.generator;

import org.junit.Test;
import org.junit.Before;
import org.junit.After;

/**
 * Generator Tester.
 *
 * @author <Authors name>
 * @version 1.0
 * @since <pre>Jun 18, 2019</pre>
 */
public class GeneratorTest {
    @Test
    public void testCreate() throws Exception {
        //TODO: Test goes here...
        Generator.create("SecurityRoleT", "SecurityRole", "角色管理", "com.wt.common.security",
                Generator.GenerateItem.controller, Generator.GenerateItem.service, Generator.GenerateItem.mapper,
                Generator.GenerateItem.domain);
    }
}

源碼github地址

相關文章
相關標籤/搜索