现在的位置: 首页 > 综合 > 正文

Struts2 启动分析

2013年12月06日 ⁄ 综合 ⁄ 共 9457字 ⁄ 字号 评论关闭
文章目录

进入struts2核心类的初始化

edited By Bruce Bob

首先,分析一个web应用程序,基本应该从web.xml开始入手。我们这里构建了一个最简单的web应用程序。

<filter>

       <filter-name>struts-execute</filter-name>

       <filter-class>

       org.apache.struts2.dispatcher.FilterDispatcher

       </filter-class>

    </filter>

    <filter-mapping>

       <filter-name>struts-execute</filter-name>

       <url-pattern>/*</url-pattern>

    </filter-mapping>

 

可以看到,这里的配置非常简单,就是将交由Struts2处理的请求通过一个filter过滤器交由struts2处理。

所以,我们就从FilterDispatcher开始入手进行分析struts的行为。

我们知道,filter的启动时会进行init()进行初始化。

这里的初始化相对简单,就是将filter的config对象传递进来。然后进行log的初始化。这里不是分析的重点,跳过。下面是重点。在这个filter中有一个dispatcher的属性,该类是处理struts请求的核心类。而FilterDispatcher只是他的一个代理而已。

 

try {

            this.filterConfig =filterConfig;

 

            initLogging();

 

            dispatcher =createDispatcher(filterConfig);

            dispatcher.init();

            //这里是对filterDispatcher进行了注入(actionMapper)

           dispatcher.getContainer().inject(this);

 

staticResourceLoader.setHostConfig(new FilterHostConfig(filterConfig));

        } finally {

           ActionContext.setContext(null);

        }

核心类Dispatcher构建configProvider

既然 Dispatcher才是struts2的核心类,那么我们就将分析的重点转向dispatcher类。看一下他是如何进行初始化的。他的init函数也就是初始化函数如下

init_DefaultProperties(); // [1]

            init_TraditionalXmlConfigurations();
// [2]

            init_LegacyStrutsProperties(); // [3]

           init_CustomConfigurationProviders(); // [5]

            init_FilterInitParameters() ; // [6]

            init_AliasStandardObjects() ; // [7]

            //将本实例注入到IOC容器当中

            Container container = init_PreloadConfiguration();

            container.inject(this);

           init_CheckConfigurationReloading(container);

            init_CheckWebLogicWorkaround(container);

 

            if (!dispatcherListeners.isEmpty()) {

                for (DispatcherListener l :
dispatcherListeners) {

                    l.dispatcherInitialized(this);

                }

            }

我们下面将会对这几个init*的方法进行详细的分析。

init_DefaultProperties()

代码比较简单,该部分主要是将struts的properties文件进行加载。

configurationManager.addConfigurationProvider(newDefaultPropertiesProvider());

 

因为是第一个初始化分析,我们将会分析的相对详细一些,后面的几个配置文件的初始化,我们将会相对粗略的进行分析。

ConfigurationManager是struts中配置的核心类。在struts2中,所有的配置都是和他相关的。在该方法中,只是简单都建了一个DefaultPropertiesProvider并通过调用ConfigurationManager .addConfigurationProvider方法,将其注册到了ConfigurationManager中。下图给出了DefaultPropertiesProvider类的集成关系。

 

init_TraditionalXmlConfigurations

该方法主要是加载struts2相关的xml配置文件

String configPaths = initParams.get("config");

        if (configPaths ==
null) {

            configPaths = DEFAULT_CONFIGURATION_PATHS;

        }

        String[] files = configPaths.split("\\s*[,]\\s*");

        for (String file : files) {

        //xwork 和其他的工作方式不一样啊

            if (file.endsWith(".xml")) {

                if ("xwork.xml".equals(file)) {

                    configurationManager.addConfigurationProvider(createXmlConfigurationProvider(file,false));

                } else {

                    configurationManager.addConfigurationProvider(createStrutsXmlConfigurationProvider(file,false,
servletContext));

                }

            } else {

                throw
new
IllegalArgumentException("Invalid configuration file name");

            }

       }

如果在web.xml中没有配置config参数,则会加载DEFAULT_CONFIGURATION_PATHS对应的配置文件,也就是下面的三个文件:struts-default.xml,struts-plugin.xml,struts.xml

另外,需要注意的是,因为struts2是xwork的升级版,自然兼容了xwork的很多特性,所以,如果在config配置文件中使用的参数是“xwork.xml”那么,加载的configurationProvider的方式和struts2的方式是不一样的,这个区别具体的以后会详细说明。

 

init_LegacyStrutsProperties

该方法主要是构建一个LegacyPropertiesConfigurationProvider对象加入到configmanager中。

加载配置文件

好了,主要的几个configprovider都已经加载到了configmanager中了,应该开始启动加载配置文件了。下面就是对配置文件的加载。

Container container = init_PreloadConfiguration();

            container.inject(this);

           init_CheckConfigurationReloading(container);

        init_CheckWebLogicWorkaround(container);

 

这里先是构建了一个Configuration 对象并调用Configuration的reloadContainer方法,将所有的配置装载器(configProvider)进行加载对应的配置文件。

packageContexts.clear();

        loadedFileNames.clear();

        List<PackageProvider>packageProviders = new ArrayList<PackageProvider>();

 

        ContainerProperties props = newContainerProperties();

        ContainerBuilder builder = newContainerBuilder();

        for (final ContainerProvidercontainerProvider : providers)

        {

        //注意,这里调用各个providerinitregister方法

            containerProvider.init(this);

            containerProvider.register(builder,props);

     }

这里主要是遍历所有的providers 并分别调用他们的init方法和register方法。下面,我们将选择两个有代表性的加载default.propertities 文件和加载struts.xml相应文件的configProvider进行这两个方法的分析。

 

加载default.properties属性文件

先来分析DefaultPropertiesProvider 中的init和register 方法。Init方法没什么内容,我们就来分析下register方法。

Settings defaultSettings = null;

        try {

            defaultSettings = new
PropertiesSettings("org/apache/struts2/default");

        } catch (Exception e) {

            throw
new
ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e);

        }

       

       loadSettings(props, defaultSettings);

这里先给出setting的类继承关系

这里默认加载的就是org/apache/struts2/default.properties文件。这里简单说明一下加载properties文件的过程吧。

这里根据之前传入的文件路径名称通过ClassLoaderUtils加载成一个URL对象。ClassLoaderUtils顾名思义,是一个类和资源的加载器。加载完毕之后,openStream打开一个流,然后读取文件内容。

 

URLsettingsUrl = ClassLoaderUtils.getResource(name +
".properties"
, getClass());

        settings = newLocatableProperties(new LocationImpl(null,
settingsUrl.toString()));

 

        // Load settings

        InputStream in = null;

        try {

            in = settingsUrl.openStream();

            settings.load(in);

        }

注意,这里读取资源文件是PropertiesReader 来读取属性文件的。这在以后的开发框架中,应该注意重用。使用起来非常方便,struts中使用如下:

Reader reader = new InputStreamReader(in);

        PropertiesReader pr = new
PropertiesReader(reader);

        while (pr.nextProperty()) {

            String name = pr.getPropertyName();

            String val = pr.getPropertyValue();

            int line = pr.getLineNumber();

            String desc =convertCommentsToString(pr.getCommentLines());

           

            Location loc = newLocationImpl(desc,
location.getURI(), line, 0);

            setProperty(name, val, loc);

        }

加载完毕之后,通过loadSettings(props,defaultSettings);将加载后的defaultSettings 复制到了props中。

这样,就在DefaultConfiguration中reloadContainer 方法中,加载了所有的属性文件中定义的属性。

 

加载struts.xml配置文件

加载xml配置文件就比属性文件复杂的多了。

在Dispatcher 的初始化中,我们知道,如果没有在web.xml中配置config属性,则默认加载struts-default.xml,struts-plugin.xml,struts.xml 这三个配置文件。而通过这三个配置文件进行构建的configProvider都是StrutsXmlConfigurationProvider类型的。那么,我们就来分析下StrutsXmlConfigurationProvider 的init和register函数。

我们这里重点分析下struts.xml文件的加载过程。

 

url = urls.next();

                    is = FileManager.loadFile(url);

                    InputSource in = newInputSource(is);

                   in.setSystemId(url.toString());

                docs.add(DomHelper.parse(in,
dtdMappings));

这先是通过FileManager加载URL成为一个InputSource(org.xml.sax.InputSource 用于XML解析的)。然后通过DomHelper将InputSource解析成为Document对象。这里的操作都相对简单,不多解释。

            //根据"order"属性为这些doc排序,

            Collections.sort(docs,
newComparator<Document>() {

                public
int
compare(Document doc1, Document doc2) {

                    return XmlHelper.getLoadOrder(doc1).compareTo(XmlHelper.getLoadOrder(doc2));

                }

            });

 

这里简单将排序说明一下:Collections的sort方法有两个版本,一个是

Collections.sort(List<T> list)

Collections .sort(List<T> list,
Comparator<? super T> c)

第一个方法中,list的元素必须是实现了Comparable接口的对象。

在第二个方法中,可以不用实现,但是需要给出比较的方法。

解析得到所有的doc之后,就是对document进行进一步的解析了。遍历所有的docs。这里主要是判断doc中是否存在“include”节点,如果存在,则还需要进一步进行加载。这里将所有的xml文件最终形成doc对象之后就完事儿了。方法返回加载所有xml文件之后解析的doc对象。

           

            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 StringnodeName = child.getNodeName();

 

                        if ("include".equals(nodeName)) {

                            String includeFileName= child.getAttribute("file");

                            //对于存在通配符和不存在通配符的分开处理

                            if(includeFileName.indexOf('*') != -1) {

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

           }

 

到这里,init方法就分析完了。

下面我们看一下register方法。方法里主要是对bean和constant进行解析。

解析bean的时候,需要注意一下几个参数:

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

                        "true".equals(child.getAttribute("optional"));

 

class:这个属性是个必填属性,它指定了Bean实例的实现类。

type:这个属性是个可选属性,它指定了Bean实例实现的Struts2的规范,该规范通常是通过某个接口或者在此前定义过的Bean,因此该属性值通常是个接口或者此前定义过的Bean的name属性值。如果需要将Bean的实例作为Strut2组件使用,则应该指定该属性的值。

name:该属性是个可选属性,它指定的Bean实例的名字,对于有相同type的多个Bean。则它们的name属性不能相同。

scope:该属性是个可选属性,它指定Bean实例的作用域,该属性的值只能是default、singleton、request、session或thread之一。

static:该属性是个可选属性,它指定Bean是否使用静态方法注入。通常而言,当指定了type属性时,该属性就不应该指定为true。

optional:该属性是个可选属性,它指定Bean是否是一个可选Bean。

 

抱歉!评论已关闭.