请求发送者与接收者解耦——命令模式(五)

6 请求日志

请求日志就是将请求的历史记录保存下来,通常以日志文件(Log File)的形式永久存储在计算机中。很多系统都提供了日志文件,例如Windows日志文件、Oracle日志文件等,日志文件可以记录用户对系统的一些操作(例如对数据的更改)。请求日志文件可以实现很多功能,常用功能如下:

(1) “天有不测风云”,一旦系统发生故障,日志文件可以为系统提供一种恢复机制,在请求日志文件中可以记录用户对系统的每一步操作,从而让系统能够顺利恢复到某一个特定的状态;

(2) 请求日志也可以用于实现批处理,在一个请求日志文件中可以存储一系列命令对象,例如一个命令队列;

(3) 可以将命令队列中的所有命令对象都存储在一个日志文件中,每执行一个命令则从日志文件中删除一个对应的命令对象,防止因为断电或者系统重启等原因造成请求丢失,而且可以避免重新发送全部请求时造成某些命令的重复执行,只需读取请求日志文件,再继续执行文件中剩余的命令即可。

在实现请求日志时,我们可以将命令对象通过序列化写到日志文件中,此时命令类必须实现Java.io.Serializable接口。下面我们通过一个简单实例来说明日志文件的用途以及如何实现请求日志:

Sunny软件公司开发了一个网站配置文件管理工具,可以通过一个可视化界面对网站配置文件进行增删改等操作,该工具使用命令模式进行设计,结构如图6所示:

请求发送者与接收者解耦——命令模式(五) - 图1

图6 网站配置文件管理工具结构图

现在Sunny软件公司开发人员希望将对配置文件的操作请求记录在日志文件中,如果网站重新部署,只需要执行保存在日志文件中的命令对象即可修改配置文件。

