进入struts2核心类的初始化
首先,分析一个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)
{
//注意,这里调用各个provider的init和register方法
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。