我們把struts2分為兩塊:一是struts2系統初始化,二是struts2處理請求,對請求作出響應。
這篇,我們先來分析下struts2的啟動過程這部分。
struts2的啟動入口為org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter。struts2的啟動就是執行它的init()方法。我覺得它的啟動就是為了一個目的,那就是為了創建出container對象和packageconfig對象。
public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations(); try { FilterHostConfig config = new FilterHostConfig(filterConfig); init.initLogging(config); Dispatcher dispatcher = init.initDispatcher(config); init.initStaticContentLoader(config, dispatcher); prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher); execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher); this.excludedPatterns = init.buildExcludedPatternsList(dispatcher); postInit(dispatcher, filterConfig); } finally { init.cleanup(); } }
FilterConfig filterConfig就是web.xml的配置信息。在init中對它進行了封裝,變成了FilterHostConfig,當然,這些不是重點。我們關注的是
Dispatcher dispatcher = init.initDispatcher(config);
創建dispatcher(調度器),struts2正是通過這個調度器來實現整個控制功能的。
public Dispatcher initDispatcher( HostConfig filterConfig ) { Dispatcher dispatcher = createDispatcher(filterConfig); dispatcher.init(); return dispatcher; }
我們關注的是
dispatcher.init();
public void init() { if (configurationManager == null) { configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } try { init_FileManager(); 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_CheckWebLogicWorkaround(container); if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } } } catch (Exception ex) { if (LOG.isErrorEnabled()) LOG.error("Dispatcher initialization failed", ex); throw new StrutsException(ex); } }
首先得到的是配置管理器。這個配置管理器控制整個配置的加載過程。。[1]...[7] 是向configurationManager的加載器列表(containerProviders)中添加加載器(Provider),它們分別是:FileManagerProvider,DefaultPropertiesProvider,StrutsXMLConfigurationProvider等等。接下來是非常重要的一句:
Container container = 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; }
public synchronized Configuration getConfiguration() { if (configuration == null) { setConfiguration(createConfiguration(defaultFrameworkBeanName)); try { configuration.reloadContainer(getContainerProviders()); } catch (ConfigurationException e) { setConfiguration(null); throw new ConfigurationException("Unable to load configuration.", e); } } else { conditionalReload(configuration.getContainer()); } return configuration; }
當設置完了configuration之後,才開始真正的創建"Container和PackageConfig對象"
configuration.reloadContainer(getContainerProviders());
public synchronized ListreloadContainer(List providers) throws ConfigurationException { packageContexts.clear(); loadedFileNames.clear(); List packageProviders = new ArrayList (); 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 () { 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 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; }
你會看到遍歷providers,將每個加載器進行初始化和注冊到builder中。builder是建造container的建造者。我們後面會看到,先不管這麼多。我們接著看代碼。
其實很多provider的init都是空的代碼,不需要進行初始化,我們關注一個很重要的provider:StrutsXMLConfigurationProvider,這個provider是加載三個默認配置文件的。struts-default.xml,struts-lugin.xml,struts.xml。前兩個是框架級別的,後面一個是應用級別的。我們看看它的init代碼吧。
public void init(Configuration configuration) { this.configuration = configuration; this.includedFileNames = configuration.getLoadedFileNames(); loadDocuments(configFileName); }
private ListloadConfigurationFiles(String fileName, Element includeElement) { List docs = new ArrayList (); List finalDocs = new ArrayList (); if (!includedFileNames.contains(fileName)) { if (LOG.isDebugEnabled()) { LOG.debug("Loading action configurations from: " + fileName); } includedFileNames.add(fileName); Iterator urls = 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 { if (LOG.isInfoEnabled()) { 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()); docs.add(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); } } } } //sort the documents, according to the "order" attribute Collections.sort(docs, new Comparator () { public int compare(Document doc1, Document doc2) { return XmlHelper.getLoadOrder(doc1).compareTo(XmlHelper.getLoadOrder(doc2)); } }); for (Document doc : docs) { 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 wildcardMatches = wildcardFinder.findMatches(); for (String match : wildcardMatches) { finalDocs.addAll(loadConfigurationFiles(match, child)); } } else { finalDocs.addAll(loadConfigurationFiles(includeFileName, child)); } } } } finalDocs.add(doc); loadedFileUrls.add(url.toString()); } if (LOG.isDebugEnabled()) { LOG.debug("Loaded action configuration from: " + fileName); } } return finalDocs; }
其實它就是為了生成Document對象而已,加入到finalDocs列表中,這個將會在以後的register和loadpackage中用到。需要注意的是if ("include".equals(nodeName)) ,這個會將它包含的文件也加入到finalDocs中。
我們接著原來的代碼,到了containerProvider.register(builder, props);
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { if (servletContext != null && !containerBuilder.contains(ServletContext.class)) { containerBuilder.factory(ServletContext.class, new Factory() { public ServletContext create(Context context) throws Exception { return servletContext; } }); } super.register(containerBuilder, props); }
我們只關注super.register(containerBuilder, props);
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { if (LOG.isInfoEnabled()) { LOG.info("Parsing configuration file [" + configFileName + "]"); } MaploadedBeans = new HashMap (); 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 (StringUtils.isEmpty(name)) { name = Container.DEFAULT_NAME; } try { Class cimpl = ClassLoaderUtil.loadClass(impl, getClass()); Class ctype = cimpl; if (StringUtils.isNotEmpty(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 unknownHandlerStack = new ArrayList (); 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); } } } } }
我們先在這裡插入一些東西:我們把provider分成兩類:ContainterProvider和PackageProvider。你可以去看ConfigurationProvider接口,它就是繼承了這兩個接口的。
它們對外提供的功能主要就是從配置文件中加載對應的元素並轉換為JAVA對象,注冊到容器中。ContainterProvider是為了去加載容器配置元素:bean和constant等,這些元素要納入容器中管理。PackageProvider是為了去加載Package配置元素,裡面包含了 action,interceptor,result等運行時的事件映射節點,這些節點元素並不需要納入容器中管理。struts2初始化的核心就是對容器配置元素和事件映射元素這兩種不同元素的初始化過程,再進一步的講就是將以各種形式配置的這兩種元素轉換為JAVA對象並統一管理的過程。
上面的代碼應該不難分析,就是遍歷每個Doc,解析每個配置文件,對於bean元素,得到它的屬性,我們關注的是
containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);
這裡是把這種bean注冊到ContainerBuilder的factories中去,那為什麼是Builder呢?其實struts2在構造container時是采用了建造者模式的,它由builder來構造。builder.create(),當所有的bean注冊完成後,就開始構造容器並把這些map放入到容器中去。你可以先看下面的分析,再會過來看這部分。
我們接著看ContainerBuilder的factory的代碼。
public class LocatableFactoryextends Located implements Factory { private Class implementation; private Class type; private String name; private Scope scope; public LocatableFactory(String name, Class type, Class implementation, Scope scope, Object location) { this.implementation = implementation; this.type = type; this.name = name; this.scope = scope; setLocation(LocationUtils.getLocation(location)); } @SuppressWarnings("unchecked") public T create(Context context) { Object obj = context.getContainer().inject(implementation); return (T) obj; } //................
}
我們可以看出我們把bean的name,type,class等屬性傳入到LocatableFactory,當我們想要一個bean對象,那麼就可以從LocatableFactory中取出來。LocatableFactory.create(),這樣就可以得到我們的一個對象。從這裡可以看出在struts2容器中並不是存放bean對象,而是產生出bean對象的工廠。你後面從代碼也可以看出來。
publicContainerBuilder factory(final Class type, final String name, final Factory extends T> factory, Scope scope) { InternalFactory internalFactory = new InternalFactory () { public T create(InternalContext context) { try { Context externalContext = context.getExternalContext(); return factory.create(externalContext); } catch (Exception e) { throw new RuntimeException(e); } } //........................ return factory(Key.newInstance(type, name), internalFactory, scope); }
ContainerBuilder的factory中創建了一個內置factory:InternalFactory
我們接著看代碼:factory(Key.newInstance(type, name), internalFactory, scope);
從這行代碼,我們也可以看出:其實在struts2容器中的鍵是:type和 name的聯合主鍵。
privateContainerBuilder factory(final Key key, InternalFactory extends T> factory, Scope scope) { ensureNotCreated(); checkKey(key); final InternalFactory extends T> scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory); factories.put(key, scopedFactory); if (scope == Scope.SINGLETON) { singletonFactories.add(new InternalFactory () { public T create(InternalContext context) { try { context.setExternalContext(ExternalContext.newInstance( null, key, context.getContainerImpl())); return scopedFactory.create(context); } finally { context.setExternalContext(null); } } }); } return this; }
這裡是factory的一個重載方法。我們先來關注這兩行代碼:
final InternalFactory extends T> scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory); factories.put(key, scopedFactory);
我們看看Scope類:這是個枚舉類。裡面每種類型都實現了scopeFactory方法。
public enum Scope { DEFAULT { @OverrideInternalFactory extends T> scopeFactory(Class type, String name, InternalFactory extends T> factory) { return factory; } }, SINGLETON { @Override InternalFactory extends T> scopeFactory(Class type, String name, final InternalFactory extends T> factory) { return new InternalFactory () { T instance; public T create(InternalContext context) { synchronized (context.getContainer()) { if (instance == null) { instance = factory.create(context); } return instance; } } }; } }, THREAD { @Override InternalFactory extends T> scopeFactory(Class type, String name, final InternalFactory extends T> factory) { return new InternalFactory () { final ThreadLocal threadLocal = new ThreadLocal (); public T create(final InternalContext context) { T t = threadLocal.get(); if (t == null) { t = factory.create(context); threadLocal.set(t); } return t; } }; } }, REQUEST { @Override InternalFactory extends T> scopeFactory(final Class type, final String name, final InternalFactory extends T> factory) { return new InternalFactory () { public T create(InternalContext context) { Strategy strategy = context.getScopeStrategy(); try { return strategy.findInRequest( type, name, toCallable(context, factory)); } catch (Exception e) { throw new RuntimeException(e); } } @Override public String toString() { return factory.toString(); } }; } }, SESSION { InternalFactory extends T> scopeFactory(final Class type, final String name, final InternalFactory extends T> factory) { return new InternalFactory () { public T create(InternalContext context) { Strategy strategy = context.getScopeStrategy(); try { return strategy.findInSession( type, name, toCallable(context, factory)); } catch (Exception e) { throw new RuntimeException(e); } } @Override public String toString() { return factory.toString(); } }; } }, WIZARD { @Override InternalFactory extends T> scopeFactory(final Class type, final String name, final InternalFactory extends T> factory) { return new InternalFactory () { public T create(InternalContext context) { Strategy strategy = context.getScopeStrategy(); try { return strategy.findInWizard( type, name, toCallable(context, factory)); } catch (Exception e) { throw new RuntimeException(e); } } }; } };
abstractInternalFactory extends T> scopeFactory( Class type, String name, InternalFactory extends T> factory); }
我摘取了Scope的部分代碼。在Scope枚舉中聲明了一個抽象方法 scopeFactory ,所以每一個枚舉實例都實現了這個方法,它們各自實現了創建了不同生命周期的對象,其默認值為 singleton,即它是返回一個單例對象。Scope.DEFAULT 則是不做處理 直接返回 factory,這樣當調用create方法時候,每次都是創建一個新對象。
其實可以參照我的上篇文章如何使用struts2中提供的IOC進行測試。我測試過了,確實是這樣。
我們再返回factories.put(key, scopedFactory)。從這裡我們現在可以肯定的說是把factory注冊到builder中(我們先說是builder,其實後面會放入到container中)。不知不覺,我們已經把一個bean注冊到builder中去了(放入到它的factories這個map中)。不要忘了key是type和name的聯合主鍵。也許你早就不知道我們該返回哪行代碼,繼續分析啦。快哭了
仔細 回顧,我們要返回reloadContainer方法啦。在那裡我們從遍歷provider到provider的初始化到它的注冊一直分析到每個bean的注冊到builder中。我們接著往下分析:終於看到了這行代碼:
container = builder.create(false);
是不是很開心,這不就是構造container嘛。
public Container create(boolean loadSingletons) { ensureNotCreated(); created = true; final ContainerImpl container = new ContainerImpl( new HashMap, InternalFactory>>(factories)); if (loadSingletons) { container.callInContext(new ContainerImpl.ContextualCallable () { public Void call(InternalContext context) { for (InternalFactory> factory : singletonFactories) { factory.create(context); } return null; } }); } container.injectStatics(staticInjections); return container; }
class ContainerImpl implements Container { final Map, InternalFactory>> factories; final Map , Set > factoryNamesByType; ContainerImpl( Map , InternalFactory>> factories ) { this.factories = factories; Map , Set > map = new HashMap , Set >(); for ( Key> key : factories.keySet() ) { Set names = map.get(key.getType()); if (names == null) { names = new HashSet (); map.put(key.getType(), names); } names.add(key.getName()); } for ( Entry , Set > entry : map.entrySet() ) { entry.setValue(Collections.unmodifiableSet(entry.getValue())); } this.factoryNamesByType = Collections.unmodifiableMap(map); }
//..................
}
ContainerImpl是Container的一個實現。這個構造函數主要做3件事,1:得到builder的factories。2:為 Key(type,name) --- InternalFactory的 Map實例字段 賦值,其來源就是 ContainerBuilder中的factories.3:將 type 和 name 的一對多關系保存在 Map實例字段 factoryNamesByType 中。 把對象生命周期為單實例的對象先創建出來,其中if語句調用回調函數,將屬於單例模式的bean事先調用create方法。singletonFactories變量中存放的是容器中屬於單例模式的工廠的引用。這樣一個完整的struts2容器就生成了。
我們這裡目前只是生成了Container對象,還沒有生成PackageConfig對象啊。接下來就是這部分了。(還是在reloadContainer方法中。緊隨container = builder.create(false)之後。)。
/ Process the configuration providers first 先得到providers中實現了PackageProvider的provider 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來源於插件中直接實現PackageProvider接口的類 SetpackageProviderNames = container.getInstanceNames(PackageProvider.class); for (String name : packageProviderNames) { PackageProvider provider = container.getInstance(PackageProvider.class, name); provider.init(this); provider.loadPackages(); packageProviders.add(provider); }
你還記得PackageProvider和ContainerProvider嗎?只有得到了PackageProvider才能去加載package配置信息。因為這之前我們已經創建出了container,所以可以用注入:container.inject(containerProvider)。然後loadPackages加載package。這樣就完成了整個PackageConfig對象的生成。
struts2的兩類配置元素Container和PackageConfig 已經初始化完畢了。其中Container提供了IOC機制。struts2的啟動過程也完成了。看了幾天的代碼,終於有點眉目了。。接下來會分析下struts2的處理請求的過程。