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

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.${className}Biz;
import ${package}.entity.${className};
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
@RequestMapping("${secondModuleName}")
public class ${className}Controller extends BaseController<${className}Biz,${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.${className}Mapper">

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

</mapper>

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

package ${package}.entity;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
#if(${hasBigDecimal})
import java.math.BigDecimal;
#end


/**
 * ${comments}
 * 
 * @author ${author}
 * @version ${datetime}
 */
@Table(name = "${tableName}")
public class ${className} implements Serializable {
	private static final long serialVersionUID = 1L;
	
#foreach ($column in $columns)
	#if($column.columnName == $pk.columnName)
    //$column.comments
    @Id
    private $column.attrType $column.attrname;
	#else
    //$column.comments
    @Column(name = "$column.columnName")
    private $column.attrType $column.attrname;
	#end

#end

#foreach ($column in $columns)
	/**
	 * 设置:${column.comments}
	 */
	public void set${column.attrName}($column.attrType $column.attrname) {
		this.$column.attrname = $column.attrname;
	}
	/**
	 * 获取:${column.comments}
	 */
	public $column.attrType get${column.attrName}() {
		return $column.attrname;
	}
#end
}

2.3 DocumentUtil

接下来我们把目光放在lucene包下的util工具类。关于lucene,在一开始接触的时候,其实我并不知道lucene是什么东西,但是后面通过查询资料,我渐渐了解了lucene是做什么的。lucene是一个搜索引擎工具,可以用于全文搜索和检索。

而Lucene、Solr、Elasticsearch三者的关系是:lucene提供底层的API和工具包,Solr是基于Lucene开发的企业级的搜索引擎产品,Elasticsearch同样也是基于Lucene开发的企业级的搜索引擎产品。

这个类实际上是对lucene中的Document(每一条记录),进行相应的操作

首先我们看到该类包的引入

可以发现,该类引入了很多org.apache.lucene包下的类,有分析器,与搜索高亮相关的类等等

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.search.highlight.Highlighter;

import java.io.StringReader;

接下来我们查看这个类的IndexObject2Document方法

经过阅读,可以发现本类并没有构造方法,而所有的方法都是静态方法,供程序员在其他程序中调用。比如下面的这个方法,核心的操作就是通过传入的IndexObject对象,创建并返回一个Document对象。IndexObject是一个索引对象,里面实现了诸多方法,这个我们之后会分析

public static Document IndexObject2Document(IndexObject indexObject) {
    Document doc = new Document();
    doc.add(new StoredField("id", indexObject.getId()));
    doc.add(new TextField("title",indexObject.getTitle(), Field.Store.YES));
    doc.add(new TextField("summary",indexObject.getKeywords(), Field.Store.YES));
    doc.add(new TextField("descripton",indexObject.getDescripton(), Field.Store.YES));
    doc.add(new StoredField("postDate", indexObject.getPostDate()));
    doc.add(new StoredField("url", indexObject.getUrl()));
    return doc;  
}  

接下来我们查看document2IndexObject方法

这个方法的目的是将传入的Document对象变为IndexObject(索引)对象,作用与上面的方法类似,之后我会重点讲解这两个类

public static  IndexObject document2IndexObject(Analyzer analyzer, Highlighter highlighter, Document doc,float score) throws Exception {
    IndexObject indexObject = new IndexObject();
    indexObject.setId(Long.parseLong(doc.get("id")));
    indexObject.setTitle(stringFormatHighlighterOut(analyzer, highlighter,doc,"title"));
    indexObject.setKeywords(stringFormatHighlighterOut(analyzer, highlighter,doc,"summary"));
    indexObject.setDescripton(stringFormatHighlighterOut(analyzer, highlighter,doc,"descripton"));
    indexObject.setPostDate(doc.get("postDate"));
    indexObject.setUrl(doc.get("url"));
    indexObject.setScore(score);
    return indexObject;
}

接下来我们查看下一个方法

这个方法的主要作用是为关键字加高亮。

这个方法传入了一个Document对象,还有一个field字符串,使用highlighter的getBestFragment将Document对象中的有关field的字符串提供高亮效果。

/*关键字加亮*/
private static String stringFormatHighlighterOut(Analyzer analyzer, Highlighter highlighter, Document document, String field) throws Exception{
    String fieldValue = document.get(field);
    if(fieldValue!=null){
        TokenStream tokenStream=analyzer.tokenStream(field, new StringReader(fieldValue));
        return highlighter.getBestFragment(tokenStream, fieldValue);
    }
    return null;
}

关于IndexObject类

由于本项目中使用的搜索引擎中存在“索引”这一概念,因此本项目中使用了IndexObject这个类用于代表对应的索引。首先可以看到这个类中引入了java.io.Serializable接口,并且实现了Comparable接口,在下面的方法中,我们可以看到,这个类重写了compareTo方法,在compareTo方法中,主要对该对象的score(相似度)属性进行比较,返回1代表本对象比传入的对象小,返回0代表相等,返回-1代表本对象比传入的对象大。

本类中也为其他的属性提供了相应的get和set方法。

import java.io.Serializable;

/**
 * 索引对象
 **/
public class IndexObject implements Comparable<IndexObject>,Serializable{
	
	private Long id;

	private String title;

	private String keywords;

	private String descripton;

	private String postDate;

	public String getPostDate() {
		return postDate;
	}

	public void setPostDate(String postDate) {
		this.postDate = postDate;
	}

	public String getDescripton() {
		return descripton;
	}

	public void setDescripton(String descripton) {
		this.descripton = descripton;
	}
	private String url;

	/*相似度*/
	private float score;


	public String getKeywords() {
		return keywords;
	}

	public void setKeywords(String keywords) {
		this.keywords = keywords;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}
	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public float getScore() {
		return score;
	}

	public  void setScore(float score) {
		this.score = score;
	}	
	public IndexObject() {
		super();
	}
	public IndexObject(Long _id, String _keywords, String _descripton, String _postDate, float _score) {
		super();
		this.id = _id;
		this.keywords = _keywords;
		this.score = _score;
		this.descripton=_descripton;
		this.postDate=_postDate;
	}
	@Override
	public int compareTo(IndexObject o) {
		if(this.score < o.getScore()){
			return 1;
		}else if(this.score > o.getScore()){
			return -1;
		}
		return 0;
	}
}

三、总结

在本篇博客中,主要对本项目中的一些工具类进行了源码分析。总的来说,还是有不少收获的。在未来的项目实训中,我希望能将这种思想渗入到我的代码中,在这个过程中,感谢老师,小组成员对我的帮助,经过我们小组的讨论,才最终成功地完成了本次源码的阅读。希望在接下来的学习中还能更近一步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值