个人理解 WebWork 与 Struts2 都是将xml配置文件作为 Controler 跳转的基本依据,WebWork 跳转 Action 前 xml 文件的读取依赖 xwork-1.0.jar,底层由 xwork实现,这部门代码读起来不是很轻松,在此做下记录供后续查阅和项目借鉴。今天的代码分析对应 下图 WebWork 框架流转图中红框框的地方。
WebWork xml配置文件读取的入口、后续的所有处理都是 Action 调用类 DefaultActionProxy 这句代码:
this.config = ConfigurationManager.getConfiguration().getRuntimeConfiguration().getActionConfig(namespace, actionName);
1. 框架中类图关系
2. ConfigurationManager
- ConfigurationManager 做为整个 xwork 获取配置信息的管理者,掌控 Configuration 与 XmlConfigurationProvider 两大类的实例化时机;
- ConfigurationManager的 getConfiguration()方法实现如下:
1 public static synchronized Configuration getConfiguration() { 2 if (configurationInstance == null) { 3 configurationInstance = new DefaultConfiguration(); 4 configurationInstance.reload(); 5 } else { 6 conditionalReload(); 7 } 8 return configurationInstance; 9 }
- 注意框架中的这个方法前面的修饰符是 synchronized 并发线程不能同时访问该函数;
- 可以通过 setConfiguration方法设置一个 configurationInstance,如果没有设置,返回XWork的默认实现类, DefaultConfiguration;
3. DefaultConfiguration
- DefaultConfiguration 实现接口 Configuration ,其中含有内部类 RuntimeConfigurationImpl 实现了 RuntimeConfiguration 接口;
- 入口中DefaultActionProxy的构造函数中调用的:ConfigurationManager.getConfiguration().getRuntimeConfiguration().getActionConfig 默认的实现为 RuntimeConfigurationImpl ;
- ConfigurationManager 在实例化 DefaultConfiguration 对象后,紧接着调用了该对象的 reload();
1 public synchronized void reload() throws ConfigurationException { 2 this.packageContexts.clear(); 3 for (Iterator iterator = ConfigurationManager.getConfigurationProviders().iterator(); iterator.hasNext();) { 4 ConfigurationProvider provider = (ConfigurationProvider) iterator.next(); 5 provider.init(this); 6 } 7 rebuildRuntimeConfiguration(); 8 }
- reload() 通过遍历 ConfigurationManager 中的configurationProviders链表,来逐个初始化 XWork 的配置信息。默认只有一个 ConfigurationProvider,也就是 XmlConfigurationProvider,同样只会读取一个XWork的配置信息,就是xwork.xml;
- 这里要注意一下,大项目都是许多工程师并发编写,一个xwork.xml 配置文件显示是不能满足各个模块一起开发的要求,这里需要编写一个类去继承 XmlConfigurationProvider,这个类需要将项目中分散在各个模块下的 xwork.xml 配置文件整合起来,写入到加载到 configurationProviders 链表当中(实现方式多种多样,看项目具体情况);
ConfigurationManager的 getConfigurationProviders方法实现如下:
public static List getConfigurationProviders() { synchronized (configurationProviders) { if (configurationProviders.size() == 0) { configurationProviders.add(new XmlConfigurationProvider()); } return configurationProviders; } }
- 在没继承 XmlConfigurationProvider 情况下,reload 函数里的 For 循环只会执行一次,调用XmlConfigurationProvider的 init方法;
- 调用该方法时, DefaultConfiguration把自身作为参数传了进去。 之后 XmlConfigurationProvider 的 init 方法会通过自身的loadConfigurationFile方法回调DefaultConfiguration的addPackageConfig方法将解析出的 Action 配置信息存放回 DefaultConfiguration 的Map 类型成员变量 packageContexts 中,供其内部类 RuntimeConfigurationImpl 的方法getActionConfig 返回某一个 Action 配置信息时查找使用;
- getActionConfig 返回的 ActionConfig 是 XWork 的一个类,包含了某一个 Action 的所有配置信息以及执行后的所有可能结果等;
- XmlConfigurationProvider 的 init 方法会通过自身的 loadConfigurationFile 方法首先读取xwork.xml配置信息,然后通过发现include标签找到其他配置文件去读取;
- 该机制保证了用户可以将一个庞大的XWork配置文件拆分为多个,在 xwork.xml通过 include标签引用进来,但要注意同名的 Action的覆盖问题[和上面说的注意区别]。
- loadConfigurationFile 通过递归解析完所有的配置文件,并将他们放入DefaultConfiguration的Map类型成员变量packageContexts中。
具体代码如下:
1 private void loadConfigurationFile(String fileName, DocumentBuilder db) { 2 if (!includedFileNames.contains(fileName)) { 3 if (LOG.isDebugEnabled()) { 4 LOG.debug("Loading xwork configuration from: " + fileName); 5 } 6 includedFileNames.add(fileName); 7 Document doc = null; 8 InputStream is = null; 9 try { 10 is = getInputStream(fileName); 11 if (is == null) { 12 throw new Exception("Could not open file " + fileName); 13 } 14 doc = db.parse(is); 15 } catch (Exception e) { 16 final String s = "Caught exception while loading file " + fileName; 17 LOG.error(s, e); 18 throw new ConfigurationException(s, e); 19 } finally { 20 if (is != null) { 21 try { 22 is.close(); 23 } catch (IOException e) { 24 LOG.error("Unable to close input stream", e); 25 } 26 } 27 } 28 Element rootElement = doc.getDocumentElement(); 29 NodeList children = rootElement.getChildNodes(); 30 int childSize = children.getLength(); 31 for (int i = 0; i < childSize; i++) { 32 Node childNode = children.item(i); 33 if (childNode instanceof Element) { 34 Element child = (Element) childNode; 35 final String nodeName = child.getNodeName(); 36 if (nodeName.equals("package")) { 37 addPackage(child); 38 } else if (nodeName.equals("include")) { 39 String includeFileName = child.getAttribute("file"); 40 loadConfigurationFile(includeFileName, db); 41 } 42 } 43 } 44 if (LOG.isDebugEnabled()) { 45 LOG.debug("Loaded xwork configuration from: " + fileName); 46 } 47 } 48 }
4. 这部分代码闪光的地方
- XmlConfigurationProvider 和 DefaultConfiguration 分别实现 ConfigurationProvider与 Configuration 接口,可以仔细看下接口中定义的抽象方法,十分合理,为程序的可扩展性提供了基础,做到了“对修改封闭,对扩展开放”。
- ConfigurationManager 采用了工厂模式来作为一个统一的入口 ,掌握了 DefaultConfiguration 与 ConfigurationProvider 的实例化时机,实例化采用单例模式,让两类的实例化对象有且只有一个,即解耦两类的同时保证了程序的高内聚,十分考究。
- DefaultConfiguration 的内部类的使用让程序的设计眼前一类,在看到它之后我一直在思考,为什么不将 内部类 RuntimeConfigurationImpl 单独作为一个类交由 ConfigurationManager 统一管理?
- RuntimeConfigurationImpl 如果交由ConfigurationManager 统一管理非常的不合理,RuntimeConfigurationImpl 中的唯一属性 namespaceActionConfigs 由外部类初始化填入, ConfigurationManager 到 赋值 namespaceActionConfigs 属性的过程:
ConfigurationManager.getConfiguration() --- Configuration.reload() --- Configuration.rebuildRuntimeConfiguration() --
Configuration.buildRuntimeConfiguration() -- RuntimeConfigurationImpl(namespaceActionConfigs)
- 内部类 RuntimeConfigurationImpl 可以随意使用外部类的成员变量(包括私有)而不用生成外部类的对象,隐藏你不想让别人知道的操作,使整个程序编码更加简洁。
- 这几个类中 方法前的修饰符,用的十分合理,包括private,protected。对多线程访问的合理控制。