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标签信息。

  ③封装配置信息对象。