struct2源码解读之解析配置文件

    上篇博文讲到struct2在初始化时,在创建Dispacher之后,会Dispacher.init()中会对配置文件进行解析,下面就让我们一起探讨下struct2是如何解析配置文件的。

public Dispatcher initDispatcher( HostConfig filterConfig ) {
          //创建Dispacher实例
        Dispatcher dispatcher = createDispatcher(filterConfig);
          //解析配置文件,搭建struct2运行环境
        dispatcher.init();
        return dispatcher;
    }

   这个init()方法,struct2到底做了些什么呢?我们一起进到init()方法中看看。

 public void init() {

    	if (configurationManager == null) {
    		configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
    	}

        try {
             //初始化解析各个配置文件的对象
            init_DefaultProperties(); //解析default.properties的对象
            init_TraditionalXmlConfigurations(); // 解析struct*.xml配置文件的对象
            init_LegacyStrutsProperties(); // 
            init_CustomConfigurationProviders(); // 自定义解析对象
            init_FilterInitParameters() ; // web.xml的初始化参数
            init_AliasStandardObjects() ; // 解析bean的对象
             
             //解析配置文件,初始化一个IOC容器
            Container container = init_PreloadConfiguration();
             //把dispacher依赖注入到容器中
            container.inject(this);
            
            //设置配置信息
            init_CheckConfigurationReloading(container);
            init_CheckWebLogicWorkaround(container);
            
             //用于扩展
            if (!dispatcherListeners.isEmpty()) {
                for (DispatcherListener l : dispatcherListeners) {
                    l.dispatcherInitialized(this);
                }
            }
        } 
        //异常信息,省略
    }

     从上面可以看到,dispacher.init()主要工作是是解析配置文件,把每个配置信息封装到不同的对象,然后将这些对象交由Container管理。Container俗称容器(本质是接口),什么是容器?就是装水的杯子,我们可以往杯子里面在装水,也可以往杯子里面取水。struct2通过inject()方法,把对象放进container,然后通过getInstance()取出里面的对象,这里过程很复杂,但结果很简单,怎么实现,struct2帮我们实现了,我们只要通过container这个接口的方法,就可以往这个容器取放对象了。这个和spring的ioc容器ActionContext有点类似,具体原理我们以后再分析,这里主要分析流程。

一:初始化解析各个配置文件的对象

    (1)struct2解析配置文件的接口

    struct2解析不同配置文件由不同的类解析。

     刚开始解析配置文件的方法统一封装到ContainerProvider这个接口的中的

public interface ContainerProvider {  
    public void destroy();
    //处理配置文件的方法
    public void init(Configuration configuration) throws ConfigurationException;
    public boolean needsReload();
    //解析配置文件的方法
    public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException;
    
}

    但后来因为配置文件中有很多的标签属性,如解析contant,解析package的,为了分类管理,又把解析package标签的方法单独封装成一个接口-PackageProvider

public interface PackageProvider {
    //处理配置文件的方法
    public void init(Configuration configuration) throws ConfigurationException;
    public boolean needsReload();
    //解析package标签的方法
    public void loadPackages() throws ConfigurationException; 
}

    这个接口专门解析package标签。

    因为ContainerProvider和PackageProvider接口都是为了解析配置文件,为了方便管理,又设计了一个接口ConfigurationProvider 继承了这两个接口,解析配置文件的实现类,只要实现这个类就行了。

public interface ConfigurationProvider extends ContainerProvider, PackageProvider {
}

   (2)struct2解析配置文件的实现类

      针对不同的配置文件,struct2都提供了不同的实现类,这些实现类都继承了ConfigurationProvider 这个接口,从名字来看,ConfigurationProvider 是配置提供者的意思,所以解析property的类为DefaultPropertiesProvider,解析xml配置文件的为XmlConfigurationProvider,当然,struct2还会对每个类进行细分,如XmlConfigurationProvider的又分StrutsXmlConfigurationProvider和其他什么的。这里大家对每个类有个概念就行了,毕竟是别人设计的。这些对象都交由一个对象-ConfigurationManager管理,从名字看,这个就是配置管理者。所谓的管理,实际是把每个实现类,添加到ConfigurationManager一个属性(list集合中),如:

