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文件的所有信息就可以了。
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标签信息。
③封装配置信息对象。
本文出自 “总有贱人想害朕。” 博客,请务必保留此出处http://yoyanda.blog.51cto.com/9675421/1709930
原文:http://yoyanda.blog.51cto.com/9675421/1709930