struct2源码解读(7)之搭建struct2运行环境

    我们前面讨论过,struct2在tomcat启动的时候会自动运行过滤器StrutsPrepareAndExecuteFilter中的init()方法,在这个方法中搭建struct2运行环境完成了初始化。这个init()方法主要创建了一个核心对象Dispacher,实例化这个Dispacher对象之后,调用它的init()方法,解析配置文件。所谓的解析配置文件,其实就是把配置文件信息封装成不同的对象,如action标签信息封装到actionConfig对象,package标签信息封装到packageConfig对象等等.最后把这些对象放入到容器(container)中,当我们处理action请求时,就可以往这个container对象取配置信息数据,从而这个container就是我们所说的struct2运行环境。


一、container对象

   container,俗称容器,里面存放了我们在配置文件中配置的对象。既然是容器,那就有存和取。container提供了两个方法inject()和getInstance()来实现存和取的功能container.inject()就是往容器存放对象,container.getInstance()就是往容器取出对象,具体实现我们下篇博文再做详细的探讨,这里大家知道这两个方法的作用即可。

   前面我们已经探讨struct2是如何封装配置的信息到不同的对象中了,这篇博文我们来看下struct2是如何把这些对象放到容器中的.当然,首先得要获得container这个对象。


二、获得container对象

   我们来看看dispacher.init()中调用的init_PreloadConfiguration()方法,这个方法返回的就是一个Container对象。

 清单清单:dispacher.init()
   public void init() {

        try {
             //部分代码略。
             //解析配置文件,并封装到Container对象中
            Container container = init_PreloadConfiguration();
            //把dispacher对象也注入到容器中
            container.inject(this);
 
    }

        在这个init_PreloadConfiguration()方法中,把配置信息都封装到了Configuration对象中,包括container这个容器对象,然后调用get()方法就可以得到container这个对象。

 private Container init_PreloadConfiguration() {
       //封装配置信息到Configuration 对象
        Configuration config = configurationManager.getConfiguration();
        //从配置信息到获得这个容器
        Container container = config.getContainer();
        return container;
    }

      Configuration,顾名思义,配置信息,这个对象包含了所有的配置信息。我们来看看这个Configuration对象。Configuration是一个接口,DefaultConfiguration是其默认实现类。

public class DefaultConfiguration implements Configuration {
    // 属性
    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;
    ObjectFactory objectFactory;
    //方法省略。
    
   }

    这个Configuration配置信息对象主要就是4个属性,一个就是packageConfig,一个就是runtimeConfiguration,还有就是Container和objectFactory.这个packageConfig对象封装了packageConfig,这个package都是我们开发的时候自己配的,而这个runtimeConfiguration是处理aciton请求时所需要的对象。那么,struct2是如何实例化这个Configuration对象的呢?

   public synchronized Configuration getConfiguration() {
         //如果Configuration,为空,就实例化一个
        if (configuration == null) {
            //setConfiguration其实就是this.configuration = configuration;这里就可以看到Configuration的默认实现类是DefaultConfiguration,实例化DefaultConfiguration时,也初始化了defaultFrameworkBeanName="struts",defaultFrameworkBeanName在初始化ConfigurationManager时就已经指定
            setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName));
            try {
                //getContainerProviders()获取解析器,这里调用reloadContainer解析配置文件,并完成Configuration 初始化,也就是设置上面属性(container)的值,这样就能通过configuration获得容器对象container了
                configuration.reloadContainer(getContainerProviders());
            }
            //异常信息,略
        } else {
            conditionalReload();
        }
        //返回configuration对象
        return configuration;
    }

    下面重点探讨下这个reloadContainer()方法,这里调用DefaultConfiguration.reloadContainer()方法。这个reloadContainer()就是设置了Configuration对象的所有属性值。


2.1.设置packageConfig

    设置这个packageConfig属性,在解析package标签的时候已经解释过了,struct2把package标签里面的action,result等标签分别封装成actionConfig和resultConfig对象等,然后把这些对象封装到packageConfig对象,然后把packageConfig对象填加到Configuration对象的一个list集合中。