private List<ContainerProvider> containerProviders = new CopyOnWriteArrayList<ContainerProvider>();
 private void init_DefaultProperties() {
        configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());
    }

  (3)初始化解析各个配置文件的解析对象

              //初始化解析各个配置文件的对象
            init_DefaultProperties(); //解析default.properties的对象
            init_TraditionalXmlConfigurations(); // 解析struct*.xml配置文件的对象
            init_LegacyStrutsProperties(); // 
            init_CustomConfigurationProviders(); // 自定义解析对象
            init_FilterInitParameters() ; // web.xml的初始化参数
            init_AliasStandardObjects() ; // 解析bean的对象

    这里用了6个方法实例化了6个对象,这些对象都是针对不同的配置文件进行解析的。下面我们选一二个例子来说明下就可以了,这里主要是实例化几个对象,没有什么实质性的工作,所以没什么好纠结的。

  ①实例化解析default.properties的对象

 private void init_DefaultProperties() {
        configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());
    }

   这里new了一个DefaultPropertiesProvider对象,然后把这个对象添加到名为configurationManager的list集合中。

  ②实例化解析struct*.xml的对象

 private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";
 private void init_TraditionalXmlConfigurations() {
        //获得web.xml中配置的*.xml路径,当协同开发时,我们可以把每个成员的xml配置在这里
        String configPaths = initParams.get("config");
        if (configPaths == null) {
            //如果没有配置自定义的,则默认解析三个*.xml文件
            configPaths = DEFAULT_CONFIGURATION_PATHS;
        }
        //把上面定义的字符串,以每个xml文件为单位,分割到一个字符数组中
        String[] files = configPaths.split("\\s*[,]\\s*");
         //循环遍历,找出每个配置文件的解析器
        for (String file : files) {
            //这个只解析xml文件
            if (file.endsWith(".xml")) {
                if ("xwork.xml".equals(file)) {
                    //如果是xwork.xml文件,则用XmlConfigurationProvider解析                   configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false));
                } else {
                  //其他xml文件,则用StrutsXmlConfigurationProvider解析 
                    configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext));
                }
            } else {
                throw new IllegalArgumentException("Invalid configuration file name");
            }
        }
    }

   这里是实例化解析xml文件的对象,默认是解析struts-default.xml,struts-plugin.xml,struts.xml这三个,struts-default.xml这个是struct2默认的,一定要有,struts-plugin.xml这个是和spring集成的时候添加进去的,struts.xml这个是自定义的,当协同工作时,每个人都会有自己的xml文件,这时可以配置到web.xml,或者是放到/web-info/class目录下,然后用include方法放到struts.xml中。struct2解析xml文件,用的是StrutsXmlConfigurationProvider对象,StrutsXmlConfigurationProvider这个继承了XmlConfigurationProvider。

   其他的,如解析自定义解析器的,我们在自定义解析器时,会以configProviders为key值配置到web.xml中,然后在这里实例化。具体的就不详细解析了。

   private void init_CustomConfigurationProviders() {
        String configProvs = initParams.get("configProviders");
        if (configProvs != null) {
            String[] classes = configProvs.split("\\s*[,]\\s*");
            for (String cname : classes) {
                try {
                    Class cls = ClassLoaderUtils.loadClass(cname, this.getClass());
                    //实例化一个ConfigurationProvider
                   ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
                    configurationManager.addConfigurationProvider(prov);
                } 
                //异常信息
            }
        }
    }

二、解析配置文件,初始化IOC容器

   我们在init_PreloadConfiguration()这个办法循环调用各个解析的regist()或者是loadpack()方法对各个配置进行解析,然后返回一个Container对象

