Java校验XML文件

前言


记录第一次写XML校验的经验。最近接了个任务是写个程序校验一些指定格式的XML文件。原本我想这种写个xsd就可以校验了,但是这样不能实现批量处理,每个文件都要打开来看有没有格式错误,还不如写个程序把所有文件的错误收集起来展示。

XML转Java对象


XML转Java对象比较简单,javax.xml.bind包下有个工具类JAXB提供了XML转Java和Java转XML的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
解组

/**
* Reads in a Java object tree from the given XML input.
*
* @param xml
* Reads the entire file as XML.
*/
public static <T> T unmarshal( File xml, Class<T> type ) {
try {
JAXBElement<T> item = getContext(type).createUnmarshaller().unmarshal(new StreamSource(xml), type);
return item.getValue();
} catch (JAXBException e) {
throw new DataBindingException(e);
}
}


编组
public static void marshal( Object jaxbObject, Writer xml ) {
_marshal(jaxbObject,xml);
}

像上面看到的,准备一个XML,然后建一个对应的Java对象,Java直接就可以给转了,但是等等。。这里有个难点是如果XML本身很复杂,那么围绕这个XML建模就很难了,很容易出错。然后我百度到有同学用一个叫做xmlspy的工具可以生成XML文件对应的xsd文件,然后Java又提供一种根据xsd生成对象的方法。(所以一直想问Java为什么不干脆帮我们生成xsd省略这个过程。。)

然后就下载xmlspy并安装,使用。(xmlspy本身是付费的,我是在CSDN上面随便找的一个绿色版)

使用截图:

文件 > 打开(选择对应的XML文件)

DTD/模式 > 产生DTD(生成xsd文件)

根据xsd文件生成对象

1
2
3
4
1、使用方法
xjc fileName.xsd -d 生成java实体类的目录 -p 生成的包名

2. eg: xjc catalog.xsd -d d: \test -p com.xjc.bean

然后拿到了生成的对象,就可以通过JAXB来做转换了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author zhangxingrui
* @create 2018-09-08 11:33
**/
public class JaxbUtil {

public static void convertToXml(Object obj, File file) {
JAXB.marshal(obj,file);
}

public static <T> T convertToJavaBean(Class<T> clz, File file) {
return JAXB.unmarshal(file, clz);
}

}

文件上传


现在Java对象有了,然后就开始准备做校验了,但是做校验之前想到这个校验工具是给同事们用的,为了方便他们使用就做个Web好了。

思路:在Web端选择要校验的文件,然后上传保存在服务器上,每个用户上传的files为一组,校验之后返回这一组文件的errors。

多文件上传页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div class="container blog-content-container">
<form id="form" method="post" action="/upload/multipleFile" enctype="multipart/form-data" >
<div class="form-group col-md-5">
<label for="files" class="col-form-label">Please choose the xml files</label>
<input type="file" id="files" class="form-control" name="files" multiple />
</div>
<div class="form-group col-md-5">
<button type="button" id="submitFiles" class="btn btn-success"
onclick="validateXml()">
校验
</button>
</div>
</form>
</div>

<div th:replace="~{fragments/footer :: footer}">...</div>

<script>
function validateXml(){

var files = $("#files").val();
if(files == null || files === "" || files === "undefined"){
layer.msg("文件不能为空", {icon: 2,timeout:2000});
return;
}

// 主动提交
$("#form").submit();
}
</script>

多文件上传Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
private File saveFile(MultipartFile file) throws IOException {
// Get the file and save it somewhere
Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename() +
UUID.randomUUID());
File dest = new File(path.toString());
if(!dest.getParentFile().exists()){
dest.getParentFile().mkdir();
}
byte[] bytes = file.getBytes();
Files.write(path, bytes);
return dest;
}

记得使用完文件之后删除文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @Author: xingrui
* @Description: 校验之后删除文件
* @Date: 13:05 2018/9/9
*/
private void deleteFiles(String path){
File file = new File(path);
if(file.exists()){
if(file.isDirectory()){//如果是文件夹
File[] fileList = file.listFiles();//获取文件夹中所有子级文件/夹
for (File item : fileList) {
if(item.isDirectory()){
deleteFiles(item.getPath());
}else{
item.delete();
}
}
}
}
}

自定义异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @author zhangxingrui
* @create 2018-09-12 13:45
**/
public class MyException extends Exception{

private String fileName;

public MyException(String message) {
super(message);
}

public MyException(String message, String fileName) {
super(message);
this.fileName = fileName;
}

public String getFileName() {
return fileName;
}

public void setFileName(String fileName) {
this.fileName = fileName;
}
}

全局异常处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @author zhangxingrui
* @create 2018-09-09 10:52
**/
@ControllerAdvice
public class GlobalExceptionHandler {

private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

@ExceptionHandler(MyException.class)
public String handleError(MyException e, RedirectAttributes redirectAttributes) {
logger.error("{}", e.getStackTrace());
redirectAttributes.addFlashAttribute("fileName", e.getFileName());
redirectAttributes.addFlashAttribute("message", GeneralConstant.FAILED);
redirectAttributes.addFlashAttribute("error", e.getMessage());
return "redirect:/upload/error";
}

}

异常返回控制器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@GetMapping("/error")
public ModelAndView error(Model model,
@ModelAttribute("fileName") String fileName,
@ModelAttribute("message") String message,
@ModelAttribute("error") String error){
List<Message> messages = new ArrayList<>();
List<Error> errors = new ArrayList<>();
Message item_message = new Message(fileName, message);
Error item_error = new Error(error);
errors.add(item_error);
item_message.setErrors(errors);
messages.add(item_message);
model.addAttribute("messages", messages);
return new ModelAndView("status", "messageModel", model);
}

错误收集


刚开始做的是比如一个人上传100个文件,这些文件里面有一个文件里面的一个标签不对,就直接把异常抛出来告诉他这个文件错了,请修改以后再校验,反复操作,直到校验成功为止。后来觉得这样的也太low了,于是改成了一个人上传100个文件,中间任何一个文件的任何一个标签或属性错误都不抛异常,只是把异常收集起来,待最后返回界面的时候再把这些全部查出来。

然后就在想怎么收集错误,最后发现使用h2内存数据库最方便,springboot和h2也天然契合,就把errors都放h2了,再开个定时任务定期清楚一下不常用的数据。

zhangxingrui wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!