業務場景:
- 兩個系統交換數據,因爲某些特殊原因,不能使用接口調用的方式
- 使用共享文件夾來處理問題,通過讀寫文件交換數據
- 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目錄下存有我們的文件,說明轉移成功了,這樣就完成了我們上述的需求了。
這裏只是提供一個業務問題的解決方案,如果有更好的方案可以在評論區留言,謝謝!