软件工程应用与实践(11)——工具类分析

Posted 叶卡捷琳堡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件工程应用与实践(11)——工具类分析相关的知识,希望对你有一定的参考价值。

2021SC@SDUSC

文章目录

一、概述

在老年健康管理系统中,有很多工具类,这些工具类在项目中扮演了关键的角色,很多功能都利用这些工具类实现,经过小组成员的讨论和交流,本篇博客主要介绍老年健康管理系统中的工具类。理解这些工具类,有利于我们理解整个系统的一些实现。

本项目的工具类主要有以下几个

generater项目的utils包下的两个工具类


tools项目下的search.lucene.util包下的几个工具类

gate项目下utils包下的工具类

common项目下的几个工具类

在本篇博客中,主要针对其中的几个类进行分析,在下一篇博客中,还会对剩下的几个类进行剖析

二、主要代码

2.1 DateUtils

DateUtil类的作用比较简单,主要提供了对日期进行格式化的方法

可以看到,在DataUtil类中定义了两个用final修饰符修饰的常量,一个是DATE_PATTERN,一个是DATE_TIME_PATTERN。分别规定了日期和含时间日期的类型

定义了两个方法(重载的方法),一个是format(Date date)方法,另一个是format(Date date, String pattern),即在其他程序中,既可以调用已经准备好的样式对日期进行格式化,也可根据具体的需要调用程序。

这个类底层的实现使用了Java的Date类

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtils 
	/** 时间格式(yyyy-MM-dd) */
	public final static String DATE_PATTERN = "yyyy-MM-dd";
	/** 时间格式(yyyy-MM-dd HH:mm:ss) */
	public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
	
	public static String format(Date date) 
        return format(date, DATE_PATTERN);
    

    public static String format(Date date, String pattern) 
        if(date != null)
            SimpleDateFormat df = new SimpleDateFormat(pattern);
            return df.format(date);
        
        return null;
    

2.2 GeneratorUtils

这个类的主要作用是,将项目中的.vm文件生成对应的代码。.vm文件是velocity模板引擎的一种页面控制文件,而velocity是一个基于Java的模板引擎,他可以让程序员使用简单的模板语言引用Java代码定义的对象,实现界面和 Java 代码的分离。在本项目中,velocity模板主要用于生成Java代码

首先我们可以看到,该类中引入了Java中关于IO读取和Zip读取的几个核心类

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

接下来我们查看构造方法

在构造方法中,首先创建了一个ArrayList对象,接着将.vm文件的路径加入这个List集合中,在对应的路径下,我们可以看到这些.vm文件

public static List<String> getTemplates() 
       List<String> templates = new ArrayList<String>();
       templates.add("template/index.js.vm");
       templates.add("template/index.vue.vm");
       templates.add("template/mapper.xml.vm");
       templates.add("template/biz.java.vm");
       templates.add("template/entity.java.vm");
       templates.add("template/mapper.java.vm");
       templates.add("template/controller.java.vm");
       return templates;
   

接下来我们看到generatorCode方法

在这个方法里需要传入表名的Map,列名等信息,在这个方法中,首先对这些信息(表信息,列信息等)进行了初始化操作,接着设置velocity资源加载器,并将对应的信息添加到模板中。关于方法中具体语句的细节,有相应的注释进行说明。

/**
 * 生成代码
 */
