首页 > 代码库 > struts2源码探索之初始化(四)

struts2源码探索之初始化(四)

在上一篇文章 struts2源码探索之初始化(三)中,已经分析到了创建bootstrap这个容器的最后一步了,调用ContainerBuilder类的create()。恩,接下来看这个方法:

public Container create(boolean loadSingletons) {
    ensureNotCreated();
    created = true;
    final ContainerImpl container = new ContainerImpl(
        new HashMap<Key<?>, InternalFactory<?>>(factories));
    if (loadSingletons) {
      container.callInContext(new ContainerImpl.ContextualCallable<Void>() {
        public Void call(InternalContext context) {
          for (InternalFactory<?> factory : singletonFactories) {
            factory.create(context);
          }
          return null;
        }
      });
    }
    container.injectStatics(staticInjections);
    return container;
  }

先创建一个Container对象,也就是其实现类ContainerImpl啦,应注意的是ContainerBuilder收集的factories传递给了它,然后通过工厂创建所有scope指定为singleton的bean,最后对某些类(在这里,目前是空,也就是没什么注入操作)注入内置bean。大概就是这样子,接下来详细说明这段代码。

1.Container

前面已经多次提到了这个接口,但一直没有一探究竟,现在是时候了。看看接口的定义吧:

public interface Container extends Serializable {

  /**
   * Default dependency name.
   */
  String DEFAULT_NAME = "default";

  /**
   * Injects dependencies into the fields and methods of an existing object.
   */
  void inject(Object o);

  /**
   * Creates and injects a new instance of type {@code implementation}.
   */
  <T> T inject(Class<T> implementation);

  /**
   * Gets an instance of the given dependency which was declared in
   * {@link com.opensymphony.xwork2.inject.ContainerBuilder}.
   */
  <T> T getInstance(Class<T> type, String name);

  /**
   * Convenience method. Equivalent to {@code getInstance(type,
   * DEFAULT_NAME)}.
   */
  <T> T getInstance(Class<T> type);
  
  /**
   * Gets a set of all registered names for the given type
   * @param type The instance type
   * @return A set of registered names or empty set if no instances are registered for that type
   */
  Set<String> getInstanceNames(Class<?> type);

  /**
   * Sets the scope strategy for the current thread.
   */
  void setScopeStrategy(Scope.Strategy scopeStrategy);

  /**
   * Removes the scope strategy for the current thread.
   */
  void removeScopeStrategy();
}

从接口的定义可以看出,Container有两种关键的操作,一是注入,也就是inject(),二是获得实例,也就是getInstance()。可还记得前面说过,要创建出一个可用的Container,需要多方收集bean配置?bean配置中的class属性指定的Factory,以及硬编码加入的诸多factory,都是用来创建bean对象的。也就是说这许许多多的factory都是为getInstance()方法服务的。而Container又为什么要创建bean呢?因为在处理请求或其他地方需要使用bean,并且这些bean可以提供给任何类,通过inject()这个方法。关于注入的更详细的分析,在后面给出。

2.ContainerImpl

先看它存储数据的属性:

class ContainerImpl implements Container {

	final Map<Key<?>, InternalFactory<?>> factories;
	final Map<Class<?>, Set<String>> factoryNamesByType;

	/**
	 * Field and method injectors.
	 */
	final Map<Class<?>, List<Injector>> injectors =
			new ReferenceCache<Class<?>, List<Injector>>() {
				@Override
				protected List<Injector> create( Class<?> key ) {
					List<Injector> injectors = new ArrayList<Injector>();
					addInjectors(key, injectors);
					return injectors;
				}
		};
}

可以看出,一是factoies,二是injectors,和Container的两种操作是一一对应的。


至此,也就完成了bootstrap这个容器的创建。所以,让我们回到Configuration类。这真是相当漫长的旅途啊。

希望大家还记得我们曾经走过的这段路:

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();
        Container bootstrap = createBootstrapContainer(providers);
        for (final ContainerProvider containerProvider : providers)
        {
            bootstrap.inject(containerProvider);
            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

            setContext(bootstrap);
            container = builder.create(false);
            setContext(container);
            objectFactory = container.getInstance(ObjectFactory.class);

            // Process the configuration providers first
            for (final ContainerProvider containerProvider : providers)
            {
                if (containerProvider instanceof PackageProvider) {
                    container.inject(containerProvider);
                    ((PackageProvider)containerProvider).loadPackages();
                    packageProviders.add((PackageProvider)containerProvider);
                }
            }

            // Then process any package providers from the plugins
            Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);
            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;
    }