private Container init_PreloadConfiguration() {
         //解析配置文件
        Configuration config = configurationManager.getConfiguration();
         //返回一个ioc容器
        Container container = config.getContainer();
        
        //i18n国际化信息
        boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));
        LocalizedTextUtil.setReloadBundles(reloadi18n);

        return container;
    }

   (1)解析配置文件

public synchronized Configuration getConfiguration() {
        if (configuration == null) {
            //初始化一个对象来保存配置信息
            setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName));
            try {
                 //解析配置文件,getContainerProviders()获取上面包含了所有解析器的集合
                configuration.reloadContainer(getContainerProviders());
            } 
            //异常信息
        } else {
            //重新加载
            conditionalReload();
        }

        return configuration;
    }

     ①初始化一个对象来保存配置信息

      Configuration是一个接口,DefaultConfiguration是其实现类,这个类封装了解析出来的配置信息。如解析每个package 标签会把配置信息封装到packageConfig对象,因为有很多个package,也就是有很多个packageConfig对象,因此这里,把这些packageConfig对象放到一个map中,这个map就交给Configuration管理,通过这个类的get()方法,就可以获取package的配置信息了

    protected Map<String, PackageConfig> packageContexts = new LinkedHashMap<String, PackageConfig>();
    protected RuntimeConfiguration runtimeConfiguration;
    protected Container container;
    protected String defaultFrameworkBeanName;
    protected Set<String> loadedFileNames = new TreeSet<String>();
    protected List<UnknownHandlerConfig> unknownHandlerStack;

     ②解析配置文件

       这里就是解析配置文件的地方了,很复杂,我下篇博文再详细解析。

    public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
        packageContexts.clear();
        loadedFileNames.clear();
        List<PackageProvider> packageProviders = new ArrayList<PackageProvider>();

        ContainerProperties props = new ContainerProperties();
        ContainerBuilder builder = new ContainerBuilder();
        //循环遍历各个解析器的register方法解析配置文件
        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;
    }

(2)获取IOC容器

    因为解析配置文件时,把配置的对象都放到了Container,然后把这个Container交给Configuration管理,通过这个类的get()方法,就可以获取package的配置信息了。

 //返回一个ioc容器
Container container = config.getContainer();

public Container getContainer() {
        return container;
    }

(3)设置配置信息

    //设置ReloadingConfigs属性
  private void init_CheckConfigurationReloading(Container container) {
        FileManager.setReloadingConfigs("true".equals(container.getInstance(String.class,
                StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD)));
    }
    //设置paramsWorkaroundEnabled 属性
    private void init_CheckWebLogicWorkaround(Container container) {
        // test whether param-access workaround needs to be enabled
        if (servletContext != null && servletContext.getServerInfo() != null
                && servletContext.getServerInfo().indexOf("WebLogic") >= 0) {
            paramsWorkaroundEnabled = true;
        } else {
            paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class,
                    StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND));
        }
    }

      设置的这个属性没多大重要的,这里就不详谈了,有兴趣的可以去了解下。

(4)设置DispatcherListener接口

         if (!dispatcherListeners.isEmpty()) {
                for (DispatcherListener l : dispatcherListeners) {
                    l.dispatcherInitialized(this);
                }
            }

  有了DispatcherListener接口,相当于我们拥有了Dispatcher初始化过程中进行自由扩展的一个接口。这也是struct2在初始化主线中为我们提供的一个重要扩展点。


四、总结

    从这里我们就了解到了struct2是如何解析配置文件和搭建struct2所需运行的环境的。在初始化核心对象Dispacher时,会先初始化解析各种配置文件的对象,这些对象都实现了ConfigurationProvider 接口,然后循环遍历这些对象的init()和regist()方法,解析各种配置文件,如default.properties和struct*.xml文件,把解析出来的属性封装到不同的对象中,如package标签的属性封装到packageConfig对象,这些对象又汇集到configuration对象中统一管理,configuration对象又交给configurationManager对象,configurationManager对象又交给Diapcher,一层一层封装,从而就搭起了struct2的运行环境。至于是如何解析的,下篇博文我们继续来探讨。