兩服務器使用文件夾共享文件的方式交換數據——XML

業務場景:

  1. 兩個系統交換數據,因爲某些特殊原因,不能使用接口調用的方式
  2. 使用共享文件夾來處理問題,通過讀寫文件交換數據
  3. A服務器將XML數據文件放在A目錄下
  4. B服務器讀取A目錄下的文件,同時將文件轉移到B目錄下,然後,將自己傳遞的XML數據文件放在C目錄下。
  5. A服務器,將C目錄的文件讀取,同時將文件轉移到D目錄下,將自己的XML數據文件放在A目錄下。
  6. 這樣一輪完成一次數據交換。

業務需求說明:

  1. 使用XML傳遞數據
  2. 通過共享目錄來交換數據
  3. 通過掃描文件夾來讀取數據

技術點:

  1. xml字符串數據與pojo對象的互相轉換
  2. 文件的讀寫操作,包括文件鎖的問題
  3. 文件夾的監聽操作,當文件創建時操作

下面通過三個技術點來解決問題,這裏只模仿一個服務器發操作流程,另一個一樣

一、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目錄下存有我們的文件,說明轉移成功了,這樣就完成了我們上述的需求了。

這裏只是提供一個業務問題的解決方案,如果有更好的方案可以在評論區留言,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章