bootstrap被创建出来之后,自然就是使用它了。用它来创建出最后的Container。

调用各个Provider的register()

下面就说说各个Provider的register()

1.FileManagerProvider

这个前面说过了,就不说了。

2.DefaultPropertiesProvider

加载default.properties文件的配置信息到ContainerProperties对象中。

3.XmlConfigurationProvider

加载xml配置文件(struts-default.xml,struts.xml,)中的<bean>和<constant>元素,bean就加入到factories,constant就加入到ContainerProperties,这些信息最终都由Container对象去维护。

4.LegacyPropertiesConfigurationProvider

加载properties文件,并且加入一个创建Local的InternalFactory。

5.FilterInitParameters的匿名Provider

加入filter的参数到ContainerProperties

6.BeanSelectionProvider

加入许多的bean factory和常量

public void register(ContainerBuilder builder, LocatableProperties props) {
        alias(ObjectFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY, builder, props);
        alias(FileManagerFactory.class, StrutsConstants.STRUTS_FILE_MANAGER_FACTORY, builder, props, Scope.SINGLETON);

        alias(XWorkConverter.class, StrutsConstants.STRUTS_XWORKCONVERTER, builder, props);
        alias(CollectionConverter.class, StrutsConstants.STRUTS_CONVERTER_COLLECTION, builder, props);
        alias(ArrayConverter.class, StrutsConstants.STRUTS_CONVERTER_ARRAY, builder, props);
        alias(DateConverter.class, StrutsConstants.STRUTS_CONVERTER_DATE, builder, props);
        alias(NumberConverter.class, StrutsConstants.STRUTS_CONVERTER_NUMBER, builder, props);
        alias(StringConverter.class, StrutsConstants.STRUTS_CONVERTER_STRING, builder, props);

        alias(ConversionPropertiesProcessor.class, StrutsConstants.STRUTS_CONVERTER_PROPERTIES_PROCESSOR, builder, props);
        alias(ConversionFileProcessor.class, StrutsConstants.STRUTS_CONVERTER_FILE_PROCESSOR, builder, props);
        alias(ConversionAnnotationProcessor.class, StrutsConstants.STRUTS_CONVERTER_ANNOTATION_PROCESSOR, builder, props);
        alias(TypeConverterCreator.class, StrutsConstants.STRUTS_CONVERTER_CREATOR, builder, props);
        alias(TypeConverterHolder.class, StrutsConstants.STRUTS_CONVERTER_HOLDER, builder, props);

        alias(TextProvider.class, StrutsConstants.STRUTS_XWORKTEXTPROVIDER, builder, props, Scope.DEFAULT);

        alias(LocaleProvider.class, StrutsConstants.STRUTS_LOCALE_PROVIDER, builder, props);
        alias(ActionProxyFactory.class, StrutsConstants.STRUTS_ACTIONPROXYFACTORY, builder, props);
        alias(ObjectTypeDeterminer.class, StrutsConstants.STRUTS_OBJECTTYPEDETERMINER, builder, props);
        alias(ActionMapper.class, StrutsConstants.STRUTS_MAPPER_CLASS, builder, props);
        alias(MultiPartRequest.class, StrutsConstants.STRUTS_MULTIPART_PARSER, builder, props, Scope.DEFAULT);
        alias(FreemarkerManager.class, StrutsConstants.STRUTS_FREEMARKER_MANAGER_CLASSNAME, builder, props);
        alias(VelocityManager.class, StrutsConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME, builder, props);
        alias(UrlRenderer.class, StrutsConstants.STRUTS_URL_RENDERER, builder, props);
        alias(ActionValidatorManager.class, StrutsConstants.STRUTS_ACTIONVALIDATORMANAGER, builder, props);
        alias(ValueStackFactory.class, StrutsConstants.STRUTS_VALUESTACKFACTORY, builder, props);
        alias(ReflectionProvider.class, StrutsConstants.STRUTS_REFLECTIONPROVIDER, builder, props);
        alias(ReflectionContextFactory.class, StrutsConstants.STRUTS_REFLECTIONCONTEXTFACTORY, builder, props);
        alias(PatternMatcher.class, StrutsConstants.STRUTS_PATTERNMATCHER, builder, props);
        alias(StaticContentLoader.class, StrutsConstants.STRUTS_STATIC_CONTENT_LOADER, builder, props);
        alias(UnknownHandlerManager.class, StrutsConstants.STRUTS_UNKNOWN_HANDLER_MANAGER, builder, props);
        alias(UrlHelper.class, StrutsConstants.STRUTS_URL_HELPER, builder, props);

        alias(TextParser.class, StrutsConstants.STRUTS_EXPRESSION_PARSER, builder, props);

        if ("true".equalsIgnoreCase(props.getProperty(StrutsConstants.STRUTS_DEVMODE))) {
            props.setProperty(StrutsConstants.STRUTS_I18N_RELOAD, "true");
            props.setProperty(StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, "true");
            props.setProperty(StrutsConstants.STRUTS_FREEMARKER_TEMPLATES_CACHE, "false");
            props.setProperty(StrutsConstants.STRUTS_FREEMARKER_TEMPLATES_CACHE_UPDATE_DELAY, "0");
            // Convert struts properties into ones that xwork expects
            props.setProperty(XWorkConstants.DEV_MODE, "true");
        } else {
            props.setProperty(XWorkConstants.DEV_MODE, "false");
        }

        // Convert Struts properties into XWork properties
        convertIfExist(props, StrutsConstants.STRUTS_LOG_MISSING_PROPERTIES, XWorkConstants.LOG_MISSING_PROPERTIES);
        convertIfExist(props, StrutsConstants.STRUTS_ENABLE_OGNL_EXPRESSION_CACHE, XWorkConstants.ENABLE_OGNL_EXPRESSION_CACHE);
        convertIfExist(props, StrutsConstants.STRUTS_ENABLE_OGNL_EVAL_EXPRESSION, XWorkConstants.ENABLE_OGNL_EVAL_EXPRESSION);
        convertIfExist(props, StrutsConstants.STRUTS_ALLOW_STATIC_METHOD_ACCESS, XWorkConstants.ALLOW_STATIC_METHOD_ACCESS);
        convertIfExist(props, StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, XWorkConstants.RELOAD_XML_CONFIGURATION);

        LocalizedTextUtil.addDefaultResourceBundle("org/apache/struts2/struts-messages");
        loadCustomResourceBundles(props);
    }