public static void generatorCode(Map<String, String> table,
                                 List<Map<String, String>> columns, ZipOutputStream zip, String author, String path, String mainModule, String tablePrefix) 
    //配置信息
    Configuration config = getConfig();

    //表信息
    TableEntity tableEntity = new TableEntity();
    tableEntity.setTableName(table.get("tableName"));
    tableEntity.setComments(table.get("tableComment"));
    //表名转换成Java类名
    String className = tableToJava(tableEntity.getTableName(), StringUtils.isBlank(tablePrefix) ? config.getString("tablePrefix") : tablePrefix);
    tableEntity.setClassName(className);
    tableEntity.setClassname(StringUtils.uncapitalize(className));

    //列信息
    List<ColumnEntity> columsList = new ArrayList<>();
    for (Map<String, String> column : columns) 
        ColumnEntity columnEntity = new ColumnEntity();
        columnEntity.setColumnName(column.get("columnName"));
        columnEntity.setDataType(column.get("dataType"));
        columnEntity.setComments(column.get("columnComment"));
        columnEntity.setExtra(column.get("extra"));

        //列名转换成Java属性名
        String attrName = columnToJava(columnEntity.getColumnName());
        columnEntity.setAttrName(attrName);
        columnEntity.setAttrname(StringUtils.uncapitalize(attrName));

        //列的数据类型,转换成Java类型
        String attrType = config.getString(columnEntity.getDataType(), "unknowType");
        columnEntity.setAttrType(attrType);

        //是否主键
        if ("PRI".equalsIgnoreCase(column.get("columnKey")) && tableEntity.getPk() == null) 
            tableEntity.setPk(columnEntity);
        

        columsList.add(columnEntity);
    
    tableEntity.setColumns(columsList);

    //没主键,则第一个字段为主键
    if (tableEntity.getPk() == null) 
        tableEntity.setPk(tableEntity.getColumns().get(0));
    

    //设置velocity资源加载器
    Properties prop = new Properties();
    prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
    Velocity.init(prop);

    //封装模板数据
    Map<String, Object> map = new HashMap<>();
    map.put("tableName", tableEntity.getTableName());
    map.put("comments", tableEntity.getComments());
    map.put("pk", tableEntity.getPk());
    map.put("className", tableEntity.getClassName());
    map.put("classname", tableEntity.getClassname());
    map.put("pathName", tableEntity.getClassname().toLowerCase());
    map.put("columns", tableEntity.getColumns());
    map.put("package", path);
    map.put("author", author);
    map.put("email", config.getString("email"));
    map.put("datetime", DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN));
    map.put("moduleName", mainModule);
    map.put("secondModuleName", toLowerCaseFirstOne(className));
    VelocityContext context = new VelocityContext(map);

    //获取模板列表
    List<String> templates = getTemplates();
    for (String template : templates) 
        //渲染模板
        StringWriter sw = new StringWriter();
        Template tpl = Velocity.getTemplate(template, "UTF-8");
        tpl.merge(context, sw);

        try 
            //添加到zip
            zip.putNextEntry(new ZipEntry(getFileName(template, tableEntity.getClassName(), path, mainModule)));
            IOUtils.write(sw.toString(), zip, "UTF-8");
            IOUtils.closeQuietly(sw);
            zip.closeEntry();
         catch (IOException e) 
            throw new RuntimeException("渲染模板失败,表名:" + tableEntity.getTableName(), e);
        
    

接下来我们看到下面3个方法

这三个方法在上面的方法中有被调用,第一个方法的作用是将列名转换为Java属性名。第二个方法的作用是将表名转换成Java属性名。在第二个方法中,调用了第一个方法。第三个方法的作用是获取配置信息。

第一个方法中,使用了replace方法,将表名中的_替换为空字符串。关于capitalizeFully方法,我们通过查看它源码的注释可以发现,该方法的作用是:将 String 中所有分隔符分隔的单词转换为大写单词,即每个单词由一个标题字符和一系列小写字符组成。分隔符代表一组被理解为分隔单词的字符。第一个字符串字符和分隔符后的第一个非分隔符字符将大写。

/**
 * 列名转换成Java属性名
 */
public static String columnToJava(String columnName) 
    return WordUtils.capitalizeFully(columnName, new char[]'_').replace("_", "");


/**
 * 表名转换成Java类名
 */
public static String tableToJava(String tableName, String tablePrefix) 
    if (StringUtils.isNotBlank(tablePrefix)) 
        tableName = tableName.replace(tablePrefix, "");
    
    return columnToJava(tableName);


/**
 * 获取配置信息
 */