本实例完整代码如下所示:

  1. import java.io.*;
  2. import java.util.*;
  3. //抽象命令类,由于需要将命令对象写入文件,因此它实现了Serializable接口
  4. abstract class Command implements Serializable {
  5. protected String name; //命令名称
  6. protected String args; //命令参数
  7. protected ConfigOperator configOperator; //维持对接收者对象的引用
  8. public Command(String name) {
  9. this.name = name;
  10. }
  11. public String getName() {
  12. return this.name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public void setConfigOperator(ConfigOperator configOperator) {
  18. this.configOperator = configOperator;
  19. }
  20. //声明两个抽象的执行方法execute()
  21. public abstract void execute(String args);
  22. public abstract void execute();
  23. }
  24. //增加命令类:具体命令
  25. class InsertCommand extends Command {
  26. public InsertCommand(String name) {
  27. super(name);
  28. }
  29. public void execute(String args) {
  30. this.args = args;
  31. configOperator.insert(args);
  32. }
  33. public void execute() {
  34. configOperator.insert(this.args);
  35. }
  36. }
  37. //修改命令类:具体命令
  38. class ModifyCommand extends Command {
  39. public ModifyCommand(String name) {
  40. super(name);
  41. }
  42. public void execute(String args) {
  43. this.args = args;
  44. configOperator.modify(args);
  45. }
  46. public void execute() {
  47. configOperator.modify(this.args);
  48. }
  49. }
  50. //省略了删除命令类DeleteCommand
  51. //配置文件操作类:请求接收者。由于ConfigOperator类的对象是Command的成员对象,它也将随Command对象一起写入文件,因此ConfigOperator也需要实现Serializable接口
  52. class ConfigOperator implements Serializable {
  53. public void insert(String args) {
  54. System.out.println("增加新节点:" + args);
  55. }
  56. public void modify(String args) {
  57. System.out.println("修改节点:" + args);
  58. }
  59. public void delete(String args) {
  60. System.out.println("删除节点:" + args);
  61. }
  62. }
  63. //配置文件设置窗口类:请求发送者
  64. class ConfigSettingWindow {
  65. //定义一个集合来存储每一次操作时的命令对象
  66. private ArrayList<Command> commands = new ArrayList<Command>();
  67. private Command command;
  68. //注入具体命令对象
  69. public void setCommand(Command command) {
  70. this.command = command;
  71. }
  72. //执行配置文件修改命令,同时将命令对象添加到命令集合中
  73. public void call(String args) {
  74. command.execute(args);
  75. commands.add(command);
  76. }
  77. //记录请求日志,生成日志文件,将命令集合写入日志文件
  78. public void save() {
  79. FileUtil.writeCommands(commands);
  80. }
  81. //从日志文件中提取命令集合,并循环调用每一个命令对象的execute()方法来实现配置文件的重新设置
  82. public void recover() {
  83. ArrayList list;
  84. list = FileUtil.readCommands();
  85. for (Object obj : list) {
  86. ((Command)obj).execute();
  87. }
  88. }
  89. }
  90. //工具类:文件操作类
  91. class FileUtil {
  92. //将命令集合写入日志文件
  93. public static void writeCommands(ArrayList commands) {
  94. try {
  95. FileOutputStream file = new FileOutputStream("config.log");
  96. //创建对象输出流用于将对象写入到文件中
  97. ObjectOutputStream objout = new ObjectOutputStream(new BufferedOutputStream(file));
  98. //将对象写入文件
  99. objout.writeObject(commands);
  100. objout.close();
  101. }
  102. catch(Exception e) {
  103. System.out.println("命令保存失败!");
  104. e.printStackTrace();
  105. }
  106. }
  107. //从日志文件中提取命令集合
  108. public static ArrayList readCommands() {
  109. try {
  110. FileInputStream file = new FileInputStream("config.log");
  111. //创建对象输入流用于从文件中读取对象
  112. ObjectInputStream objin = new ObjectInputStream(new BufferedInputStream(file));
  113. //将文件中的对象读出并转换为ArrayList类型
  114. ArrayList commands = (ArrayList)objin.readObject();
  115. objin.close();
  116. return commands;
  117. }
  118. catch(Exception e) {
  119. System.out.println("命令读取失败!");
  120. e.printStackTrace();
  121. return null;
  122. }
  123. }
  124. }

编写如下客户端测试代码:

  1. class Client {
  2. public static void main(String args[]) {
  3. ConfigSettingWindow csw = new ConfigSettingWindow(); //定义请求发送者
  4. Command command; //定义命令对象
  5. ConfigOperator co = new ConfigOperator(); //定义请求接收者
  6. //四次对配置文件的更改
  7. command = new InsertCommand("增加");
  8. command.setConfigOperator(co);
  9. csw.setCommand(command);
  10. csw.call("网站首页");
  11. command = new InsertCommand("增加");
  12. command.setConfigOperator(co);
  13. csw.setCommand(command);
  14. csw.call("端口号");
  15. command = new ModifyCommand("修改");
  16. command.setConfigOperator(co);
  17. csw.setCommand(command);
  18. csw.call("网站首页");
  19. command = new ModifyCommand("修改");
  20. command.setConfigOperator(co);
  21. csw.setCommand(command);
  22. csw.call("端口号");
  23. System.out.println("----------------------------");
  24. System.out.println("保存配置");
  25. csw.save();
  26. System.out.println("----------------------------");
  27. System.out.println("恢复配置");
  28. System.out.println("----------------------------");
  29. csw.recover();
  30. }
  31. }

编译并运行程序,输出结果如下:

  1. 增加新节点:网站首页
  2. 增加新节点:端口号
  3. 修改节点:网站首页
  4. 修改节点:端口号
  5. ----------------------------
  6. 保存配置
  7. ----------------------------
  8. 恢复配置
  9. ----------------------------
  10. 增加新节点:网站首页
  11. 增加新节点:端口号
  12. 修改节点:网站首页
  13. 修改节点:端口号