Configuration Factory

各个Provider的register()方法加入了许多的factory,另外还加了创建Configuration的factory。


factory的收集完成之后,就是创建Container了,和上一个Container即bootstrap的创建过程是一样的,就不重复说了。

接着看reloadContainer(),已经到了这段代码:

container = builder.create(false);
            setContext(container);

关于ActionContext,在处理请求时再详细讲吧。

先看接下来的代码:


for (final ContainerProvider containerProvider : providers)
            {
                if (containerProvider instanceof PackageProvider) {
                    container.inject(containerProvider);
                    ((PackageProvider)containerProvider).loadPackages();
                    packageProviders.add((PackageProvider)containerProvider);
                }
            }

一一调用loadPackage()方法来加载<package>元素,且看loadPackage():

public void loadPackages() throws ConfigurationException {
        List<Element> reloads = new ArrayList<Element>();
        verifyPackageStructure();

        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();

                    if ("package".equals(nodeName)) {
                        PackageConfig cfg = addPackage(child);
                        if (cfg.isNeedsRefresh()) {
                            reloads.add(child);
                        }
                    }
                }
            }
            loadExtraConfiguration(doc);
        }

        if (reloads.size() > 0) {
            reloadRequiredPackages(reloads);
        }

        for (Document doc : documents) {
            loadExtraConfiguration(doc);
        }

        documents.clear();
        declaredPackages.clear();
        configuration = null;
    }

不详细讲了,就是加载xml中的<package>,最终封装成PackageConfig类。