public static Configuration getConfig() 
    try 
        return new PropertiesConfiguration("generator.properties");
     catch (ConfigurationException e) 
        throw new RuntimeException("获取配置文件失败,", e);
    

接下来这个方法的作用是获取文件名

这里的template,对应的就是构造方法中对应的template链表的一个对象,这个方法,在上面的generatorCode方法中被调用,目的是获取文件名。

在本方法中,源码首先使用字符串拼接的方式,用File.separator拼接出对应的包名,接着又拼接出包的前缀。之后通过isNotBlank判断包名是否为空(或者只有空格),接着通过if语句,判断该字符串的具体内容,根据对应的情况返回不同的文件名

/**
 * 获取文件名
 */
public static String getFileName(String template, String className, String packageName, String moduleName) 
    String packagePath = "main" + File.separator + "java" + File.separator;
    String frontPath = "ui" + File.separator;
    if (StringUtils.isNotBlank(packageName)) 
        packagePath += packageName.replace(".", File.separator) + File.separator;
    

    if (template.contains("index.js.vm")) 
        return frontPath + "api" + File.separator + moduleName + File.separator + toLowerCaseFirstOne(className) + File.separator + "index.js";
    

    if (template.contains("index.vue.vm")) 
        return frontPath + "views" + File.separator + moduleName + File.separator + toLowerCaseFirstOne(className) + File.separator + "index.vue";
    

    if (template.contains("biz.java.vm")) 
        return packagePath + "biz" + File.separator + className + "Biz.java";
    
    if (template.contains("mapper.java.vm")) 
        return packagePath + "mapper" + File.separator + className + "Mapper.java";
    
    if (template.contains("entity.java.vm")) 
        return packagePath + "entity" + File.separator + className + ".java";
    
    if (template.contains("controller.java.vm")) 
        return packagePath + "rest" + File.separator + className + "Controller.java";
    
    if (template.contains("mapper.xml.vm")) 
        return "main" + File.separator + "resources" + File.separator + "mapper" + File.separator + className + "Mapper.xml";
    

    return null;

本类中的最后一个方法

本类中的最后一个方法是首字母转小写的方法,本方法调用了Java.lang.String类中的toLowerCase方法。如果已经为小写,则不需要再转小写。同时利用StringBuilder类的append方法拼接字符串。这比直接使用String类的效率要高一些

//首字母转小写
public static String toLowerCaseFirstOne(String s) 
    if (Character.isLowerCase(s.charAt(0))) 
        return s;
     else 
        return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
    

关于.vm文件

本类涉及对vm文件的读取,在本项目中,我个人认为,vm文件相当于规定了一些java代码的基本结构,比如下面的controller.java.vm,里面固定了Controller类(接口类)的基本形态

import $package.biz.$classNameBiz;
import $package.entity.$className;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
@RequestMapping("$secondModuleName")
public class $classNameController extends BaseController<$classNameBiz,$className> 


还有下面关于mybatis中mapper文件的配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="$package.mapper.$classNameMapper">

	<!-- 可根据自己的需求,是否要使用 -->
    <resultMap type="$package.entity.$className" id="$classnameMap">
#foreach($column in $columns)
        <result property="$column.attrname" column="$column.columnName"/>
#end
    </resultMap>

</mapper>

比较有意思的是关于实体类配置的.vm文件,在这个文件中,首先我们可以看到,在引入包时,通过#if判断是否含有BigDecimal,如果有,就引入java.math.BigDecimal类,在之后还对注释进行了相应的配置。在实体类的配置中,使用了对应的for-each

以上是关于软件工程应用与实践(11)——工具类分析的主要内容,如果未能解决你的问题,请参考以下文章

软件工程应用与实践(12)——工具类分析

软件工程应用与实践(12)——工具类分析

黑盒测试实践(小组作业)每日例会记录——11.25

软件工程应用与实践(14)——工具类分析

软件工程应用与实践(14)——工具类分析

实践作业1:测试管理工具实践(小组作业)每日任务记录1