1. 程式人生 > >一次程式碼重構之旅-快速讀寫xml檔案工具類封裝

一次程式碼重構之旅-快速讀寫xml檔案工具類封裝

   為了滿足系統的靈活性,有些功能經常需要用到配置檔案,一般是xml格式的居多.如何能快速讀寫配置檔案呢?

   以前都是用dom4j提供的api來讀寫xml檔案,用dom4j讀寫配置檔案總感覺像是在結構化的處理問題,能不能直接把xml檔案和JavaBean之間相互轉換呢?答案肯定是可以,xstream中提供了很簡單的方式將二者轉化,感覺這樣才像面向物件化處理問題.

xstream知識點簡單總結:
1.JavaBean到xml,用toXML()方法;Xml到JavaBean,用fromXML()方法;
2.轉換時別名對映:
  1)類別名,alias(String name, Class type)。 
  2)類成員別名,aliasField(String alias, Class definedIn, String fieldName) 
  3)類成員作為屬性別名,aliasAttribute(Class definedIn, String attributeName, String alias)
  4)去掉JavaBean中某個欄位不生成到xml中,omitField(definedIn, fieldName)
  5)去掉集合型別生成xml的父節點,addImplicitCollection(Class ownerType, String fieldName) 
  6)註冊一個轉換器,registerConverter(Converter converter)  
3.對映別名對應註解
  @XStreamAlias("xxx")
  @XStreamAsAttribute  
  @XStreamImplicit()或@XStreamImplicit(itemFieldName = "xxx")
  @XStreamOmitField
  @XStreamConverter(Class)

我比較傾向於使用註解來處理,這樣只要修改JavaBean上的內容就可以了.
實體類Stdent,配置註解:

import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("STUDENT")
public class Student {
	
	@XStreamAlias("NAME")
	private String name;
	
	@XStreamAlias("CLASS")
	private String classes;
	
	//get和set方法

	@Override
	public String toString() {
		return "Student [classes=" + classes + ", name=" + name + "]";
	}
}
    我覺得如果讀不到配置檔案就報錯不太友好,為了程式健壯性考慮應該在讀不到配置檔案時生成一個預設的下次就能讀到了.
第一版工具類:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

public class XmlUtil1 {
	private static final String file = "student1.xml";//執行main方法後重新整理整個工程可以找到student1.xml