接下来是这段代码:

Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);
            for (String name : packageProviderNames) {
                PackageProvider provider = container.getInstance(PackageProvider.class, name);
                provider.init(this);
                provider.loadPackages();
                packageProviders.add(provider);
            }

由于返回的packageProvidernames是没有元素的,所以也就不执行什么有意义的代码了。

再接下来就是:

rebuildRuntimeConfiguration();

前面说过,RuntimeConfiguration封装了Action配置,封装成了ActionConfig类,这里就是重新读取PackageConfig中的ActionConfig了。如果觉得有点迷茫,可以看看PackageConfig的属性:

public class PackageConfig extends Located implements Comparable, Serializable, InterceptorLocator {

    private static final Logger LOG = LoggerFactory.getLogger(PackageConfig.class);

    protected Map<String, ActionConfig> actionConfigs;
    protected Map<String, ResultConfig> globalResultConfigs;
    protected Map<String, Object> interceptorConfigs;
    protected Map<String, ResultTypeConfig> resultTypeConfigs;
    protected List<ExceptionMappingConfig> globalExceptionMappingConfigs;
    protected List<PackageConfig> parents;
    protected String defaultInterceptorRef;
    protected String defaultActionRef;
    protected String defaultResultType;
    protected String defaultClassRef;
    protected String name;
    protected String namespace = "";
    protected boolean isAbstract = false;
    protected boolean needsRefresh;
}

可以看到,PackageConfig封装了ActionConfig,ResultConfig,ExceptionMappingConfig,InterceptorConfigs,以及namespace等等。是不是和<package>元素一一对应呢?哈哈。


到此为止,最终的Container也构造出来了。

接下来干嘛呢?接下来又回到了Dispatcher的init()方法。想当初,我们是从以下这个方法开始渐行渐远的:

private Container init_PreloadConfiguration() {
        Configuration config = configurationManager.getConfiguration();
        Container container = config.getContainer();

        boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));
        LocalizedTextUtil.setReloadBundles(reloadi18n);

        ContainerHolder.store(container);

        return container;
    }

Container已经创建好了,接下来就是把它保存起来。保存在ContainerHolder中。这里用到了ThreadLocal设计模式,且听我细细道来。

看ContainerHolder类的源代码:

class ContainerHolder {

    private static ThreadLocal<Container> instance = new ThreadLocal<Container>();

    public static void store(Container instance) {
        boolean reloadConfigs = Boolean.valueOf(instance.getInstance(String.class, StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD));
        if (!reloadConfigs) {
            // reloadConfigs is false, configuration will do not change, just keep it
            ContainerHolder.instance.set(instance);
        }
    }

    public static Container get() {
        return ContainerHolder.instance.get();
    }

    public static void clear() {
        ContainerHolder.instance.remove();
    }

}

关于ThreadLocal设计模式的详细,可以参阅我转载的一篇文章:正确理解ThreadLocal


接下来就看前面提到的注入了。看Dispatcher类的init()中,这么一句代码:

container.inject(this);

这句代码执行什么功能呢?我们先找到Dispatcher类的这样的语句:

/**
     * Modify state of StrutsConstants.STRUTS_DEVMODE setting.
     * @param mode New setting
     */
    @Inject(StrutsConstants.STRUTS_DEVMODE)
    public void setDevMode(String mode) {
        devMode = "true".equals(mode);
    }

    /**
     * Modify state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting.
     * @param disableRequestAttributeValueStackLookup New setting
     */
    @Inject(value=http://www.mamicode.com/StrutsConstants.STRUTS_DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP, required=false)>
这些方法都是带@Inject注解的。

通俗易懂的说,对于带这种注解的方法来说,当调用container.inject(dispatcher)方法时,以上方法会一一被调用,并且传递进去的参数就是Container中内置的bean或者常量。是不是相当神奇呢?

通过这种方法,Container中的bean就可以为任何类服务了。


ok,到此为止,Dispatcher的初始化也已经全部完成。这就意味着,本系列的文章到此就要告一段落了。


struts2框架的初始化写了四篇文章才算基本介绍完成,嘿嘿,有小小的成就感,毕竟,如此遥远的旅途啊。

虽然才刚刚起步,但是,还是会再接再厉,接下来就是struts2处理请求的过程了。这就比初始化还要复杂的多了,真是一个挑战呢。