struct2源码解读(4)之配置文件具体解析过程
从上篇博文我们探讨过了struct2解析配置文件的简单流程,对于具体的解析过程还没做具体深入的解析,下面就我们探讨下struct2是如何解析配置文件的。
public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException { //清空解析对象,解析package标签会把属性封装到PackageConfig,然后把PackageConfig放到packageContexts这个map中 packageContexts.clear(); loadedFileNames.clear(); List<PackageProvider> packageProviders = new ArrayList<PackageProvider>(); //保存properties属性的对象 ContainerProperties props = new ContainerProperties(); //保存其他属性的对象 ContainerBuilder builder = new ContainerBuilder(); //循环遍历解析对象解析 for (final ContainerProvider containerProvider : providers) { containerProvider.init(this); containerProvider.register(builder, props); } props.setConstants(builder); builder.factory(Configuration.class, new Factory<Configuration>() { public Configuration create(Context context) throws Exception { return DefaultConfiguration.this; } }); ActionContext oldContext = ActionContext.getContext(); try { // Set the bootstrap container for the purposes of factory creation Container bootstrap = createBootstrapContainer(); setContext(bootstrap); //实例化一个container对象,这里用到两个工厂模式 container = builder.create(false); setContext(container); objectFactory = container.getInstance(ObjectFactory.class); // 解析package 标签 for (final ContainerProvider containerProvider : providers) { if (containerProvider instanceof PackageProvider) { container.inject(containerProvider); ((PackageProvider)containerProvider).loadPackages(); packageProviders.add((PackageProvider)containerProvider); } } // 解析插件的package标签 Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class); if (packageProviderNames != null) { for (String name : packageProviderNames) { PackageProvider provider = container.getInstance(PackageProvider.class, name); provider.init(this); provider.loadPackages(); packageProviders.add(provider); } } //整理配置信息 rebuildRuntimeConfiguration(); } finally { if (oldContext == null) { ActionContext.setContext(null); } } return packageProviders; }
从上面我们大致了解了解析配置文件到底做了什么事情,下面就让我们详细探讨。
一、清空存放配置信息的对象
packageContexts是一个map集合
protected Map<String, PackageConfig> packageContexts = new LinkedHashMap<String, PackageConfig>();
这个map存放的是packageConfig对象,而这个packageConfig对象封装了package标签的属性,一个xml文件可能会有几个package标签。我们来看下这个packageConfig对象
private String name; private String namespace = ""; private boolean isAbstract = false;
再来看看struct.xml配置文件中的package标签
<package name="forword" namespace="/" extends="struts-default">
有没有发现,这其实就是把package标签的属性,封装到了一个对象中。解析其他标签也是这个原理,只是封装的对象不一样而儿。
这里调用了map.clear()方法,先清空这个map,确保每次存放的都是解析的对象。
二、实例化存放配置信息的对象
package标签的属性封装到了packageConfig对象,而其他的属性封装到了ContainerProperties和ContainerBuilder对象中,如property中这些有key-value值的属性,封装到了ContainerProperties对象,bean就封装到了ContainerBuilder对象中。这里用到了工厂的模式。工厂的英文是factory,factory是一个接口
public interface Factory<T> { T create(Context context) throws Exception; }
调用这个接口的create方法就可以返回这个对象实例。把bean属性封装到ContainerBuilder这个对象中,然后掉用create方法,就会返回这个bean实例了,这个具体原理我们下面再分析。
三、循环遍历解析配置文件
for (final ContainerProvider containerProvider : providers) { containerProvider.init(this); containerProvider.register(builder, props); }
上篇博文我们分析到,我们已经实例化解析每个配置文件的对象,并把这些对象添加了一个list集合中,然后通过getContainerProviders()方法,就可以获得这个集合。
private List<ContainerProvider> containerProviders = new CopyOnWriteArrayList<ContainerProvider>(); public List<ContainerProvider> getContainerProviders() { providerLock.lock(); try { if (containerProviders.size() == 0) { //如果为空,默认给出2个 containerProviders.add(new XWorkConfigurationProvider()); containerProviders.add(new XmlConfigurationProvider("xwork.xml", false)); } //返回这个集合 return containerProviders; } finally { providerLock.unlock(); } }
通过循环遍历这个集合里面的每个解析器,就可以解析每个配置文件了。这里具2个例子,分析下原理,其他的大家可以自己去看下。
3.1.解析default.properties文件
3.1.1.原理
default.properties这个文件配置了struct2默认的运行信息。如我们在structs.xml中配置了
代码清单:structs.xml <!-- 配置为开发模式 --> <constant name="struts.devMode" value="true" />
把struts.devMode设为true,在开发的时候,我们改动structs.xml,就不用重启tomcat了。而这个struts.devMode就是在default.properties文件中设置的。
代码清单:default.properties struts.devMode = false ### when set to true, resource bundles will be reloaded on _every_ request. ### this is good during development, but should never be used in production struts.i18n.reload=false
strcut2默认设置是false的,在解析default.properties时会把这个属性以(struts.devMode,false)保存到property对象中,到后面解析structs.xml的constant时,就会判断property这个对象是否有struts.devMode这个key,有的话,就会设置成constant中设置的值。具体看下面分析。
3.1.2.解析过程
我们找到DefaultPropertiesProvider这个解析器
public class DefaultPropertiesProvider extends LegacyPropertiesConfigurationProvider { public void destroy() { } //init()方法没有做任何事情 public void init(Configuration configuration) throws ConfigurationException { } //解析 public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { Settings defaultSettings = null; try { //1.加载并解析属性文件org/apache/struts2/default.properties defaultSettings = new PropertiesSettings("org/apache/struts2/default"); } //异常信息 //2.封装配置信息到ContainerProperties对象 loadSettings(props, defaultSettings); } }
(1)加载属性文件
public PropertiesSettings(String name) { //获取资源地址 URL settingsUrl = ClassLoaderUtils.getResource(name + ".properties", getClass()); if (settingsUrl == null) { LOG.debug(name + ".properties missing"); settings = new LocatableProperties(); return; } // 创建一个propertis对象 settings = new LocatableProperties(new LocationImpl(null, settingsUrl.toString())); // 解析配置文件 InputStream in = null; try { //打开文件流 in = settingsUrl.openStream(); //解析property文件 settings.load(in); } catch (IOException e) { throw new StrutsException("Could not load " + name + ".properties:" + e, e); } finally { if(in != null) { try { in.close(); } catch(IOException io) { LOG.warn("Unable to close input stream", io); } } } }
(2)具体解析过程,把配置信息封装到setting对象
public void load(InputStream in) throws IOException { //property文件解析器 Reader reader = new InputStreamReader(in); //读取property文件 PropertiesReader pr = new PropertiesReader(reader); while (pr.nextProperty()) { //获取配置信息 String name = pr.getPropertyName(); String val = pr.getPropertyValue(); int line = pr.getLineNumber(); String desc = convertCommentsToString(pr.getCommentLines()); Location loc = new LocationImpl(desc, location.getURI(), line, 0); //以key-value形式保存配置信息到property对象中 setProperty(name, val, loc); } }
(3)setting对象封装到ContainerProperties对象
protected void loadSettings(LocatableProperties props, final Settings settings) { //循环遍历解析出来的配置信息 for (Iterator i = settings.listImpl(); i.hasNext(); ) { String name = (String) i.next(); props.setProperty(name, settings.getImpl(name), settings.getLocationImpl(name)); } }
3.2.解析struct*.xml文件
struct2设计是通过StrutsXmlConfigurationProvider这个对象解析struct*.xml文件的,我们找到StrutsXmlConfigurationProvider的register()方法。
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { if (servletContext != null && !containerBuilder.contains(ServletContext.class)) { containerBuilder.factory(ServletContext.class, new Factory<ServletContext>() { public ServletContext create(Context context) throws Exception { return servletContext; } }); } //调用父类的方法解析 super.register(containerBuilder, props); }
我们这里看到,无论是init()还是register(),StrutsXmlConfigurationProvider都是调用父类的方法进行解析,而StrutsXmlConfigurationProvider的父类是XmlConfigurationProvider。
3.2.1.init()方法
public void init(Configuration configuration) { this.configuration = configuration; this.includedFileNames = configuration.getLoadedFileNames(); //获得*.xml文档 loadDocuments(configFileName); }
这个configFileName是在实例化这个解析器时传进的file值,默认是struts-default.xml,struts-plugin.xml,struts.xml中的一个。特别注意,这里循环遍历,每个文件都有一个解析器,这里重点解析struts-default.xml的解析过程。
String[] files = configPaths.split("\\s*[,]\\s*"); //循环遍历struts-default.xml,struts-plugin.xml,struts.xml for (String file : files) { if (file.endsWith(".xml")) { if ("xwork.xml".equals(file)) { configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false)); } else { configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext)); } } else { throw new IllegalArgumentException("Invalid configuration file name"); } }
我们来看下loadDocuments这个方法
private void loadDocuments(String configFileName) { try { loadedFileUrls.clear(); //获得document对象 documents = loadConfigurationFiles(configFileName, null); } //异常处理 }
从这里我们可以看出,xml的解析器的init()方法主要是获取配置文件的document对象,通过文件流读取这个xml文件,然后用xml的解析器解析这个文件流,最终得到一个document对象,这里用到了dom解析,所以这里获得的是dom的document对象。具体的看xml的知识点,因篇幅问题,这里就不作过多的解析了,大家知道这里用dom技术解析xml文件获得一个document对象,这个document对象包含了xml文件的所有信息就可以了。特别注意的是,因为<include>这个标签包含的也是一个配置文件,因此在获得这个Document的时候,其实也解析了配置文件的<include>标签,所以这个Document对象包含了一个xml文件的所有信息。
if ("include".equals(nodeName)) { String includeFileName = child.getAttribute("file"); if (includeFileName.indexOf('*') != -1) { ClassPathFinder wildcardFinder = new ClassPathFinder(); wildcardFinder.setPattern(includeFileName); Vector<String> wildcardMatches = wildcardFinder.findMatches(); for (String match : wildcardMatches) { finalDocs.addAll(loadConfigurationFiles(match, child)); } } else { finalDocs.addAll(loadConfigurationFiles(includeFileName, child)); } }
3.2.2.register()方法
这个方法就是解析上面的document对象,也就是struct*.xml文件,把里面的属性进行封装,我们先来看下struct*.xml文件有什么属性,再来看下struct2是怎么解析这个文件的
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <bean class="com.opensymphony.xwork2.ObjectFactory" name="xwork" /> <!-- 配置为开发模式 --> <constant name="struts.devMode" value="true" /> <!-- 把扩展名配置为action --> <constant name="struts.action.extension" value="action" /> <!-- 把主题配置为simple --> <constant name="struts.ui.theme" value="simple" /> <include file="../PlayWellstruts.xml"></include> <!-- Add packages here --> <package name=""> </package> </struts>
这个xml文件,根节点是<struct2>,我们在开发的时候,主要都是配置<package>里面的东东,比较复杂,所以structs2设计对<contant>阿,<bean>啊,这些相对“固定”的,用register()方法解析,对于<package>这个比较复杂的另外用了一个方法(loadpackage())单独解析,我们先来看下register这个方法。
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { Map<String, Node> loadedBeans = new HashMap<String, Node>(); for (Document doc : documents) { //获得根节点 Element rootElement = doc.getDocumentElement(); NodeList children = rootElement.getChildNodes(); int childSize = children.getLength(); for (int i = 0; i < childSize; i++) { //获得子节点 Node childNode = children.item(i); if (childNode instanceof Element) { Element child = (Element) childNode; //获得子节点名 final String nodeName = child.getNodeName(); //获取bean节点信息 if ("bean".equals(nodeName)) { //得到bean属性信息 String type = child.getAttribute("type"); String name = child.getAttribute("name"); String impl = child.getAttribute("class"); String onlyStatic = child.getAttribute("static"); String scopeStr = child.getAttribute("scope"); boolean optional = "true".equals(child.getAttribute("optional")); Scope scope = Scope.SINGLETON;//默认是singleton if ("default".equals(scopeStr)) { scope = Scope.DEFAULT; } else if ("request".equals(scopeStr)) { scope = Scope.REQUEST; } else if ("session".equals(scopeStr)) { scope = Scope.SESSION; } else if ("singleton".equals(scopeStr)) { scope = Scope.SINGLETON; } else if ("thread".equals(scopeStr)) { scope = Scope.THREAD; } //如果name属性不填,默认为default if (StringUtils.isEmpty(name)) { name = Container.DEFAULT_NAME; } //封装 bean配置信息到containerBuilder对象 try { } } else if ("constant".equals(nodeName)) { //解析<constant>标签,把属性值保存到containerProperty对象中,注意因为default.properties的属性也保存到这个对象,因此这里如果发现key相同,会替换原来的值 String name = child.getAttribute("name"); String value = child.getAttribute("value"); props.setProperty(name, value, childNode); } else if (nodeName.equals("unknown-handler-stack")) { //解析unknown-handler-stack } } } } }
这个封装bean用了factory方法,下篇博文我单独解析下,这里大家先有个印象。
四、解析package
for (final ContainerProvider containerProvider : providers) { if (containerProvider instanceof PackageProvider) { container.inject(containerProvider); //loadPackages()方法解析package ((PackageProvider)containerProvider).loadPackages(); packageProviders.add((PackageProvider)containerProvider); } }
无论是解析配置文件的package,还是解析其他package,这里都用到了loadPackage()方法,对于这个方法我也会新开一个博文详细解析。
五、封装配置对象
protected synchronized RuntimeConfiguration buildRuntimeConfiguration() throws ConfigurationException { //ActionConfig Map<String, Map<String, ActionConfig>> namespaceActionConfigs = new LinkedHashMap<String, Map<String, ActionConfig>>(); Map<String, String> namespaceConfigs = new LinkedHashMap<String, String>(); //packageConfig for (PackageConfig packageConfig : packageContexts.values()) { } //返回RuntimeConfigurationImpl return new RuntimeConfigurationImpl(namespaceActionConfigs, namespaceConfigs); }
通过上面解析,把不通的配置信息封装到了不同的对象中,如package标签的属性封装到了packageConfig,把action标签的属性封装到了actionConfig中等等,这里所说的封装是指,把配置文件中的key-value值设置到对象的property值中,以对象来管理这些属性。在解析配置文件的过程中,这些对象都是相对独立的,为了统一起来,这里把分散的对象都又进一步封装到了RuntimeConfiguration对象中,通过一个对象来管理这些分散的对象,方便以后处理aciton请求时调用。具体过程我也会单开一篇博文解析。
六、总结
通过上面分析,我们大致了解了struct2是如何解析配置文件的:通过不同文件的解析器解析不同的配置文件,如DefaulatPropertyProvider解析properties文件,xmlConfigurateProvide解析xml文件等等。通过调用这些解析器的init()和register()或者是loadpackage()方法把配置文件中的key-value信息封装到不同的对象中,如如package标签的属性封装到了packageConfig;把action标签的属性封装到了actionConfig中;把常量信息封装到containerPorperty对象;把类信息以factory的方法封装到containerBuilder对象中等等,最后再把这些对象进一步封装到RuntimeConfiguration中,方便以后处理action请求时调用,从而完成了初始化。
下三篇博文将对上面未解决的问题进行一一详解:
①封装bean标签信息。
②封装package标签信息。
③封装配置信息对象。