	public void saveConfig(Student entity) {
		System.out.println("===儲存配置====");
		XStream stream = new XStream(new DomDriver("utf-8"));// xml檔案使用utf-8格式
		stream.autodetectAnnotations(true);// 設定自動匹配annotation.
		try {
			FileOutputStream out = new FileOutputStream(file);
			stream.toXML(entity, out);// 將實體類轉為xml並輸出到檔案.
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 從配置檔案中讀取配置,並自動轉換為對應的物件.
	 * 
	 * @return Student
	 */
	public Student getConfig() {
		XStream stream = new XStream(new DomDriver("utf-8"));// xml檔案使用utf-8格式
		stream.autodetectAnnotations(true);// 設定自動匹配annotation.
		FileInputStream input;
		Student entity;
		try {
			input = new FileInputStream(file);
			// 由於xstream-1.3.1.jar中有個bug,從xml檔案中讀出的內容autodetectAnnotations(true)不生效,必須用alias後才正常.
			stream.alias("STUDENT", Student.class);
			entity = (Student) stream.fromXML(input); // 從配置檔案中讀取配置,並自動轉換為對應的物件
		} catch (FileNotFoundException e1) {
			entity = setDefaultConfig();// 檔案不存在時建立一個預設的配置檔案.
			saveConfig(entity);
		}
		return entity;
	}

	// 建立一個預設的配置檔案,需重寫
	private Student setDefaultConfig() {
		Student stu = new Student();
		stu.setName("李坤");
		stu.setClasses("五期提高班");
		return stu;
	}

	public static void main(String[] args) {
		XmlUtil1 util = new XmlUtil1();
		System.out.println(util.getConfig());
	}
}
    當寫了兩三個配置檔案之後發現有很多內容是重複的,讀getConfig和寫saveConfig的過程每個配置檔案基本一致,只要修改一點點內容就行了.
    重構一下吧,以後修改的內容越少越好,考慮使用泛型(使用泛型的好處,把執行時錯誤帶入到編譯期發現),見下面的工具類以後不用修改讀getConfig和寫saveConfig的過程了,只需修改預設的配置檔案setDefaultConfig()方法即可.所以形成了第二版工具類:
注:由於xstream-1.3.1.jar中有個bug,從xml檔案中讀出的內容autodetectAnnotations(true)不生效,必須用alias後才正常.

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.io.xml.DomDriver;

/*
 * 封裝後的讀寫配置檔案的工具類,可以將設定註解後的物件與xml轉化. 
 * 寫入用saveConfig方法,讀取用getConfig方法
 * 
 * @author 李坤
 * 
 * 使用步驟: 
 * 1.引入工具類和xstream.jar
 * 2.修改檔名,可以用../代表上級資料夾路徑.
 * 3.寫一個類或幾個類並配置xstream註解,用於物件和xml之間的轉換.注意,一定要在需轉化的實體類上設定註解.
 * 4.修改setDefaultConfig方法,本方法的作用是當配置檔案找不到時自動生成一個預設的配置檔案,提高程式的健壯性.
 * 5.修改main方法可進行測試,寫入用saveConfig方法,讀取用getConfig方法.
 * 6.還可以加一些自己的邏輯處理自己的業務的方法.
 */
public class XmlUtil2<T> {
	private static final String file = "student2.xml";// 檔名,可以用../代表上級資料夾路徑
	Class<T> clazz;

	public XmlUtil2(Class<T> clazz) {
		this.clazz = clazz;
	}

	/**
	 *儲存xml檔案
	 * 
	 * @param entity
	 *            xml檔案對應的物件
	 */
	public void saveConfig(T entity) {
		System.out.println("===儲存配置====");
		XStream stream = new XStream(new DomDriver("utf-8"));// xml檔案使用utf-8格式
		stream.autodetectAnnotations(true);// 設定自動匹配annotation.
		try {
			FileOutputStream out = new FileOutputStream(file);
			stream.toXML(entity, out);// 將實體類轉為xml並輸出到檔案.
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 從配置檔案中讀取配置,並自動轉換為對應的物件.
	 * 
	 * @return T
	 */
	public T getConfig() {
		XStream stream = new XStream(new DomDriver("utf-8"));// xml檔案使用utf-8格式
		stream.autodetectAnnotations(true);// 設定自動匹配annotation.
		FileInputStream input;
		T entity;
		try {
			input = new FileInputStream(file);
			String alias = "";
			if (clazz.isAnnotationPresent(XStreamAlias.class)) {
				alias = clazz.getAnnotation(XStreamAlias.class).value();
			}
			stream.alias(alias, clazz);// 由於xstream-1.3.1.jar中有個bug,從xml檔案中讀出的內容autodetectAnnotations(true)不生效,必須用alias後才正常.
			entity = (T) stream.fromXML(input); // 從配置檔案中讀取配置,並自動轉換為對應的物件
		} catch (FileNotFoundException e1) {
			entity = setDefaultConfig();
			saveConfig(entity);// 檔案不存在時建立一個預設的配置檔案.
		}
		return entity;
	}

	// 建立一個預設的配置檔案,需重寫
	private T setDefaultConfig() {
		Student stu = new Student();
		stu.setName("李坤");
		stu.setClasses("五期提高班");
		T entity = (T) stu;
		return entity;
	}

	// 執行main方法可檢視測試結果.
	public static void main(String[] args) {
		XmlUtil2<Student> util = new XmlUtil2<Student>(Student.class);
		System.out.println(util.getConfig());
	}
}
    上面的工具類每次讀寫配置檔案還是需要複製一份並修改一個方法,感覺又多餘又違反了開閉原則,好吧,再重構一份出來,使用抽象類吧,子類繼承父類並實現抽象方法,這樣的好處是父類的方法以後完全不用修改了,需要讀寫一個新配置檔案繼承並實現抽象方法就行了,符合開閉原則.這樣就有了第三版程式碼:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.io.xml.DomDriver;

/*
 * 封裝後的讀寫配置檔案的工具類,可以將設定註解後的物件與xml轉化. 
 * 寫入用saveConfig方法,讀取用getConfig方法
 * 
 * @author 李坤
 * 
 * 使用步驟: 
 * 1.引入工具類和xstream.jar
 * 2.寫一個類或幾個類並配置xstream註解,用於物件和xml之間的轉換.注意,一定要在需轉化的實體類上設定註解.
 * 3.寫一個子類繼承XmlUtil3類,並且實現抽象方法.
 * 4.檔名,可以用../代表上級資料夾路徑.
 * 5.setDefaultConfig方法,本方法的作用是當配置檔案找不到時自動生成一個預設的配置檔案,提高程式的健壯性.
 * 6.修改main方法可進行測試,寫入用saveConfig方法,讀取用getConfig方法.
 * 7.還可以加一些自己的邏輯處理自己的業務的方法.
 */
public abstract class XmlUtil3<T> {
	abstract String setFileName();
	abstract Class<T> setClazz();
	// 建立一個預設的配置檔案,需重寫
	abstract T setDefaultConfig();

	/**
	 *儲存xml檔案
	 * 
	 * @param entity
	 *            xml檔案對應的物件
	 */
	public void saveConfig(T entity) {
		System.out.println("===儲存配置====");
		XStream stream = new XStream(new DomDriver("utf-8"));// xml檔案使用utf-8格式
		stream.autodetectAnnotations(true);// 設定自動匹配annotation.
		try {
			FileOutputStream out = new FileOutputStream(setFileName());
			stream.toXML(entity, out);// 將實體類轉為xml並輸出到檔案.
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 從配置檔案中讀取配置,並自動轉換為對應的物件.
	 * 
	 * @return T
	 */
	public T getConfig() {
		XStream stream = new XStream(new DomDriver("utf-8"));// xml檔案使用utf-8格式
		stream.autodetectAnnotations(true);// 設定自動匹配annotation.
		FileInputStream input;
		T entity;
		try {
			input = new FileInputStream(setFileName());
			String alias = "";
			if (setClazz().isAnnotationPresent(XStreamAlias.class)) {
				alias = setClazz().getAnnotation(XStreamAlias.class).value();
			}
			stream.alias(alias, setClazz());// 由於xstream-1.3.1.jar中有個bug,從xml檔案中讀出的內容autodetectAnnotations(true)不生效,必須用alias後才正常.
			entity = (T) stream.fromXML(input); // 從配置檔案中讀取配置,並自動轉換為對應的物件
		} catch (FileNotFoundException e1) {
			entity = setDefaultConfig();// 檔案不存在時建立一個預設的配置檔案.
			if (entity !=null) {
				saveConfig(entity);
			}
		}
		return entity;
	}
}
public class StuConfig3 extends XmlUtil3<Student>{

	@Override
	Class<Student> setClazz() {
		return Student.class;
	}

	@Override
	Student setDefaultConfig() {
		Student stu = new Student();
		stu.setName("李坤");
		stu.setClasses("五期提高班");
		return stu;
	}

	@Override
	String setFileName() {
		return "student3.xml";
	}

	public static void main(String[] args) {
		StuConfig3 config = new StuConfig3();
		System.out.println(config.getConfig());
	}
}
    感覺上一版中抽象方法太多了,繼承一次需要實現三個抽象方法,能不能簡化一下讓類更易用呢?好吧,那就簡化一下,因為檔名和泛型的真實類在上一版的抽象方法實現時只有一行程式碼return xxx;所以咱們可以拿它做文章,把它放到構造方法中,強制在構造這個類的時候初始化它們,OK,動手實現第四版程式碼:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.io.xml.DomDriver;

/*
 * 封裝後的讀寫配置檔案的工具類,可以將設定註解後的物件與xml轉化. 
 * 寫入用saveConfig方法,讀取用getConfig方法
 * 
 * @author 李坤
 * 
 * 使用步驟: 
 * 1.引入工具類和xstream.jar
 * 2.寫一個類或幾個類並配置xstream註解,用於物件和xml之間的轉換.注意,一定要在需轉化的實體類上設定註解.
 * 3.寫一個子類繼承XmlUtil3類,並且實現抽象方法.
 * 4.檔名,可以用../代表上級資料夾路徑.
 * 5.setDefaultConfig方法,本方法的作用是當配置檔案找不到時自動生成一個預設的配置檔案,提高程式的健壯性.
 * 6.修改main方法可進行測試,寫入用saveConfig方法,讀取用getConfig方法.
 * 7.還可以加一些自己的邏輯處理自己的業務的方法.
 */
public abstract class XmlUtil4<T> {
	// 建立一個預設的配置檔案,需重寫
	abstract T setDefaultConfig();

	private String file;
	private Class<T> clazz;

	public XmlUtil4(Class<T> clazz, String file) {
		this.clazz = clazz;
                this.file = file;
                String dirPath = file.substring(0, file.lastIndexOf("/"));
                File dirFile = new File(dirPath);
                if (!dirFile.exists()) {
                        dirFile.mkdirs();
                }
	}

	/**
	 *儲存xml檔案
	 * 
	 * @param entity
	 *            xml檔案對應的物件
	 */
	public void saveConfig(T entity) {
		System.out.println("===儲存配置====");
		XStream stream = new XStream(new DomDriver("utf-8"));// xml檔案使用utf-8格式
		stream.autodetectAnnotations(true);// 設定自動匹配annotation.
		try {
			FileOutputStream out = new FileOutputStream(file);
			stream.toXML(entity, out);// 將實體類轉為xml並輸出到檔案.
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 從配置檔案中讀取配置,並自動轉換為對應的物件.
	 * 
	 * @return T
	 */
	public T getConfig() {
		XStream stream = new XStream(new DomDriver("utf-8"));// xml檔案使用utf-8格式
		stream.autodetectAnnotations(true);// 設定自動匹配annotation.
		FileInputStream input;
		T entity;
		try {
			input = new FileInputStream(file);
			String alias = "";
			if (clazz.isAnnotationPresent(XStreamAlias.class)) {
				alias = clazz.getAnnotation(XStreamAlias.class).value();
			}
			stream.alias(alias, clazz);// 由於xstream-1.3.1.jar中有個bug,從xml檔案中讀出的內容autodetectAnnotations(true)不生效,必須用alias後才正常.
			entity = (T) stream.fromXML(input); // 從配置檔案中讀取配置,並自動轉換為對應的物件
		} catch (FileNotFoundException e1) {
			entity = setDefaultConfig();// 檔案不存在時建立一個預設的配置檔案.
			if (entity != null) {
				saveConfig(entity);
			}
		}
		return entity;
	}
}
public class StuConfig4 extends XmlUtil4<Student> {
	private static final String file = "student4.xml";

	public StuConfig4() {
		super(Student.class, file);
	}

	@Override
	Student setDefaultConfig() {
		Student stu = new Student();
		stu.setName("李坤");
		stu.setClasses("五期提高班");
		return stu;
	}

	public static void main(String[] args) {
		StuConfig4 config = new StuConfig4();
		System.out.println(config.getConfig());
	}

}

    總結一下吧,重構的過程是體現一個程式設計師基本素質,知識上學到了xstream的基本使用,考慮問題時用到了:物件化的思想和思考方式,程式設計時健壯性的考慮,開閉原則,抽象類,繼承和實現,泛型(使用泛型可以把執行時錯誤提前到編譯期得到檢查和處理),類也要求易用性等.

本例子的程式碼結構: