业务场景:
- 两个系统交换数据,因为某些特殊原因,不能使用接口调用的方式
- 使用共享文件夹来处理问题,通过读写文件交换数据
- A服务器将XML数据文件放在A目录下
- B服务器读取A目录下的文件,同时将文件转移到B目录下,然后,将自己传递的XML数据文件放在C目录下。
- A服务器,将C目录的文件读取,同时将文件转移到D目录下,将自己的XML数据文件放在A目录下。
- 这样一轮完成一次数据交换。
业务需求说明:
- 使用XML传递数据
- 通过共享目录来交换数据
- 通过扫描文件夹来读取数据
技术点:
- xml字符串数据与pojo对象的互相转换
- 文件的读写操作,包括文件锁的问题
- 文件夹的监听操作,当文件创建时操作
下面通过三个技术点来解决问题,这里只模仿一个服务器发操作流程,另一个一样
一、xml字符串数据与pojo对象的互相转换
首先我们需要一个POJO对象
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.FIELD)
// XML文件中的根标识
@XmlRootElement(name = "User")
// 控制JAXB 绑定类中属性和字段的排序
@XmlType(propOrder = {
"userId",
"userName",
"password",
"birthday",
"money",
"userList",
})
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// 读取的文件目录
public static final String UrlA = "D:\\AA\\User.xml";
// 转移的文件目录
public static final String UrlB = "D:\\BB";
// 用户Id
private int userId;
// 用户名
private String userName;
// 用户密码
private String password;
// 用户生日
private Date birthday;
// 用户钱包
private double money;
private List<User> userList;
public List<User> getUserList() {
return userList;
}
public void setUserList(List<User> userList) {
this.userList = userList;
}
public User() {
super();
}
public User(int userId, String userName, String password, Date birthday,
double money) {
super();
this.userId = userId;
this.userName = userName;
this.password = password;
this.birthday = birthday;
this.money = money;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
", birthday=" + birthday +
", money=" + money +
", userList=" + userList +
'}';
}
}
下面是一个转换工具
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import static java.lang.Thread.sleep;
public class XMLUtil {
//设置锁的休眠时间
private static final long INTERVAL = TimeUnit.MILLISECONDS.toMillis(500);
/**
* 将对象直接转换成String类型的 XML输出
*
* @param obj
* @return
*/
public static String convertToXml(Object obj) {
// 创建输出流
StringWriter sw = new StringWriter();
try {
// 利用jdk中自带的转换类实现
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = context.createMarshaller();
// 格式化xml输出的格式
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
Boolean.TRUE);
// 将对象转换成输出流形式的xml
marshaller.marshal(obj, sw);
} catch (JAXBException e) {
e.printStackTrace();
}
return sw.toString();
}
@SuppressWarnings("unchecked")
/**
* 将String类型的xml转换成对象
*/
public static Object convertXmlStrToObject(Class clazz, String xmlStr) {
Object xmlObject = null;
try {
JAXBContext context = JAXBContext.newInstance(clazz);
// 进行将Xml转成对象的核心接口
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader sr = new StringReader(xmlStr);
xmlObject = unmarshaller.unmarshal(sr);
} catch (JAXBException e) {
e.printStackTrace();
}
return xmlObject;
}
}
我们写个测试类测试一下
@Test
public void test03() {
// 创建需要转换的对象
User user = new User(1, "Steven", "@sun123", new Date(), 1000.0);
User user1 = new User(2, "Steven", "@sun123", new Date(), 2000.0);
User user2 = new User(3, "Steven", "@sun123", new Date(), 3000.0);
User user3 = new User(4, "Steven", "@sun123", new Date(), 4000.0);
List<User> userList = new ArrayList<>();
userList.add(user1);
userList.add(user2);
userList.add(user3);
user.setUserList(userList);
System.out.println("---将对象转换成string类型的xml Start---");
String str = XMLUtil.convertToXml(user);
System.out.println(str);
System.out.println("---将对象转换成string类型的xml End---");
System.out.println("---将string类型的xml转换成对象 Start---");
User user4 = (User) XMLUtil.convertXmlStrToObject(User.class, str);
System.out.println(user4.toString());
System.out.println("---将string类型的xml转换成对象 End---");
}
测试结果
---将对象转换成string类型的xml Start---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User>
<userId>1</userId>
<userName>Steven</userName>
<password>@sun123</password>
<birthday>2020-01-19T20:56:53.046+08:00</birthday>
<money>1000.0</money>
<userList>
<userId>2</userId>
<userName>Steven</userName>
<password>@sun123</password>
<birthday>2020-01-19T20:56:53.046+08:00</birthday>
<money>2000.0</money>
</userList>
<userList>
<userId>3</userId>
<userName>Steven</userName>
<password>@sun123</password>
<birthday>2020-01-19T20:56:53.046+08:00</birthday>
<money>3000.0</money>
</userList>
<userList>
<userId>4</userId>
<userName>Steven</userName>
<password>@sun123</password>
<birthday>2020-01-19T20:56:53.046+08:00</birthday>
<money>4000.0</money>
</userList>
</User>
---将对象转换成string类型的xml End---
---将string类型的xml转换成对象 Start---
User{userId=1, userName='Steven', password='@sun123', birthday=Sun Jan 19 20:56:53 GMT+08:00 2020, money=1000.0, userList=[User{userId=2, userName='Steven', password='@sun123', birthday=Sun Jan 19 20:56:53 GMT+08:00 2020, money=2000.0, userList=null}, User{userId=3, userName='Steven', password='@sun123', birthday=Sun Jan 19 20:56:53 GMT+08:00 2020, money=3000.0, userList=null}, User{userId=4, userName='Steven', password='@sun123', birthday=Sun Jan 19 20:56:53 GMT+08:00 2020, money=4000.0, userList=null}]}
---将string类型的xml转换成对象 End---
这样就完成了数据的转换工作
二、文件的读写操作,包括文件锁的问题
下面解决文件的读写操作,当我们读取和写文件时先锁文件,在操作文件,如果锁不了,说明有其他对象在操作文件,于是线程进入等待状态,等待时间为500毫秒每轮。读取完数据后将文件转移到B文件夹。
下面在改造我们的xml工具类,添加读写函数
/**
* 将对象根据路径转换成xml文件
*
* @param obj
* @param path
* @return
*/
public static void convertToXmlFile(Object obj, String path) {
Calendar calstart = Calendar.getInstance();
File file = new File(path);
try {
if (!file.exists()) {
file.createNewFile();
}
//对该文件加锁
RandomAccessFile out = new RandomAccessFile(file, "rw");
FileChannel fcout = out.getChannel();
FileLock flout = null;
while (true) {
try {
flout = fcout.tryLock();
break;
} catch (Exception e) {
System.out.println("有其他线程正在操作该文件,当前线程休眠500毫秒");
sleep(INTERVAL);
}
}
StringBuffer sb = new StringBuffer();
sb.append(convertToXml(obj));
out.write(sb.toString().getBytes("utf-8"));
flout.release();
fcout.close();
out.close();
out = null;
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
/**
* 将file类型的xml转换成对象
*/
public static Object convertXmlFileToObject(Class clazz, String xmlPath, String newPath) {
StringBuffer sb = new StringBuffer();
try {
File file = new File(xmlPath);
//给该文件加锁
RandomAccessFile fis = new RandomAccessFile(file, "rw");
FileChannel fcin = fis.getChannel();
FileLock flin = null;
while (true) {
try {
flin = fcin.tryLock();
break;
} catch (Exception e) {
System.out.println("有其他线程正在操作该文件,当前线程休眠500毫秒");
sleep(INTERVAL);
}
}
byte[] buf = new byte[1024];
while ((fis.read(buf)) != -1) {
sb.append(new String(buf, "utf-8"));
buf = new byte[1024];
}
flin.release();
fcin.close();
fis.close();
fis = null;
if (file.getName().indexOf(".") >= 0) {
String filename = file.getName().substring(0, file.getName().lastIndexOf("."));
file.renameTo(new File(newPath + "\\" + filename + (new SimpleDateFormat("yyyyMMddHHmmss")).format(new Date()) + ".xml"));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return convertXmlStrToObject(clazz, sb.toString().trim());
}
下面测试函数
@Test
public void test04() {
// 创建需要转换的对象
User user = new User(1, "Steven", "@sun123", new Date(), 1000.0);
User user1 = new User(2, "Steven", "@sun123", new Date(), 2000.0);
User user2 = new User(3, "Steven", "@sun123", new Date(), 3000.0);
User user3 = new User(4, "Steven", "@sun123", new Date(), 4000.0);
List<User> userList = new ArrayList<>();
userList.add(user1);
userList.add(user2);
userList.add(user3);
user.setUserList(userList);
System.out.println("---将对象转换成string类型的xml文件 Start---");
XMLUtil.convertToXmlFile(user, User.UrlA);
System.out.println("---将对象转换成string类型的xml文件 End---");
System.out.println("---将string类型的xml文件转换成对象 Start---");
System.out.println(XMLUtil.convertXmlFileToObject(User.class, User.UrlA, User.UrlB).toString());
System.out.println("---将string类型的xml文件转换成对象 End---");
}
测试结果:
---将对象转换成string类型的xml文件 Start---
---将对象转换成string类型的xml文件 End---
---将string类型的xml文件转换成对象 Start---
User{userId=1, userName='Steven', password='@sun123', birthday=Sun Jan 19 21:16:29 GMT+08:00 2020, money=1000.0, userList=[User{userId=2, userName='Steven', password='@sun123', birthday=Sun Jan 19 21:16:29 GMT+08:00 2020, money=2000.0, userList=null}, User{userId=3, userName='Steven', password='@sun123', birthday=Sun Jan 19 21:16:29 GMT+08:00 2020, money=3000.0, userList=null}, User{userId=4, userName='Steven', password='@sun123', birthday=Sun Jan 19 21:16:29 GMT+08:00 2020, money=4000.0, userList=null}]}
---将string类型的xml文件转换成对象 End---
这样我们就完成了锁的逻辑了
三、文件夹的监听操作,当文件创建时操作
我这里使用的是SpringBoot做测试,有commons.io ,如果不是,可以导入这个工具:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
这里使用观察者模式,需要一个观察者
import com.hello.service.ListenerService;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import java.io.File;
public class FileListener extends FileAlterationListenerAdaptor {
// 声明业务服务
private ListenerService listenerService;
// 采用构造函数注入服务
public FileListener(ListenerService listenerService) {
this.listenerService = listenerService;
}
// 文件创建执行
@Override
public void onFileCreate(File file) {
listenerService.doSomething();
}
// 文件创建修改
@Override
public void onFileChange(File file) {
// 触发业务
listenerService.doSomething();
}
// 文件创建删除
@Override
public void onFileDelete(File file) {
listenerService.doNothing();
}
// 目录创建
@Override
public void onDirectoryCreate(File directory) {
}
// 目录修改
@Override
public void onDirectoryChange(File directory) {
}
// 目录删除
@Override
public void onDirectoryDelete(File directory) {
}
// 轮询开始
@Override
public void onStart(FileAlterationObserver observer) {
System.out.println("轮询开始");
}
// 轮询结束
@Override
public void onStop(FileAlterationObserver observer) {
System.out.println("轮询结束");
}
}
再去创建一个工厂,监控对象是A目录下的xml文件
import com.hello.service.ListenerService;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.HiddenFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.concurrent.TimeUnit;
@Component
public class FileListenerFactory {
// 设置监听路径
private final String monitorDir = "D:\\AA";
// 设置轮询间隔
private final long interval = TimeUnit.SECONDS.toMillis(5);
// 自动注入业务服务
@Autowired
private ListenerService listenerService;
public FileAlterationMonitor getMonitor() {
// 创建过滤器
IOFileFilter directories = FileFilterUtils.and(
FileFilterUtils.directoryFileFilter(),
HiddenFileFilter.VISIBLE);
IOFileFilter files = FileFilterUtils.and(
FileFilterUtils.fileFileFilter(),
FileFilterUtils.suffixFileFilter(".xml"));
IOFileFilter filter = FileFilterUtils.or(directories, files);
// 装配过滤器
// FileAlterationObserver observer = new FileAlterationObserver(new File(monitorDir));
FileAlterationObserver observer = new FileAlterationObserver(new File(monitorDir), filter);
// 向监听者添加监听器,并注入业务服务
observer.addListener(new FileListener(listenerService));
// 返回监听者
return new FileAlterationMonitor(interval, observer);
}
}
这里通过CommandLineRunner接口完成当服务启动后开始监听文件夹
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class FileListenerRunner implements CommandLineRunner {
@Autowired
private FileListenerFactory fileListenerFactory;
@Override
public void run(String... args) throws Exception {
// 创建监听者
FileAlterationMonitor fileAlterationMonitor = fileListenerFactory.getMonitor();
try {
// do not stop this thread
fileAlterationMonitor.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里我们需要一个业务功能的整合
import com.hello.bean.User;
import com.hello.service.ListenerService;
import com.hello.util.XMLUtil;
import com.hello.util.YamlFromConstant;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class ListenerServiceImpl implements ListenerService {
@Override
public void doSomething() {
System.out.println("检测文件夹检测操作数据");
System.out.println("---将String类型的xml转换成对象 Start---");
User userTest = (User) XMLUtil.convertXmlFileToObject(User.class, User.UrlA, User.UrlB);
System.out.println(userTest);
System.out.println("---将String类型的xml转换成对象 End---");
}
@Override
public void doNothing() {
System.out.println("检测文件夹什么也不做");
}
}
我们启动服务器日志如下
轮询开始
轮询结束
轮询开始
轮询结束
轮询开始
轮询结束
我们发现日志一直在刷这个日志
下面我们用测试类向A文夹写文件:
@Test
public void test01() {
// 创建需要转换的对象
User user = new User(1, "Steven", "@sun123", new Date(), 1000.0);
User user1 = new User(2, "Steven", "@sun123", new Date(), 2000.0);
User user2 = new User(3, "Steven", "@sun123", new Date(), 3000.0);
User user3 = new User(4, "Steven", "@sun123", new Date(), 4000.0);
List<User> userList = new ArrayList<>();
userList.add(user1);
userList.add(user2);
userList.add(user3);
user.setUserList(userList);
System.out.println("---将对象转换成string类型的xml文件 Start---");
// 将对象转换成string类型的xml
XMLUtil.convertToXmlFile(user, User.UrlA);
System.out.println("---将对象转换成string类型的xml文件 End---");
}
日志如下:
轮询结束
轮询开始
检测文件夹检测操作数据
---将String类型的xml文件转换成对象 Start---
User{userId=1, userName='Steven', password='@sun123', birthday=Sun Jan 19 21:34:27 GMT+08:00 2020, money=1000.0, userList=[User{userId=2, userName='Steven', password='@sun123', birthday=Sun Jan 19 21:34:27 GMT+08:00 2020, money=2000.0, userList=null}, User{userId=3, userName='Steven', password='@sun123', birthday=Sun Jan 19 21:34:27 GMT+08:00 2020, money=3000.0, userList=null}, User{userId=4, userName='Steven', password='@sun123', birthday=Sun Jan 19 21:34:27 GMT+08:00 2020, money=4000.0, userList=null}]}
---将String类型的xml文件转换成对象 End---
轮询结束
轮询开始
检测文件夹什么也不做
轮询结束
轮询开始
我们查看文件夹
location > D:\AA > dir
location > D:\AA > cd ../BB
location > D:\BB > dir
目录: D:\BB
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2020/1/19 21:34 900 User20200119213432.xml
location > D:\BB >
我们发现A目录下没有文件,B目录下存有我们的文件,说明转移成功了,这样就完成了我们上述的需求了。
这里只是提供一个业务问题的解决方案,如果有更好的方案可以在评论区留言,谢谢!