在Struts2中提供了两种类型的框架级配置文件,XML格式和.properties,其中系统提供的
dafault.properties文件是struts2默认的框架级参数配置,struts.properties是struts2默认的应用级的参数配
置,包含所有的应用级的参数对框架级的参数的覆盖,XML中配置properties文件的格式如下:
<constant name="struts.devMode" value="false" />
本文将从内部的实现原理,来告诉读者,Struts2是如何实现将struts2的XML文件的constant节点中中定义
的参数值读取出来,并覆盖了struts.properties定义的框架级的参数。
我们首先要从Dispatcher的init()方法来讲:下面是init方法的定义,
1 /** 2 * Load configurations, including both XML and zero-configuration strategies, 3 * and update optional settings, including whether to reload configurations and resource files. 4 */ 5 public void init() { 6 7 if (configurationManager == null) { 8 configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); 9 } 10 11 try { 12 init_DefaultProperties(); // [1] 13 init_TraditionalXmlConfigurations(); // [2] 14 init_LegacyStrutsProperties(); // [3] 15 init_CustomConfigurationProviders(); // [5] 16 init_FilterInitParameters() ; // [6] 17 init_AliasStandardObjects() ; // [7] 18 19 Container container = init_PreloadConfiguration(); 20 container.inject(this); 21 init_CheckConfigurationReloading(container); 22 init_CheckWebLogicWorkaround(container); 23 24 if (!dispatcherListeners.isEmpty()) { 25 for (DispatcherListener l : dispatcherListeners) { 26 l.dispatcherInitialized(this); 27 } 28 } 29 } catch (Exception ex) { 30 if (LOG.isErrorEnabled()) 31 LOG.error("Dispatcher initialization failed", ex); 32 throw new StrutsException(ex); 33 } 34 }
对上述几个步骤初始化的文件进行介绍:
[1]:初始化stuts2默认的properties文件加载器(default.properties)
[2]:初始化XML文件文件加载器
[3]:初始化的properties文件加载器(struts.properties)
[4]:初始化用户自定义配置加载器
[5]:初始化由web.xml传入的运行参数
[6]:初始化默认容器内置对象加载器
下面我们来查看读取XML配置文件的加载器(XmlConfigurationProvider)的register方法:我只摘取了读
取constant节点变量值的一段
else if ("constant".equals(nodeName)) { String name = child.getAttribute("name"); String value = child.getAttribute("value"); props.setProperty(name, value, childNode); }
最终将值读取到props中,register方法 register(ContainerBuilder containerBuilder,
LocatableProperties props),中props变量,是专门存储properties变量的,其中props.setProperty(name,
value, childNode);将会覆盖,之前定义的框架级别运行参数,下面介绍下读取default.properties和
struts.properties参数用到的两个Provider,以及它们读取参数的不同之处。default.properties:
DefaultPropertiesProvider类,它的register方法定义,
1 public void register(ContainerBuilder builder, LocatableProperties props) 2 throws ConfigurationException { 3 4 Settings defaultSettings = null; 5 try { 6 defaultSettings = new PropertiesSettings("org/apache/struts2/default"); 7 } catch (Exception e) { 8 throw new ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e); 9 } 10 11 loadSettings(props, defaultSettings); 12 }
其中defaultSettings = new PropertiesSettings("org/apache/struts2/default");是将配置文件中的值解析
出来,也就是properties文件的解析器(PropertiesReader),下面是解析器的构造方法;
1 /** 2 * Creates a new properties config given the name of a properties file. The name is expected to NOT have 3 * the ".properties" file extension. So when <tt>new PropertiesSettings("foo")</tt> is called 4 * this class will look in the classpath for the <tt>foo.properties</tt> file. 5 * 6 * @param name the name of the properties file, excluding the ".properties" extension. 7 */ 8 public PropertiesSettings(String name) { 9 10 URL settingsUrl = ClassLoaderUtils.getResource(name + ".properties", getClass()); 11 12 if (settingsUrl == null) { 13 LOG.debug(name + ".properties missing"); 14 settings = new LocatableProperties(); 15 return; 16 } 17 18 settings = new LocatableProperties(new LocationImpl(null, settingsUrl.toString())); 19 20 // Load settings 21 InputStream in = null; 22 try { 23 in = settingsUrl.openStream(); 24 settings.load(in); 25 } catch (IOException e) { 26 throw new StrutsException("Could not load " + name + ".properties:" + e, e); 27 } finally { 28 if(in != null) { 29 try { 30 in.close(); 31 } catch(IOException io) { 32 LOG.warn("Unable to close input stream", io); 33 } 34 } 35 } 36 }
其中 settings.load(in);是读取真正的properties文件;
1 @Override 2 public void load(InputStream in) throws IOException { 3 Reader reader = new InputStreamReader(in); 4 PropertiesReader pr = new PropertiesReader(reader); 5 while (pr.nextProperty()) { 6 String name = pr.getPropertyName();--读取参数名称 7 String val = pr.getPropertyValue();--读取参数值 8 int line = pr.getLineNumber(); --读取参数的行 9 String desc = convertCommentsToString(pr.getCommentLines()); 10 --读取参数的注释信息 11 Location loc = new LocationImpl(desc, location.getURI(), line, 0); 12 setProperty(name, val, loc); 13 } 14 }
下面来观察DefaultPropertiesProvider类中的loadSettings()方法,该方法是将从配置文件中读取出来的键值映
射对保存到LocatableProperties这个类中,这个类是贯穿Struts2初始化主线的。下面来具体的分析该方法,该方
法在register方法中调用:
1 public void register(ContainerBuilder builder, LocatableProperties props) 2 throws ConfigurationException { 3 4 Settings defaultSettings = null; 5 try { 6 defaultSettings = new PropertiesSettings("org/apache/struts2/default"); 7 } catch (Exception e) { 8 throw new ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e); 9 } 10 11 loadSettings(props, defaultSettings);--将从default.properties文件中读取出来的值放到prop中 12 }
loadSettings(props, defaultSettings)方法,DefaultPropertiesProvider类继承自
LegacyPropertiesConfigurationProvider类,它调用了父类中的loadSettings方法:
1 /** 2 * @param props 3 * @param settings 4 */ 5 protected void loadSettings(LocatableProperties props, final Settings settings) { 6 // We are calling the impl methods to get around the single instance of Settings that is expected 7 for (Iterator i = settings.listImpl(); i.hasNext(); ) { 8 String name = (String) i.next(); 9 props.setProperty(name, settings.getImpl(name), settings.getLocationImpl(name)); 10 } 11 }
上面的方法,将从配置文件读取到properties文件中的值全部的放到LocatableProperties类中。其中
在PropertiesSettings类中已经封装了LocatableProperties类,现在只不过是将PropertiesSetting变量中的值
拿出来放到loadSettings的那个全局的LocatableProperties类(prop)参数。相当与转移了下位置。下面是
PropertiesSettings构造方法,他包含了LocatableProperties类的一个变量setting
1 /** 2 * Creates a new properties config given the name of a properties file. The name is expected to NOT have 3 * the ".properties" file extension. So when <tt>new PropertiesSettings("foo")</tt> is called 4 * this class will look in the classpath for the <tt>foo.properties</tt> file. 5 * 6 * @param name the name of the properties file, excluding the ".properties" extension. 7 */ 8 public PropertiesSettings(String name) { 9 10 URL settingsUrl = ClassLoaderUtils.getResource(name + ".properties", getClass()); 11 12 if (settingsUrl == null) { 13 LOG.debug(name + ".properties missing"); 14 settings = new LocatableProperties(); 15 return; 16 } 17 --实例化LocatableProperties类 18 settings = new LocatableProperties(new LocationImpl(null, settingsUrl.toString())); 19 20 // Load settings 21 InputStream in = null; 22 try { 23 in = settingsUrl.openStream(); 24 settings.load(in);--读取配置文件中的参数值和名称 25 } catch (IOException e) { 26 throw new StrutsException("Could not load " + name + ".properties:" + e, e); 27 } finally { 28 if(in != null) { 29 try { 30 in.close(); 31 } catch(IOException io) { 32 LOG.warn("Unable to close input stream", io); 33 } 34 } 35 } 36 }
settings.load(in);--读取配置文件中的参数值和名称
1 @Override 2 public void load(InputStream in) throws IOException { 3 Reader reader = new InputStreamReader(in); 4 PropertiesReader pr = new PropertiesReader(reader);--properties文件加载器 5 while (pr.nextProperty()) {--pr.nextProperty文件 6 String name = pr.getPropertyName();--名字 7 String val = pr.getPropertyValue();--值 8 int line = pr.getLineNumber();--行号 9 String desc = convertCommentsToString(pr.getCommentLines());--描述信息 10 --具体位置信息 11 Location loc = new LocationImpl(desc, location.getURI(), line, 0); 12 setProperty(name, val, loc); 13 } 14 }
下面观察下方法:setProperty()方法,它将key和他的位置信息存到propLocations中。
1 public Object setProperty(String key, String value, Object locationObj) { 2 Object obj = super.setProperty(key, value); 3 if (location != null) { 4 Location loc = LocationUtils.getLocation(locationObj); 5 propLocations.put(key, loc); 6 } 7 return obj; 8 }
这时候,我们可以返回去看loadSetting方法:
1 /** 2 * @param props 3 * @param settings 4 */ 5 protected void loadSettings(LocatableProperties props, final Settings settings) { 6 // We are calling the impl methods to get around the single instance of Settings that is expected 7 for (Iterator i = settings.listImpl(); i.hasNext(); ) { 8 String name = (String) i.next(); 9 props.setProperty(name, settings.getImpl(name), settings.getLocationImpl(name)); 10 } 11 }
它用到了上面的三个方法;可以在DefaultSettings类中查看上述三个方法是如何定义的。
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
下面介绍下在DefaultPropertiesProvider和LegacyPropertiesConfigurationProvider读取配置文件的
不同方式,在LegacyPropertiesConfigurationProvider使用了代理模式;下面来进行讲解:
DefaultPropertiesProvider的register方法:
1 public void register(ContainerBuilder builder, LocatableProperties props) 2 throws ConfigurationException { 3 4 Settings defaultSettings = null; 5 try { 6 defaultSettings = new PropertiesSettings("org/apache/struts2/default");
--直接新建了PropertiesSettings类,该构造 7 } catch (Exception e) { 8 throw new ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e); 9 } 10 11 loadSettings(props, defaultSettings); 12 }
它直接defaultSettings = new PropertiesSettings("org/apache/struts2/default");从指定的位置读取数据。
而LegacyPropertiesConfigurationProvider的register的方法:
1 public void register(ContainerBuilder builder, LocatableProperties props) 2 throws ConfigurationException { 3 4 final Settings settings = Settings.getInstance();通过Setting.getInstance()方法加载配置文件 5 6 loadSettings(props, settings); 7 8 // Set default locale by lazily resolving the locale property as needed into a Locale object 9 builder.factory(Locale.class, new Factory() { 10 private Locale locale; 11 12 public synchronized Object create(Context context) throws Exception { 13 if (locale == null) { 14 String loc = context.getContainer().getInstance(String.class, StrutsConstants .STRUTS_LOCALE); 15 if (loc != null) { 16 StringTokenizer localeTokens = new StringTokenizer(loc, "_"); 17 String lang = null; 18 String country = null; 19 20 if (localeTokens.hasMoreTokens()) { 21 lang = localeTokens.nextToken(); 22 } 23 24 if (localeTokens.hasMoreTokens()) { 25 country = localeTokens.nextToken(); 26 } 27 locale = new Locale(lang, country); 28 } else { 29 LOG.info("No locale define, substituting the default VM locale"); 30 locale = Locale.getDefault(); 31 } 32 } 33 return locale; 34 } 35 }); 36 }
下面来观察Settings.getInstance()方法,
1 /** 2 * Provides the Settings object. 3 * <p> 4 * This method will substitute the default instance if another instance is not registered. 5 * 当默认的实例没有被注册,则使用该方法代替,获取默认的实例对象 6 * @return the Settings object. 7 */ 8 public static Settings getInstance() { 9 return (settingsImpl == null) ? getDefaultInstance() : settingsImpl; 10 }
下面我们来观察getDefaultInstance()方法
1 /** 2 * Creates a default Settings object. 3 * <p> 4 * A default implementation may be specified by the <code>struts.configuration</code> setting; 5 * otherwise, this method instantiates {@link DefaultSettings} as the default implementation. 6 * 7 * @return A default Settings object. 8 */ 9 private static Settings getDefaultInstance() { 10 if (defaultImpl == null) { 11 // Create bootstrap implementation 12 defaultImpl = new DefaultSettings();--新建一个默认的Setting,但是,上面的是propertiesSetting 13 下面我们观察DefaultSettings是如何初始化的 14 // Create default implementation 15 try { 16 String className = get(StrutsConstants.STRUTS_CONFIGURATION); 17 18 if (!className.equals(defaultImpl.getClass().getName())) { 19 try { 20 // singleton instances shouldn't be built accessing request or session-specific context data 21 defaultImpl = (Settings) ObjectFactory.getObjectFactory().buildBean(Thread.currentThread().getContextClassLoader().loadClass(className), null); 22 } catch (Exception e) { 23 LOG.error("Settings: Could not instantiate the struts.configuration object, substituting the default implementation.", e); 24 } 25 } 26 } catch (IllegalArgumentException ex) { 27 // ignore 28 } 29 } 30 31 return defaultImpl; 32 }
DefaultSetting的构造方法:
1 /** 2 * Constructs an instance by loading the standard property files, 3 * any custom property files (<code>struts.custom.properties</code>), 4 * and any custom message resources (). 5 * <p> 6 * Since this constructor combines Settings from multiple resources, 7 * it utilizes a {@link DelegatingSettings} instance, 8 * and all API calls are handled by that instance. 9 */ 10 public DefaultSettings() { 11 12 ArrayList<Settings> list = new ArrayList<Settings>(); 13 14 // stuts.properties, default.properties 15 try {这里用了一个list 16 list.add(new PropertiesSettings("struts"));--开始读取用户配置的struts.properties 17 } catch (Exception e) { 18 log.warn("DefaultSettings: Could not find or error in struts.properties", e); 19 } 20 21 Settings[] settings = new Settings[list.size()]; 22 delegate = new DelegatingSettings(list.toArray(settings));--实际上是PropertiesSettings 23 24 // struts.custom.properties 25 try { 26 StringTokenizer customProperties = new StringTokenizer(delegate.getImpl(StrutsConstants.STRUTS_CUSTOM_PROPERTIES), ","); 27 28 while (customProperties.hasMoreTokens()) { 29 String name = customProperties.nextToken(); 30 31 try { 32 list.add(new PropertiesSettings(name));--如果配置了struts.custom.properties 33 } catch (Exception e) { 34 log.error("DefaultSettings: Could not find " + name + ".properties. Skipping."); 35 } 36 } 37 38 settings = new Settings[list.size()]; 39 delegate = new DelegatingSettings(list.toArray(settings));--代理 40 } catch (IllegalArgumentException e) { 41 // Assume it's OK, since IllegalArgumentException is thrown 42 // when Settings is unable to find a certain setting, 43 // like the struts.custom.properties, which is commented out 44 } 45 46 }
下面来观察代理的两个方法:
1 // See superclass for Javadoc 2 public void setImpl(String name, String value) throws IllegalArgumentException, UnsupportedOperationException { 3 delegate.setImpl(name, value); 4 } 5 6 // See superclass for Javadoc 7 public String getImpl(String aName) throws IllegalArgumentException { 8 return delegate.getImpl(aName); 9 }
实际上,上述两个方法,最终会调用:
1 // See superclass for Javadoc 2 public void setImpl(String name, String value) throws IllegalArgumentException, UnsupportedOperationException { 3 IllegalArgumentException e = null; 4 5 for (Settings delegate : delegates) { 6 try { 7 delegate.getImpl(name); // Throws exception if not found 8 delegate.setImpl(name, value); // Found it 9 return; // Done 10 } catch (IllegalArgumentException ex) { 11 e = ex; 12 13 // Try next delegate:执行下一个代理的类 14 } 15 } 16 17 throw e;--如果最终未找到,则抛出异常,否则在循环中就return了。 18 } 19 20 // See superclass for Javadoc 21 public String getImpl(String name) throws IllegalArgumentException { 22 23 IllegalArgumentException e = null; 24 25 for (Settings delegate : delegates) { 26 try { 27 return delegate.getImpl(name); // Throws exception if not found 28 } catch (IllegalArgumentException ex) { 29 e = ex; 30 31 // Try next delegate:执行下一个代理的类 32 } 33 } 34 35 throw e;--如果,最终未找到,则抛出异常,否则在循环中就return 36 }
对应在register方法中引用的loadSettings(props, settings);方法;
1 /** 2 * @param props 3 * @param settings 4 */ 5 protected void loadSettings(LocatableProperties props, final Settings settings) { 6 // We are calling the impl methods to get around the single instance of Settings that is expected 7 for (Iterator i = settings.listImpl(); i.hasNext(); ) { 8 String name = (String) i.next(); 9 props.setProperty(name, settings.getImpl(name), settings.getLocationImpl(name)); 10 } 11 }
这时候,settings.listImpl()方法,实际上最终调用的是DelegatingSettings的listImpl()方法,这个方法会把
代理类的list中读取的参数值的放到一个list中,然后统一返回settingList.iterator();
1 // See superclass for Javadoc 2 public Iterator listImpl() { 3 boolean workedAtAll = false; 4 5 Set<Object> settingList = new HashSet<Object>(); 6 UnsupportedOperationException e = null; 7 8 for (Settings delegate : delegates) { 9 try { 10 Iterator list = delegate.listImpl(); 11 12 while (list.hasNext()) { 13 settingList.add(list.next());--循环迭代,加入到一个新的list中 14 } 15 16 workedAtAll = true; 17 } catch (UnsupportedOperationException ex) { 18 e = ex; 19 20 // Try next delegate--循环迭代下一个代理的类 21 } 22 } 23 24 if (!workedAtAll) { 25 throw (e == null) ? new UnsupportedOperationException() : e; 26 } else { 27 return settingList.iterator(); 28 }
--end