2.2.设置objectFactory

 objectFactory = container.getInstance(ObjectFactory.class);

   这个objectFactory是直接用getInstance(Class class)方法从容器中取出来的,也就是说前面肯定有inject()注入操作,我们来看下struct-default.xml配置文件

  <bean class="com.opensymphony.xwork2.ObjectFactory" name="xwork" />
  <bean type="com.opensymphony.xwork2.ObjectFactory" name="struts" class="org.apache.struts2.impl.StrutsObjectFactory" />

    果然,配置文件中配置了objectFactory,那它肯定会在容器中,通过getInstance(Class class)这个ByClass的方法就可以取出相应的objectFactory。这里再一次体现了依赖注入的概念。通过配置配置文件,就能控制要实现的类型(这个bean会在解析配置文件的时候封装到containerBuilder对象中,然后创建container时,会把这个beans作为参数传进container)


2.3.设置runtimeConfiguration

   通过rebuildRuntimeConfiguration()这个方法就能设置runtimeConfiguration这个属性

public void rebuildRuntimeConfiguration() {
        runtimeConfiguration = buildRuntimeConfiguration();
    }

   这个runtimeConfiguration是指后面处理action请求时所需要的环境信息。那肯定有人问,处理action请求所需的信息,也就是我们配置的package内容不是已经封装到了packageConfig中了吗?为什么还要runtimeConfiguration这个对象。你们要清楚,每一个packageConfig对象都是对应一个package标签的信息,需要把他们放到一个list集合中了,但从集合中取出某个packageConfig的信息依然会很不方便,因此这就需要进行进一步的封装。

   protected synchronized RuntimeConfiguration buildRuntimeConfiguration() throws ConfigurationException {
        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()) {
      //package没配置abstract的才会进行封装,因此配置了abstract的package是不会实例化的
           if (!packageConfig.isAbstract()) {
               //获得当前package的命名空间namespace
               String namespace = packageConfig.getNamespace();
               //判断
               Map<String, ActionConfig> configs = namespaceActionConfigs.get(namespace);
                if (configs == null) {
                    configs = new LinkedHashMap<String, ActionConfig>();
                }
            //把当前package的actionConfig包括父类的actionConfig对象都放到一个map中
           Map<String, ActionConfig> actionConfigs = packageConfig.getAllActionConfigs();

            for (Object o : actionConfigs.keySet()) {
                    String actionName = (String) o;
                    ActionConfig baseConfig = actionConfigs.get(actionName);
               configs.put(actionName, buildFullActionConfig(packageConfig, baseConfig));
                }
             //以namespace为key值,Map<String, ActionConfig>为vaule值存到一个map中
                namespaceActionConfigs.put(namespace, configs);
               //DefaultActionRef
                if (packageConfig.getFullDefaultActionRef() != null) {
        namespaceConfigs.put(namespace, packageConfig.getFullDefaultActionRef());
                }
            }
        }
        //返回一个RuntimeConfiguration对象
        return new RuntimeConfigurationImpl(namespaceActionConfigs, namespaceConfigs);
    }

       这个RuntimeConfiguration对象有两个方法,一个是getActionConfigs(),得到的Map<String, Map<String, ActionConfig>>,一个是getActionConfig()得到ActionConfigs。

代码清单:RuntimeConfigurationImpl构造函数
 public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespaceActionConfigs, Map<String, String> namespaceConfigs) {
            this.namespaceActionConfigs = namespaceActionConfigs;
            this.namespaceConfigs = namespaceConfigs;
     //正规表达式解析器
    PatternMatcher<int[]> matcher = container.getInstance(PatternMatcher.class);
   this.namespaceActionConfigMatchers = new LinkedHashMap<String, ActionConfigMatcher>();
 this.namespaceMatcher = new NamespaceMatcher(matcher, namespaceActionConfigs.keySet());

            for (String ns : namespaceActionConfigs.keySet()) {
                namespaceActionConfigMatchers.put(ns,
                        new ActionConfigMatcher(matcher,
                                namespaceActionConfigs.get(ns), true));
            }
        }

    这里就明了了,RuntimeConfiguration其实就是把所有的actionConfig封装到了一起。下面再来探讨下设置Container属性,这样,configuration的属性就基本设置完了。


