struct2源码解读(4)之配置文件具体解析过程
从上篇博文我们探讨过了struct2解析配置文件的简单流程,对于具体的解析过程还没做具体深入的解析,下面就我们探讨下struct2是如何解析配置文件的。
public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
//清空解析对象,解析package标签会把属性封装到PackageConfig,然后把PackageConfig放到packageContexts这个map中
packageContexts.clear();
loadedFileNames.clear();
List<PackageProvider> packageProviders = new ArrayList<PackageProvider>();
//保存properties属性的对象
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对象,这里用到两个工厂模式
container = builder.create(false);
setContext(container);
objectFactory = container.getInstance(ObjectFactory.class);
// 解析package 标签
for (final ContainerProvider containerProvider : providers)
{
if (containerProvider instanceof PackageProvider) {
container.inject(containerProvider);
((PackageProvider)containerProvider).loadPackages();
packageProviders.add((PackageProvider)containerProvider);
}
}
// 解析插件的package标签
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;
}从上面我们大致了解了解析配置文件到底做了什么事情,下面就让我们详细探讨。
一、清空存放配置信息的对象
packageContexts是一个map集合
protected Map<String, PackageConfig> packageContexts = new LinkedHashMap<String, PackageConfig>();
这个map存放的是packageConfig对象,而这个packageConfig对象封装了package标签的属性,一个xml文件可能会有几个package标签。我们来看下这个packageConfig对象
private String name; private String namespace = ""; private boolean isAbstract = false;
再来看看struct.xml配置文件中的package标签
<package name="forword" namespace="/" extends="struts-default">
有没有发现,这其实就是把package标签的属性,封装到了一个对象中。解析其他标签也是这个原理,只是封装的对象不一样而儿。
这里调用了map.clear()方法,先清空这个map,确保每次存放的都是解析的对象。
二、实例化存放配置信息的对象
package标签的属性封装到了packageConfig对象,而其他的属性封装到了ContainerProperties和ContainerBuilder对象中,如property中这些有key-value值的属性,封装到了ContainerProperties对象,bean就封装到了ContainerBuilder对象中。这里用到了工厂的模式。工厂的英文是factory,factory是一个接口
public interface Factory<T> {
T create(Context context) throws Exception;
}调用这个接口的create方法就可以返回这个对象实例。把bean属性封装到ContainerBuilder这个对象中,然后掉用create方法,就会返回这个bean实例了,这个具体原理我们下面再分析。
三、循环遍历解析配置文件
for (final ContainerProvider containerProvider : providers)
{
containerProvider.init(this);
containerProvider.register(builder, props);
}上篇博文我们分析到,我们已经实例化解析每个配置文件的对象,并把这些对象添加了一个list集合中,然后通过getContainerProviders()方法,就可以获得这个集合。
private List<ContainerProvider> containerProviders = new CopyOnWriteArrayList<ContainerProvider>();
public List<ContainerProvider> getContainerProviders() {
providerLock.lock();
try {
if (containerProviders.size() == 0) {
//如果为空,默认给出2个
containerProviders.add(new XWorkConfigurationProvider());
containerProviders.add(new XmlConfigurationProvider("xwork.xml", false));
}
//返回这个集合
return containerProviders;
} finally {
providerLock.unlock();
}
}通过循环遍历这个集合里面的每个解析器,就可以解析每个配置文件了。这里具2个例子,分析下原理,其他的大家可以自己去看下。
3.1.解析default.properties文件
3.1.1.原理
default.properties这个文件配置了struct2默认的运行信息。如我们在structs.xml中配置了
代码清单:structs.xml <!-- 配置为开发模式 --> <constant name="struts.devMode" value="true" />
把struts.devMode设为true,在开发的时候,我们改动structs.xml,就不用重启tomcat了。而这个struts.devMode就是在default.properties文件中设置的。
代码清单:default.properties struts.devMode = false ### when set to true, resource bundles will be reloaded on _every_ request. ### this is good during development, but should never be used in production struts.i18n.reload=false
strcut2默认设置是false的,在解析default.properties时会把这个属性以(struts.devMode,false)保存到property对象中,到后面解析structs.xml的constant时,就会判断property这个对象是否有struts.devMode这个key,有的话,就会设置成constant中设置的值。具体看下面分析。
3.1.2.解析过程
我们找到DefaultPropertiesProvider这个解析器
public class DefaultPropertiesProvider extends LegacyPropertiesConfigurationProvider {
public void destroy() {
}
//init()方法没有做任何事情
public void init(Configuration configuration) throws ConfigurationException {
}
//解析
public void register(ContainerBuilder builder, LocatableProperties props)
throws ConfigurationException {
Settings defaultSettings = null;
try {
//1.加载并解析属性文件org/apache/struts2/default.properties
defaultSettings = new PropertiesSettings("org/apache/struts2/default");
}
//异常信息
//2.封装配置信息到ContainerProperties对象
loadSettings(props, defaultSettings);
}
}(1)加载属性文件
public PropertiesSettings(String name) {
//获取资源地址
URL settingsUrl = ClassLoaderUtils.getResource(name + ".properties", getClass());
if (settingsUrl == null) {
LOG.debug(name + ".properties missing");
settings = new LocatableProperties();
return;
}
// 创建一个propertis对象
settings = new LocatableProperties(new LocationImpl(null, settingsUrl.toString()));
// 解析配置文件
InputStream in = null;
try {
//打开文件流
in = settingsUrl.openStream();
//解析property文件
settings.load(in);
} catch (IOException e) {
throw new StrutsException("Could not load " + name + ".properties:" + e, e);
} finally {
if(in != null) {
try {
in.close();
} catch(IOException io) {
LOG.warn("Unable to close input stream", io);
}
}
}
}(2)具体解析过程,把配置信息封装到setting对象
public void load(InputStream in) throws IOException {
//property文件解析器
Reader reader = new InputStreamReader(in);
//读取property文件
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 = new LocationImpl(desc, location.getURI(), line, 0);
//以key-value形式保存配置信息到property对象中
setProperty(name, val, loc);
}
}(3)setting对象封装到ContainerProperties对象
protected void loadSettings(LocatableProperties props, final Settings settings) {
//循环遍历解析出来的配置信息
for (Iterator i = settings.listImpl(); i.hasNext(); ) {
String name = (String) i.next();
props.setProperty(name, settings.getImpl(name), settings.getLocationImpl(name));
}
}3.2.解析struct*.xml文件
struct2设计是通过StrutsXmlConfigurationProvider这个对象解析struct*.xml文件的,我们找到StrutsXmlConfigurationProvider的register()方法。
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);
}我们这里看到,无论是init()还是register(),StrutsXmlConfigurationProvider都是调用父类的方法进行解析,而StrutsXmlConfigurationProvider的父类是XmlConfigurationProvider。
3.2.1.init()方法
public void init(Configuration configuration) {
this.configuration = configuration;
this.includedFileNames = configuration.getLoadedFileNames();
//获得*.xml文档
loadDocuments(configFileName);
}这个configFileName是在实例化这个解析器时传进的file值,默认是struts-default.xml,struts-plugin.xml,struts.xml中的一个。特别注意,这里循环遍历,每个文件都有一个解析器,这里重点解析struts-default.xml的解析过程。
String[] files = configPaths.split("\\s*[,]\\s*");
//循环遍历struts-default.xml,struts-plugin.xml,struts.xml
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");
}
}我们来看下loadDocuments这个方法
private void loadDocuments(String configFileName) {
try {
loadedFileUrls.clear();
//获得document对象
documents = loadConfigurationFiles(configFileName, null);
}
//异常处理
}从这里我们可以看出,xml的解析器的init()方法主要是获取配置文件的document对象,通过文件流读取这个xml文件,然后用xml的解析器解析这个文件流,最终得到一个document对象,这里用到了dom解析,所以这里获得的是dom的document对象。具体的看xml的知识点,因篇幅问题,这里就不作过多的解析了,大家知道这里用dom技术解析xml文件获得一个document对象,这个document对象包含了xml文件的所有信息就可以了。
3.2.2.register()方法
这个方法就是解析上面的document对象,也就是struct*.xml文件,把里面的属性进行封装,我们先来看下struct*.xml文件有什么属性,再来看下struct2是怎么解析这个文件的
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <bean class="com.opensymphony.xwork2.ObjectFactory" name="xwork" /> <!-- 配置为开发模式 --> <constant name="struts.devMode" value="true" /> <!-- 把扩展名配置为action --> <constant name="struts.action.extension" value="action" /> <!-- 把主题配置为simple --> <constant name="struts.ui.theme" value="simple" /> <include file="../PlayWellstruts.xml"></include> <!-- Add packages here --> <package name=""> </package> </struts>
这个xml文件,根节点是<struct2>,我们在开发的时候,主要都是配置<package>里面的东东,比较复杂,所以structs2设计对<contant>阿,<bean>啊,这些相对“固定”的,用register()方法解析,对于<package>这个比较复杂的另外用了一个方法(loadpackage())单独解析,我们先来看下register这个方法。
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
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();
//获取bean节点信息
if ("bean".equals(nodeName)) {
//得到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");
boolean optional = "true".equals(child.getAttribute("optional"));
Scope scope = Scope.SINGLETON;//默认是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;
}
//如果name属性不填,默认为default
if (StringUtils.isEmpty(name)) {
name = Container.DEFAULT_NAME;
}
//封装 bean配置信息到containerBuilder对象
try {
}
} else if ("constant".equals(nodeName)) {
//解析<constant>标签,把属性值保存到containerProperty对象中,注意因为default.properties的属性也保存到这个对象,因此这里如果发现key相同,会替换原来的值
String name = child.getAttribute("name");
String value = child.getAttribute("value");
props.setProperty(name, value, childNode);
} else if (nodeName.equals("unknown-handler-stack")) {
//解析unknown-handler-stack
}
}
}
}
}这个封装bean用了factory方法,下篇博文我单独解析下,这里大家先有个印象。
四、解析package
for (final ContainerProvider containerProvider : providers)
{
if (containerProvider instanceof PackageProvider) {
container.inject(containerProvider);
//loadPackages()方法解析package
((PackageProvider)containerProvider).loadPackages();
packageProviders.add((PackageProvider)containerProvider);
}
}无论是解析配置文件的package,还是解析其他package,这里都用到了loadPackage()方法,对于这个方法我也会新开一个博文详细解析。
五、封装配置对象
protected synchronized RuntimeConfiguration buildRuntimeConfiguration() throws ConfigurationException {
//ActionConfig
Map<String, Map<String, ActionConfig>> namespaceActionConfigs = new LinkedHashMap<String, Map<String, ActionConfig>>();
Map<String, String> namespaceConfigs = new LinkedHashMap<String, String>();
//packageConfig
for (PackageConfig packageConfig : packageContexts.values()) {
}
//返回RuntimeConfigurationImpl
return new RuntimeConfigurationImpl(namespaceActionConfigs, namespaceConfigs);
}通过上面解析,把不通的配置信息封装到了不同的对象中,如package标签的属性封装到了packageConfig,把action标签的属性封装到了actionConfig中等等,这里所说的封装是指,把配置文件中的key-value值设置到对象的property值中,以对象来管理这些属性。在解析配置文件的过程中,这些对象都是相对独立的,为了统一起来,这里把分散的对象都又进一步封装到了RuntimeConfiguration对象中,通过一个对象来管理这些分散的对象,方便以后处理aciton请求时调用。具体过程我也会单开一篇博文解析。
六、总结
通过上面分析,我们大致了解了struct2是如何解析配置文件的:通过不同文件的解析器解析不同的配置文件,如DefaulatPropertyProvider解析properties文件,xmlConfigurateProvide解析xml文件等等。通过调用这些解析器的init()和register()或者是loadpackage()方法把配置文件中的key-value信息封装到不同的对象中,如如package标签的属性封装到了packageConfig;把action标签的属性封装到了actionConfig中;把常量信息封装到containerPorperty对象;把类信息以factory的方法封装到containerBuilder对象中等等,最后再把这些对象进一步封装到RuntimeConfiguration中,方便以后处理action请求时调用,从而完成了初始化。
下三篇博文将对上面未解决的问题进行一一详解:
①封装bean标签信息。
②封装package标签信息。
③封装配置信息对象。
本文出自 “总有贱人想害朕。” 博客,请务必保留此出处http://yoyanda.blog.51cto.com/9675421/1709930
原文:http://yoyanda.blog.51cto.com/9675421/1709930