struts2源码分析之初始化(1)
2013-02-22 15:53 阅读(182)

本次解析使用的版本是: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篇中我在接过来。因为如果不说容器,好多东西还是很难理解的。本篇完毕。