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最核心的一个对象的原因。