前面提到了打包的操作,自然不得不说一下maven了。
可以将maven理解为管理jar包的工具:既可以管理依赖的jar包,也可以简化打包的操作。
关于maven的介绍,网上资料很多,这里只在用到时,简单介绍命令。
maven的安装配置,以及Eclipse的m2e插件安装,请自行查找相关资料。
命令行创建maven项目,请参考官方提供的 maven in 5 minutes(http://maven.apache.org/guides/getting-started/maven-in-five-minutes.html)
安装过m2e插件的eclipse可执行通过new--》other-->maven project创建
为了简化操作,maven提供了多种模板可供选择,这将简化了基础包的创建,以及一些依赖包的引入。
这里选择最基础的 maven-archetype-quickstart。
首先是依赖包的导入,在pom.xml配置相关的依赖包
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.10-FINAL</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.10-FINAL</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
scope代表的执行范围,test指只在测试阶段运行,junit不会被打入到正式的jar包中。
相关的jar包,你可以根据名字在maven仓库中搜索。
maven仓库有:http://search.maven.org 和 http://mvnrepository.com/等
maven只是工具,辅助开发之用,核心还是代码。
下面就来谈谈代码。之前的代码,虽然实现了功能,但我一直有一个疑问:这是面向对象吗?
相信不少人,学习java的第一课就被告知:万物皆对象。
但是之前的代码,几乎都是static方法,纯粹的面向过程。想事物抽象成java对象,也许很简单,但我觉得这个思考过程是苦恼的。
做了很多尝试,结果都是舍本逐末,为了面向对象而面向对象。
思考很多,得出自认为的结论:工具类和对象的区别在于是否需要共享的数据属性。 如果有private 和set、get的属性,则需要对象;否则,只为了某一功能,和其他方法没有交互,又需要大量被其他类调用,则可采用static方法的工具类。
直接动手写代码,确实不是一个好习惯,必须在一开始想好整体的结构。停止写代码,苦思冥想后,试图切换一个切入点:
以前是从ExcelUtil类开始想起,然后是WordUtil。
现在,我在思考,
首先,如果我只暴露给别人一个方法,用来实现这一功能,我需要哪些东西。
答案:定义的接口是需要word文件和excel文件的路径,已经生成文件的目录。autoExport(String wordPath, String excelPath, String exportDir)
其次,获得了这些参数,怎么处理他们呢?
答案:怎么处理这个文件,只有他们本身最清楚。于是,我需要两个对象,一个负责读取excel,另一个负责读取word模板,替换并生成新的word文件。而具体怎么处理,则应该是对象本身的事,和接口无关。
再次,两个对象之前有些相同的属性,如文件路径,文件名,类型等。是不是可以将相同属性放置到父类对象中?
答案:必须的。
最后,如果暴露的接口是工具类,static方法,那么怎么调用对象的方法呢?
答案:可以将工具类作为static对象属性放入接口类中,并在调用autoExport时,先new这两个工具类。
本来就是很抽象的事,只是这么说就更抽象了,所以我们还是来看代码:
首先是OfficeFileType类,增加了getLabe()l和parse()方法,分别用来区分word文件还是excel文件和根据后缀得到type枚举
package com.crick.excel2word.core.enumeration;
public enum OfficeFileType {
WORD_03_TYPE(".doc"),
WORD_07UP_TYPE(".docx"),
EXCEL_03_TYPE(".xls"),
EXCEL_07UP_TYPE(".xlsx")
;
private OfficeFileType(String suffix) {
this.suffix = suffix;
}
private String suffix;
public String getSuffix() {
return suffix;
}
public String getLabel() {
switch (this) {
case EXCEL_03_TYPE:
case EXCEL_07UP_TYPE:
return "excel";
case WORD_03_TYPE:
case WORD_07UP_TYPE:
return "word";
default:
return "";
}
}
public static OfficeFileType parse(String suffix) {
OfficeFileType[] values = OfficeFileType.values();
for (OfficeFileType type : values) {
if(suffix.equals(type.getSuffix())) {
return type;
}
}
return null;
}
}
然后是两个工具对象的父对象,提供了一些共有属性,并通过构造函数初始化这些数值,所以属性之提供了get方法。
并且提供了一个scanDir()方法,可通过搜索得到某一目录下的某种类型文件的集合。
package com.crick.excel2word.util;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import com.crick.excel2word.core.enumeration.OfficeFileType;
public class OfficeFileUtil {
private String fullPath;
private String dirPath;
private String fileName;
private String fileSuffix;
private File file;
private OfficeFileType officeFileType;
protected OfficeFileUtil(String fullPath) {
file = new File(fullPath);
if(file.exists()) {
this.fullPath = fullPath;
this.dirPath = fullPath.substring(0, fullPath.lastIndexOf(File.separator));
this.fileName = fullPath.substring(fullPath.lastIndexOf(File.separator)+1, fullPath.length());
this.fileSuffix = fileName.substring(fileName.lastIndexOf("."), fileName.length());
this.officeFileType = OfficeFileType.parse(this.fileSuffix);
}
}
public String getDirPath() {
return dirPath;
}
public String getFileName() {
return fileName;
}
public String getFullPath() {
return fullPath;
}
public File getFile() {
return file;
}
public String getSuffix() {
return fileSuffix;
}
public OfficeFileType getOfficeFileType() {
return officeFileType;
}
/**
* 扫描文件下某类型的文件
*
* @param dirPath 目录路径
* @param types office文件类型
* @return
*/
protected List<File> scanDir(String dirPath, OfficeFileType... types) {
List<File> files = null;
File dir = new File(dirPath);
if (dir.exists() && dir.isDirectory() && types.length >0){
files = new ArrayList<File>();
for (File file : dir.listFiles()) {
if (!file.isDirectory() && isEndsWith(file.getName(), types)) {
files.add(file);
}
}
}
return files;
}
private boolean isEndsWith(String fileName, OfficeFileType[] types) {
String suffix = fileName.substring(fileName.lastIndexOf("."), fileName.length());
for (int i=0; i<types.length; i++) {
if (suffix.equals(types[i].getSuffix())){
return true;
}
}
return false;
}
}
WordUtil对象,去掉了static方法,并且和直接获取父类对象中的属性,所以减少了方法参数。
并提供了scanDirWord方法,可搜索某一目录下的word文件(包括03和07以后)
package com.crick.excel2word.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import com.crick.excel2word.core.enumeration.OfficeFileType;
public class WordUtil extends OfficeFileUtil {
public WordUtil(String fullPath) {
super(fullPath);
}
public List<File> scanDirWord(String dirPath) {
return super.scanDir(dirPath, OfficeFileType.WORD_03_TYPE, OfficeFileType.WORD_07UP_TYPE);
}
public void export(Map<String, Object> params, String exportDir, String exportName) {
FileOutputStream fos = null;
if(exportName == null) {
exportName = "未命名";
}
try {
HWPFDocument doc = new HWPFDocument(new FileInputStream(getFile()));
Range range = doc.getRange();
for (String key : params.keySet()) {
range.replaceText(key, params.get(key).toString());
}
fos = new FileOutputStream(getRenameFile(exportDir, exportName, getOfficeFileType()));
doc.write(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
fos = null;
}
}
}
}
private File getRenameFile(String path, String docName, OfficeFileType type) {
//解决重名问题
int i = 1;
File doc = new File(path + docName+type.getSuffix());
while (doc.exists()) {
doc = new File(path + docName+"("+i+")"+type.getSuffix());
i++;
}
return doc;
}
}
ExcelUtil对象,好吧,同上
package com.crick.excel2word.util;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import com.crick.excel2word.core.enumeration.OfficeFileType;
public class ExcelUtil extends OfficeFileUtil {
public ExcelUtil(String fullPath) {
super(fullPath);
}
public List<File> scanDirExcel(String dirPath) {
return super.scanDir(dirPath, OfficeFileType.EXCEL_03_TYPE, OfficeFileType.EXCEL_07UP_TYPE);
}
public List<Map<String, Object>> extract() {
List<Map<String, Object>> results = new ArrayList<Map<String,Object>>();
Workbook book;
try {
book = new HSSFWorkbook(new BufferedInputStream(new FileInputStream(getFile())));
Sheet sheet = book.getSheetAt(0);
if (sheet.getLastRowNum() == 0) {
return null;
}
List<String> keys = getKeyList(sheet.getRow(0));
for (int i=sheet.getFirstRowNum()+1; i <= sheet.getLastRowNum(); i++) {
Map<String, Object> param = extractRow(sheet.getRow(i), keys);
results.add(param);
}
} catch (Exception e) {
e.printStackTrace();
}
return results;
}
private Map<String, Object> extractRow(Row row, List<String> keys){
Map<String, Object> params = new HashMap<String, Object>();
for (int i=0; i<row.getLastCellNum(); i++) {
if (keys.get(i) == null || keys.get(i) == "") {
continue;
}
row.getCell(i).setCellType(Cell.CELL_TYPE_STRING);
params.put("#{"+keys.get(i)+"}", row.getCell(i));
}
return params;
}
/**
* 第一行作为key,即mark标签名
*
* @param row
* @return
*/
private List<String> getKeyList(Row row) {
List<String> keys = new ArrayList<String>();
for (int i=0; i<=row.getLastCellNum();i++) {
if (row.getCell(i) != null) {
row.getCell(i).setCellType(Cell.CELL_TYPE_STRING);
}
keys.add(row.getCell(i)==null?"":row.getCell(i).toString().toLowerCase());
}
//如果未指定name标签,则默认第一列为生成的word文件名
if (!keys.contains("name")) {
keys.set(0, "name");
}
return keys;
}
}
最后是暴露出去的工具类,也就是我们定义的接口Excel2Words
package com.crick.excel2word;
import java.io.File;
import java.util.List;
import java.util.Map;
import com.crick.excel2word.util.ExcelUtil;
import com.crick.excel2word.util.WordUtil;
public class Excel2Words {
private static WordUtil wordUtil;
private static ExcelUtil excelUtil;
private static void init(String wordPath, String excelPath) {
wordUtil = new WordUtil(wordPath);
excelUtil = new ExcelUtil(excelPath);
}
public static void autoExport(String wordPath, String excelPath, String exportDir) {
init(wordPath, excelPath);
if (!exportDir.endsWith(File.separator)) {
exportDir = exportDir.concat(File.separator);
}
List<Map<String, Object>> rowList = excelUtil.extract();
for (Map<String, Object> rowParam : rowList) {
wordUtil.export(rowParam, exportDir, rowParam.get("#{name}").toString());
}
}
}
至此,代码阶段完成。
我不确定这就是面向对象的思考方式和变成方法,但至少我在改变着自己编程思维。
最大的收获是,知道了思考的作用远大于写代码本身。
最多的bug是关于目录的"/",这个问题出现了多次,在代码中都增加了相关控制。
关于String是否为空的控制,并没有过多的判断。我觉得这些基础的判断应该在上次,调用者进行判断。
maven给项目打包,可以通过命令行,在pom所在文件夹使用命令
mvn clean install
生成的jar包在target目录下。
maven有一个有趣的机制是,你可以在一行中输入多个命令,maven会依次执行。
上面的命令,其实是mvn clean 清除target目录下的内容, mvn install 打包。
其实maven打包的命令是mvn package,同样也会在打包的同时进行编译和测试,但是install多了一项功能,就是想jar包发布到本地maven仓库中,因此mvn clean install更加常用。
如果将excel2word-0.0.3-SNAPSHOT.jar放入其他项目中使用,会发现无法运行,因为这个项目可能没有引入依赖的poi等jar包。
而如果将依赖包也打入excel2word-0.0.3-SNAPSHOT.jar中,则有可能因为其他项目也引入的相同包,而造成冲突。面对这种问题,暂时我还不知道好的解决方法,但是我还是提供一下将依赖包打入jar的方法,可根据具体情形分析使用。
利用maven的assembly插件。现在pom增加相关plugin的配置,然后执行命令
mvn clean install assembly:assembly
附完整pom.xml文件
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.crick</groupId>
<artifactId>excel2word</artifactId>
<version>0.0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>excel2word</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.10-FINAL</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.10-FINAL</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>