struts2启动过程分析

来源:互联网 发布:流量使用情况软件 编辑:程序博客网 时间:2024/05/19 15:24

我们把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 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;    }


你会看到遍历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 List<Document> loadConfigurationFiles(String fileName, Element includeElement) {        List<Document> docs = new ArrayList<Document>();        List<Document> finalDocs = new ArrayList<Document>();        if (!includedFileNames.contains(fileName)) {            if (LOG.isDebugEnabled()) {                LOG.debug("Loading action configurations from: " + fileName);            }            includedFileNames.add(fileName);            Iterator<URL> 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<Document>() {                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<String> 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<ServletContext>() {                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 + "]");        }        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 (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<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);                    }                }            }        }    }


我们先在这里插入一些东西:我们把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 LocatableFactory<T> extends Located implements Factory<T> {    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对象的工厂。你后面从代码也可以看出来。

public <T> ContainerBuilder factory(final Class<T> type, final String name,      final Factory<? extends T> factory, Scope scope) {    InternalFactory<T> internalFactory =        new InternalFactory<T>() {      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<T> internalFactory。这是struts2容器内部使用的Factory,而不是我们开发者使用的。很明显这些对我们传入的factory进行的封装。

我们接着看代码:factory(Key.newInstance(type, name), internalFactory, scope);

从这行代码,我们也可以看出:其实在struts2容器中的键是:type和 name的联合主键。

private <T> ContainerBuilder factory(final Key<T> 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<T>() {        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 {    @Override            <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,        InternalFactory<? extends T> factory) {      return factory;    }  },  SINGLETON {    @Override            <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,        final InternalFactory<? extends T> factory) {      return new InternalFactory<T>() {        T instance;        public T create(InternalContext context) {          synchronized (context.getContainer()) {            if (instance == null) {              instance = factory.create(context);            }            return instance;          }        }      };    }  },   THREAD {    @Override            <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,        final InternalFactory<? extends T> factory) {      return new InternalFactory<T>() {        final ThreadLocal<T> threadLocal = new ThreadLocal<T>();        public T create(final InternalContext context) {          T t = threadLocal.get();          if (t == null) {            t = factory.create(context);            threadLocal.set(t);          }          return t;        }      };    }  },  REQUEST {    @Override            <T> InternalFactory<? extends T> scopeFactory(final Class<T> type,        final String name, final InternalFactory<? extends T> factory) {      return new InternalFactory<T>() {        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 {                <T> InternalFactory<? extends T> scopeFactory(final Class<T> type,        final String name, final InternalFactory<? extends T> factory) {      return new InternalFactory<T>() {        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            <T> InternalFactory<? extends T> scopeFactory(final Class<T> type,        final String name, final InternalFactory<? extends T> factory) {      return new InternalFactory<T>() {        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);          }        }             };    }  };
abstract <T> InternalFactory<? extends T> scopeFactory(      Class<T> 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<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;  }
 
class ContainerImpl implements Container {final Map<Key<?>, InternalFactory<?>> factories;final Map<Class<?>, Set<String>> factoryNamesByType;ContainerImpl( Map<Key<?>, InternalFactory<?>> factories ) {this.factories = factories;Map<Class<?>, Set<String>> map = new HashMap<Class<?>, Set<String>>();for ( Key<?> key : factories.keySet() ) {Set<String> names = map.get(key.getType());if (names == null) {names = new HashSet<String>();map.put(key.getType(), names);}names.add(key.getName());}for ( Entry<Class<?>, Set<String>> 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接口的类            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);            }

 

你还记得PackageProvider和ContainerProvider吗?只有得到了PackageProvider才能去加载package配置信息。因为这之前我们已经创建出了container,所以可以用注入:container.inject(containerProvider)。然后loadPackages加载package。这样就完成了整个PackageConfig对象的生成。

struts2的两类配置元素Container和PackageConfig 已经初始化完毕了。其中Container提供了IOC机制。struts2的启动过程也完成了。看了几天的代码,终于有点眉目了。奋斗。接下来会分析下struts2的处理请求的过程。