2.4.设置Container

    public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
        //解析配置文件。略
        //把protperty value值封装到一个factory,然后把factory添加到builder的一个集合中
        props.setConstants(builder);
        //把Configuration封装到Factory,然后把Factory添加到builder的一个集合中
        builder.factory(Configuration.class, new Factory<Configuration>() {
            public Configuration create(Context context) throws Exception {
                return DefaultConfiguration.this;
            }
        });
        //从线程变量actionConext中取出ActionContext,刚开始是null的
        ActionContext oldContext = ActionContext.getContext();
        try {
            // Set the bootstrap container for the purposes of factory creation
            Container bootstrap = createBootstrapContainer();
            setContext(bootstrap);
            container = builder.create(false);
            setContext(container);
            //解析package标签,略
        } finally {
            //清空ActionContext
            if (oldContext == null) {
                ActionContext.setContext(null);
            }
        }
        return packageProviders;
    }

    前几篇博文我们已经解析过struct2是如何封装各种配置信息到不同对象上的了,如大家不明白可以参考下前几篇博文,这里探讨下struct2是如何把这些对象注入到container中的。

    struct2在循环遍历解析器解析配置文件时,把property文件和xml文件中的常量属性,以key-value的形式封装到ContainerProperties对象中,如<constant name="struts.devMode" value="true" />,封装后为(struts.devMode:true);而把<bean>标签的属性,封装到一个继承factory接口的实现类上,而把这个实现类封装到ContainerBuilder的一个list集合属性中。详细解析可参考前几篇博文。从设计上来说,ContainerProperties对象里面的属性能否也能封装到ContainerBuilder对象中呢 ?

props.setConstants(builder);

这个方法就是把ContainerProperties对象里面的属性封装到ContainerBuilder对象中

public void setConstants(ContainerBuilder builder) {
           //keySet()获取hashtable上的所有key值,循环遍历
            for (Object keyobj : keySet()) {
                String key = (String)keyobj;
                //这里又调用了builder的factory方法,把value值封装到了LocatableConstantFactory,然后把这个LocatableConstantFactory添加到builder的一个list集合中
                builder.factory(String.class, key,
                        new LocatableConstantFactory<String>(getProperty(key), getPropertyLocation(key)));
            }
        }

    把所有配置信息(除了package标签的属性)封装到ContainerBuilder对象中之后.把DefaultConfiguration也用工厂方法封装到ContainerBuilder中。这样,ContainerBuilder就拥有了配置文件中的所有对象的信息。

    最后,调用了ContainerBuilder的ceate(false)方法,返回了一个container对象

container = builder.create(false);

   这里也用到了上篇博文我们提到的构造者模式。在这个create(false方法)创建了一个Container对象实例。

 public Container create(boolean loadSingletons) {
    ensureNotCreated();
    created = true;
    final ContainerImpl container = new ContainerImpl(
        new HashMap<Key<?>, InternalFactory<?>>(factories));
    if (loadSingletons) {
      //loadSingletons为false,这段代码不用管
    }
    container.injectStatics(staticInjections);
    return container;
  }

    factorise封装了所有的配置信息(package除外),这里用factorise做参数,new了一个container对象,从而就把对象都放进了容器中。我们还可以通过inject()方法,手工地往容器中放入对象。


三、总结

   至此,struct2初始化工作就全部完成了。struct2在创始化过程中,解析配置文件,把所有配置信息(package除外)封装到factory接口的实现类中,然后把这些factory设置到container容器对象中,package的配置信息,最后封装到runtimeConfiguration。这两个对像有点类似于系统属性和自定义属性,最后把这些对象封装到Configuration对象,Configuration对象又交给ConfigurationManager对象

ConfigurationManager对象是Dispacher对象的一个属性,通过层层封装,最终把所有信息都交给了Dispacher.所以说,struct2的初始化,也就是生成一个包含配置信息的Dispacher对象,以后就是通过这个对象处理action请求的,这也是dispacher是struct2最核心的一个对象的原因。