本次解析使用的版本是:struts2-2.1.6、xwork-2.1.2
struts2在初始化过程中其实已经将它的内部结构、组件都建立起来了,通过了解它的内部结构我们就可以清晰的看到其内部实现机制、原理是怎样的。从而对于我们加深理解、灵活运用都会有很大好处。接下来就开始分析下。
struts2在初始化过程中的主要任务就是加载配置文件,同时初始化相应组件。和struts1不同,struts2是采用web服务器(如:tomcat)的过滤器(filter)来实现主控制的,通过过滤器截获请求,然后再通过内部组件实现具体功能,org.apache.struts2.dispatcher.FilterDispatcher就是这个过滤器,整个初始化工作就是通过过滤器的init()方法完成的,进入代码如下:
public void init(FilterConfig filterConfig) throws ServletException { try { this.filterConfig = filterConfig; initLogging(); dispatcher = createDispatcher(filterConfig); dispatcher.init(); dispatcher.getContainer().inject(this); staticResourceLoader.setHostConfig(new FilterHostConfig(filterConfig)); } finally { ActionContext.setContext(null); } }
首先保存下filterConfig ,以便下面通过其加载描述文件(web.xml)中的初始化参数。 接着创建dispatcher(调度器),保存到过滤器的dispatcher属性中,struts2正是通过这个调度器来实现整个控制功能的,也即MVC中M的功能。过滤器拦截到请求后,剩下的工作就交由调度器管理。F5进入下:
protected Dispatcher createDispatcher(FilterConfig filterConfig) { Map<String, String> params = new HashMap<String, String>(); for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); String value = filterConfig.getInitParameter(name); params.put(name, value); } return new Dispatcher(filterConfig.getServletContext(), params); }这里只是简单的生成了Dispatche实例,同时利用上面保存的filterConfig读取描述文件(web.xml)中的配置参数作文构造参数传入Dispatche。
返回init()方法继续dispatcher.init(),F5进入:
public void init() { if (configurationManager == null) { configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } init_DefaultProperties(); // [1] init_TraditionalXmlConfigurations(); // [2] init_LegacyStrutsProperties(); // [3] init_CustomConfigurationProviders(); // [5] init_FilterInitParameters() ; // [6] init_AliasStandardObjects() ; // [7] Container container = init_PreloadConfiguration(); container.inject(this); init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } } }这里会进行整个struts2初始化的所有工作。首先创建个配置文件管理器ConfigurationManager,这个管理器可以控制整个配置加载过程。[1]...[7] 是向configurationManager的加载器列表(containerProviders)中添加加载器(Provider),具体的加载任务实际上是由每个加载器完成的。下面先来说下加载器(Provider)的结构是啥样的:
每个加载器都必须实现com.opensymphony.xwork2.config.ContainerProvider ,或者说实现了该接口的就叫加载器(或叫提供器),F5进入下:
public interface ContainerProvider { /** * Called before removed from the configuration manager */ public void destroy(); public void init(Configuration configuration) throws ConfigurationException; public boolean needsReload(); public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException; }上面的就是加载器接口定义,其中有俩个方法是主要的,init()和register()。init()主要完成加载前的初始化工作,但是并非所有加载都需要初始化,这需要根据不同的加载方式视情况而定。比如配置文件是由xml配置的,这时init方法主要是生成Document,
在比如struts2-convention-plugin-2.1.6.jar插件中的配置是通过注解配置的,这时初始化就要完成另外的工作了,总之该方法必须不必须以及怎样实现是根据不同的配置方式而定的。register()方法是完成注册工作的,将xml中<bean>标签注册到struts2容器中,或者说是将其封装成对象添加到容器中,具体的如何会在下文详细说。 说到这不知道大家是否存在疑问,那配置文件中的包(<package>)是谁来加载那,其实也是加载器负责的,只不过这种加载器实现了更多的接口。F5
看下接口定义:
public interface ConfigurationProvider extends ContainerProvider, PackageProvider { }
可以看到,ConfigurationProvider接口继承了ContainerProvider, PackageProvider。因为继承了ContainerProvider接口,所以实现ConfigurationProvider的类必定是个加载器。PackageProvider接口,顾名思义是包接口,实现该接口的类能够具有
加载包的功能。这样实现ConfigurationProvider接口的类是加载器,同时又具有加载包的功能。F5进入PackageProvider接口:
public interface PackageProvider { public void init(Configuration configuration) throws ConfigurationException; public boolean needsReload(); public void loadPackages() throws ConfigurationException; }
根据上面的定义可以看到,里边有个loadPackages()方法,这个就是加载包的,init()方法的作用和上面的相同,不过这个方法会被最终的加载器重写,即每个加载器中只能有一个init()就够了。
下面举个加载struts.xml的过程,来看看加载器是如何工作的。F5进入init_TraditionalXmlConfigurations() :
private void init_TraditionalXmlConfigurations() { String configPaths = initParams.get("config"); if (configPaths == null) { configPaths = DEFAULT_CONFIGURATION_PATHS; } String[] files = configPaths.split("\\s*[,]\\s*"); 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"); } } }initParams是创建调度器时通过构造方法传入的。调用initParams.get("config")获得在web.xml中配置的配置文件列表,如果未配置则取系统中DEFAULT_CONFIGURATION_PATHS中默认配置的,查看DEFAULT_CONFIGURATION_PATHS可以看到默认是"struts-default.xml,struts-lugin.xml,struts.xml"。struts-default.xml系统默认配置、struts-plugin.xml扩展插件中配置、struts.xml由用户自定义的,一般我们开发中只需要配置struts.xml就行。代码第5行的for循环为每个配置文件都创建单独的加载器,然后通过addConfigurationProvider添加到配置文件管理器configurationManager的加载器列表中。由 if ("xwork.xml".equals(file)) 句判断可知配置的是StrutsXmlConfigurationProvider类型的加载器, F5进入这个加载器:
public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider { . //省略 . }
由上面代码定义看到StrutsXmlConfigurationProvider继承自XmlConfigurationProvider,继续看发现XmlConfigurationProvider实现了ConfigurationProvider接口,上面讲到ConfigurationProvider同时继承了ContainerProvider, PackageProvider俩个接口,所以它既是加载器,同时又具有加载包(<package>)的功能。
接下来我们先分析StrutsXmlConfigurationProvider的init()方法,StrutsXmlConfigurationProvider的init方法继承自 XmlConfigurationProvider的, F5进入XmlConfigurationProvider:
public void init(Configuration configuration) { this.configuration = configuration; this.includedFileNames = configuration.getLoadedFileNames(); loadDocuments(configFileName); }configuration是DefaultConfiguration的实例,是在调用加载器时通过init()方法传入的。第2句是获得已经加载的文件名,避免重复加载。configFileName是要加载的文件名,是生成加载器实例时,通过构造方法传入的,参考上面的代码。 loadDocuments()是加载(生成)Document,上面我们说过,对于xml文件来说加载器初始化(init)的主要工作是生成Document,F5进入:
private void loadDocuments(String configFileName) { try { loadedFileUrls.clear(); documents = loadConfigurationFiles(configFileName, null); } catch (ConfigurationException e) { throw e; } catch (Exception e) { throw new ConfigurationException("Error loading configuration file " + configFileName, e); } }loadedFileUrls.clear() 清空下已加载的文件列表,继续loadConfigurationFiles()方法, F5进入:
private List<Document> loadConfigurationFiles(String fileName, Element includeElement) { List<Document> docs = new ArrayList<Document>(); if (!includedFileNames.contains(fileName)) { if (LOG.isDebugEnabled()) { LOG.debug("Loading action configurations from: " + fileName); } includedFileNames.add(fileName); Iterator<URL> urls = null; Document doc = null; InputStream is = null; IOException ioException = null; try { urls = getConfigurationUrls(fileName); } catch (IOException ex) { ioException = ex; } if (urls == null || !urls.hasNext()) { if (errorIfMissing) { throw new ConfigurationException("Could not open files of the name " + fileName, ioException); } else { LOG.info("Unable to locate configuration files of the name " + fileName + ", skipping"); return docs; } } URL url = null; while (urls.hasNext()) { try { url = urls.next(); is = FileManager.loadFile(url); InputSource in = new InputSource(is); in.setSystemId(url.toString()); doc = DomHelper.parse(in, dtdMappings); } catch (XWorkException e) { if (includeElement != null) { throw new ConfigurationException("Unable to load " + url, e, includeElement); } else { throw new ConfigurationException("Unable to load " + url, e); } } catch (Exception e) { final String s = "Caught exception while loading file " + fileName; throw new ConfigurationException(s, e, includeElement); } finally { if (is != null) { try { is.close(); } catch (IOException e) { LOG.error("Unable to close input stream", e); } } } 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 ("include".equals(nodeName)) { String includeFileName = child.getAttribute("file"); if (includeFileName.indexOf('*') != -1) { // handleWildCardIncludes(includeFileName, docs, child); ClassPathFinder wildcardFinder = new ClassPathFinder(); wildcardFinder.setPattern(includeFileName); Vector<String> wildcardMatches = wildcardFinder.findMatches(); for (String match : wildcardMatches) { docs.addAll(loadConfigurationFiles(match, child)); } } else { docs.addAll(loadConfigurationFiles(includeFileName, child)); } } } } docs.add(doc); loadedFileUrls.add(url.toString()); } if (LOG.isDebugEnabled()) { LOG.debug("Loaded action configuration from: " + fileName); } } return docs; }上面代码虽然多,但是功能单一,就是生成Document,加入到docs列表中,最终会将docs返回给加载器的属性documents,供register()、loadPackages()方法使用。同时注意下这句:if ("include".equals(nodeName)) 是 解析xml,找出include包含的文件名递归进行加载,这点需要注意下。接着我们看下加载器的register()方法,StrutsXmlConfigurationProvider对它进行了重写, F5进入:
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { if (servletContext != null && !containerBuilder.contains(ServletContext.class)) { containerBuilder.factory(ServletContext.class, new Factory() { public Object create(Context context) throws Exception { return servletContext; } }); } super.register(containerBuilder, props); }第1句的if我们先不用关心,有关容器的东西,我会专门在下一篇中单独摘出来。直接看第2句,F5进入:
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { LOG.info("Parsing configuration file [" + configFileName + "]"); 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(); if ("bean".equals(nodeName)) { 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; 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; } if (!TextUtils.stringSet(name)) { name = Container.DEFAULT_NAME; } try { Class cimpl = ClassLoaderUtil.loadClass(impl, getClass()); Class ctype = cimpl; if (TextUtils.stringSet(type)) { ctype = ClassLoaderUtil.loadClass(type, getClass()); } if ("true".equals(onlyStatic)) { // Force loading of class to detect no class def found exceptions cimpl.getDeclaredClasses(); containerBuilder.injectStatics(cimpl); } else { if (containerBuilder.contains(ctype, name)) { Location loc = LocationUtils.getLocation(loadedBeans.get(ctype.getName() + name)); if (throwExceptionOnDuplicateBeans) { throw new ConfigurationException("Bean type " + ctype + " with the name " + name + " has already been loaded by " + loc, child); } } // Force loading of class to detect no class def found exceptions cimpl.getDeclaredConstructors(); if (LOG.isDebugEnabled()) { LOG.debug("Loaded type:" + type + " name:" + name + " impl:" + impl); } containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope); } loadedBeans.put(ctype.getName() + name, child); } catch (Throwable ex) { if (!optional) { throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, childNode); } else { LOG.debug("Unable to load optional class: " + ex); } } } else if ("constant".equals(nodeName)) { String name = child.getAttribute("name"); String value = child.getAttribute("value"); props.setProperty(name, value, childNode); } else if (nodeName.equals("unknown-handler-stack")) { List<UnknownHandlerConfig> unknownHandlerStack = new ArrayList<UnknownHandlerConfig>(); NodeList unknownHandlers = child.getElementsByTagName("unknown-handler-ref"); int unknownHandlersSize = unknownHandlers.getLength(); for (int k = 0; k < unknownHandlersSize; k++) { Element unknownHandler = (Element) unknownHandlers.item(k); unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name"))); } if (!unknownHandlerStack.isEmpty()) configuration.setUnknownHandlerStack(unknownHandlerStack); } } } } }第三行的for语句可以看到,遍历init()中生成的documents,然后解析标签,看if ("bean".equals(nodeName)) 句,如果是"<bean>"标签,将其解析出来,最后通过
containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope)将其添加到容器中,大家先记住这点就行,有关容器的东西,下篇会说。同时这句 if ("constant".equals(nodeName)) 也将常量解析出来,添加到属性对象props中,查下源码可以看到props是com.opensymphony.xwork2.util.location.LocatableProperties的实例,而LocatableProperties继承自Properties,所以也是个属性对象。这时注册就完成了,bean已经加载到容器中。
接下来再来看下加载包(<package>)的方法, F5进入StrutsXmlConfigurationProvider的loadPackages()方法:
@Override public void loadPackages() { ActionContext ctx = ActionContext.getContext(); ctx.put(reloadKey, Boolean.TRUE); super.loadPackages(); }上面的代码关注下第3行, F5进入:
public void loadPackages() throws ConfigurationException { List<Element> reloads = new ArrayList<Element>(); 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(); configuration = null; }
通过遍历在初始化中生成的documents解析xml文档。语句if ("package".equals(nodeName))当发现有包配置时开始解析<package>元素,可以发现loadPackages()方法只负责解析包,其他的不负责。语句PackageConfig cfg = addPackage(child)中
PackageConfig类就是包的封装对象,每个包都会单独创建一个PackageConfig实例,具体的解析过程在方法addPackage()中,child参数是待解析的包对应的xml元素,F5进入:
protected PackageConfig addPackage(Element packageElement) throws ConfigurationException { PackageConfig.Builder newPackage = buildPackageContext(packageElement); if (newPackage.isNeedsRefresh()) { return newPackage.build(); } if (LOG.isDebugEnabled()) { LOG.debug("Loaded " + newPackage); } // add result types (and default result) to this package addResultTypes(newPackage, packageElement); // load the interceptors and interceptor stacks for this package loadInterceptors(newPackage, packageElement); // load the default interceptor reference for this package loadDefaultInterceptorRef(newPackage, packageElement); // load the default class ref for this package loadDefaultClassRef(newPackage, packageElement); // load the global result list for this package loadGlobalResults(newPackage, packageElement); // load the global exception handler list for this package loadGobalExceptionMappings(newPackage, packageElement); // get actions NodeList actionList = packageElement.getElementsByTagName("action"); for (int i = 0; i < actionList.getLength(); i++) { Element actionElement = (Element) actionList.item(i); addAction(actionElement, newPackage); } // load the default action reference for this package loadDefaultActionRef(newPackage, packageElement); PackageConfig cfg = newPackage.build(); configuration.addPackageConfig(cfg.getName(), cfg); return cfg; }看第1句,PackageConfig.Builder中的Builder是包封装类PackageConfig的内部类,由名字可知是个"建造器"。它主要负责PackageConfig的实例化、拦截器(Interceptor)、返回类型(ResultTypes)、action等等的添加管理,最终会返回个PackageConfig实例。看源码会发现,包元素(<package>)中的几乎所有子元素对应的封装类都会有这样个建造器。这样做的好处是,确保数据的安全性。例如我们可以看下PackageConfig的定义源码, 会发现,PackageConfig本身不存在修改自身的任何方法,只存在一些类似getXXX的方法,这样就确保了PackageConfig对象在一旦建立好后的使用过程中,避免被有意或无意的恶意改动,这个就是使用建造器的目的。书归正传,看下第1句buildPackageContext()方法的实现,F5进入:
protected PackageConfig.Builder buildPackageContext(Element packageElement) { String parent = packageElement.getAttribute("extends"); String abstractVal = packageElement.getAttribute("abstract"); boolean isAbstract = Boolean.valueOf(abstractVal).booleanValue(); String name = TextUtils.noNull(packageElement.getAttribute("name")); String namespace = TextUtils.noNull(packageElement.getAttribute("namespace")); if (TextUtils.stringSet(packageElement.getAttribute("externalReferenceResolver"))) { throw new ConfigurationException("The 'externalReferenceResolver' attribute has been removed. Please use " + "a custom ObjectFactory or Interceptor.", packageElement); } PackageConfig.Builder cfg = new PackageConfig.Builder(name) .namespace(namespace) .isAbstract(isAbstract) .location(DomHelper.getLocationObject(packageElement)); if (TextUtils.stringSet(TextUtils.noNull(parent))) { // has parents, let's look it up List<PackageConfig> parents = ConfigurationUtil.buildParentsFromString(configuration, parent); if (parents.size() <= 0) { cfg.needsRefresh(true); } else { cfg.addParents(parents); } } return cfg; }前面5句获得包元素<package>的属性,然后通过PackageConfig.Builder cfg = new PackageConfig.Builder(name)....句建造个包建造器,同时将属性name、namespace等添加进去。最后将建造好的包对象cfg返回。其中还有句if (TextUtils.stringSet(TextUtils.noNull(parent)))需要注意下,将该包的父包找到,通过cfg.addParents(parents)将其添加到专门存放父包的属性中。configuration我们上面说过是专门用于存放配置的对象,整个系统中只存在一个。每个加载器中
都把持着configuration的引用,每次解析生成的PackageConfig实例,都会添加到configuration中统一管理。好的,这样一个包对象就创建好了,接下来就要解析它的子元素,将拦截器(interceptor)、返回类型(result-type)等子元素也封装进包对象中。返回到上个代码addPackage()方法中,余下的代码:
addResultTypes(newPackage, packageElement);
loadInterceptors(newPackage, packageElement);
loadDefaultInterceptorRef(newPackage, packageElement);
loadDefaultClassRef(newPackage, packageElement);
loadGlobalResults(newPackage, packageElement);
上面几个方法,包括下面还有个解析<action>元素的都是用于解析<package>子元素的,最后将子元素封装进PackageConfig对象中,具体的解析过程大家可以看下,比较简单,只要耐心看。这样一个包的完整解析就完成了,最后通过configuration.addPackageConfig(cfg.getName(), cfg)句,将解析的包添加进configuration中,完毕。哦,还有点需要注意下,我们进入configuration.addPackageConfig()方法(),configuration是com.opensymphony.xwork2.config.impl.DefaultConfiguration的实例:
public void addPackageConfig(String name, PackageConfig packageContext) { PackageConfig check = packageContexts.get(name); if (check != null) { if (check.getLocation() != null && packageContext.getLocation() != null && check.getLocation().equals(packageContext.getLocation())) { if (LOG.isDebugEnabled()) { LOG.debug("The package name '" + name + "' is already been loaded by the same location and could be removed: " + packageContext.getLocation()); } } else { throw new ConfigurationException("The package name '" + name + "' at location "+packageContext.getLocation() + " is already been used by another package at location " + check.getLocation(), packageContext); } } packageContexts.put(name, packageContext); }packageContexts是configuration中存放所有包的地方,是个Map实例,key值是包名,value是PackageConfig实例。首先第1句通过包名name找下是否已经有了同名的包,if()句,如果有了,会发现最终会抛出异常,这就是我们在配置自定义的struts
文件时,为啥包名不能相同的原因。
这样加载器的工作过程就说完了,我这里只是将其中涉及到的功能大概说了下,具体的每个功能点的实现还需要耐心逐句去体会,据我所知道的喜欢读源码的朋友中了解到,每个人都是读了n遍才能逐渐理解的,所以一定要耐心。
下面我们得返回去再看下加载器是怎样被调用的,继续Dispatche的init()方法,看这句Container container = init_PreloadConfiguration(); F5进入 init_PreloadConfiguration():
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); return container; }
configurationManager是配置(结构)管理器,加载器就是通过它调用的,最终它会生成个完整configuration。上面我们提到过configuration,Configuration是配置(结构)对象,在其他的开源工具中我们也经常会看到,有配置文件的地方总会有个Configuration对象,用于存储将加载上来的配置文件进行封装后存入Configuration中。 Container container = config.getContainer()句是Configuration生成的容器对象,有个这个容器我们就可以获得任意的组件了,包括系统内置的以及我们自定义的。F5进入configurationManager.getConfiguration():
public synchronized Configuration getConfiguration() { if (configuration == null) { setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName)); try { configuration.reloadContainer(getContainerProviders()); } catch (ConfigurationException e) { setConfiguration(null); throw new ConfigurationException("Unable to load configuration.", e); } } else { conditionalReload(); } return configuration; }如果configuration不存在,先创建个,可以看到configuration是DefaultConfiguration类型的。configuration.reloadContainer(getContainerProviders())句,获得所有加载器,然后作为参数传入reloadContainer()方法,该方法主要作用就是调用我们上面创建的加载器加载配置文件,同时生成容器对象,F5进入:
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(); 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 = 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); 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; }看到上面的代码是不是有种豁然开朗的感觉,别的有关容器的东西先不用管,就看其中的俩个for语句,第一个for遍历所有加载器,然后逐个调用每个加载器的init()、register()方法。第二个for也是遍历所有加载器,但其中有个判断,是否是PackageProvider类型,即是否实现了PackageProvider接口,如果是则说明这个加载器需要加载包,调用loadPackages()方法。这样就和上面的接上了,整个配置加载完毕。最后会调用rebuildRuntimeConfiguration()方法,顾名思义,建造运行时配置,其实在实际的使用配置时是不会访问configuration的,会单独建立个运行时的configuration,rebuildRuntimeConfiguration()就是干这个用的,有关这个方法的解析会在第三篇摘出来单独进行说明。这样加载配置的过程就大致的说完了,希望哪里不对的地方还请大家及时纠正,以免误人子弟。
接下来在第2篇中我先插篇有关容器的介绍,在第3篇中我在接过来。因为如果不说容器,好多东西还是很难理解的。本篇完毕。