首先强调一下struts2的线程程安全,在Struts2中大量采用ThreadLocal线程局部变量的方法来保证线程的安全,像Dispatcher等都是通过ThreadLocal来保存变量值,使得每个线程都有自己独立的实例变量,互不相干。
接下来就从Dispatcher开始看起,先看其构造函数:
//创建Dispatcher,此类是一个Delegate,它是真正完成根据url解析转向,读取对应Action的地方publicDispatcher(ServletContext servletContext, Map<String, String> initParams) {this.servletContext = servletContext;//配置在web.xml中的param参数this.initParams = initParams;
}
我们再看在FilterDispatcher创建Dispatcher的:
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);
}//都可以从FilterConfig中得到returnnew Dispatcher(filterConfig.getServletContext(),params);
}
创建Dispatcher之后,来看init()方法,init()方法是用来Load用户配置文件,资源文件以及默认的配置文件,主要分七步走,看下面注释:
publicvoid init() {if (configurationManager ==null) {//设置ConfigurationManager的defaultFrameworkBeanName.//这里DEFAULT_BEAN_NAME为struts,这是xwork框架的内容,Framework可以是xwork,struts,webwork等
configurationManager =new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
}//读取properties信息,默认的default.properties,
init_DefaultProperties();// [1]//读取xml配置文件
init_TraditionalXmlConfigurations();// [2]//读取用户自定义的struts.properties
init_LegacyStrutsProperties();// [3]//自定义的configProviders
init_CustomConfigurationProviders();// [5]//载入FilterDispatcher传进来的initParams
init_FilterInitParameters() ;// [6]//将配置文件中的bean与具体的类映射
init_AliasStandardObjects() ;// [7]//构建一个用于依赖注射的Container对象//在这里面会循环调用上面七个ConfigurationProvider的register方法//其中的重点就是DefaultConfiguration的#reload()方法
Containercontainer = init_PreloadConfiguration();container.inject(this);
init_CheckConfigurationReloading(container);
init_CheckWebLogicWorkaround(container);if (!dispatcherListeners.isEmpty()) {for (DispatcherListener l : dispatcherListeners) {
l.dispatcherInitialized(this);
}
}
}
分七步载入各种配置属性,都是通过ConfigurationProvider接口进行的,这个接口提供init(),destroy(),register()等方法.
将各种ConfigurationProvider初始化之后将实例添加到ConfigurationManager的List里面.最后通过循环调用List里的这些destroy(),register()等方法实现对配置文件的属性进行注册和销毁等功能.
下面将分析分析7步流程:
- 1:首先是init_DefaultProperties()
privatevoidinit_DefaultProperties() {
configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());
}//接来看DefaultPropertiesProvider好了,DefaultPropertiesProvider实际上只是实现了register()方法publicvoidregister(ContainerBuilder builder, LocatableProperties props)throws ConfigurationException {
Settings defaultSettings =null;try {
defaultSettings =new PropertiesSettings("org/apache/struts2/default");
}catch (Exception e) {thrownew ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e);
}
loadSettings(props, defaultSettings);
}
//PropertiesSettings构造方法//读取org/apache/struts2/default.properties的配置信息,如果项目中需要覆盖,可以在classpath里的struts.properties里覆写publicPropertiesSettings(String name) {
URL settingsUrl = ClassLoaderUtils.getResource(name +".properties", getClass());if (settingsUrl ==null) {
LOG.debug(name +".properties missing");
settings =new LocatableProperties();return;
}
settings =new LocatableProperties(new LocationImpl(null, settingsUrl.toString()));// Load settings
InputStreamin =null;try {in = settingsUrl.openStream();
settings.load(in);
}catch (IOException e) {thrownew 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);
}
}
}
}//loadSettings主要是将progerty的value和Locale从上面PropertiesSettings中取得并存放到LocatableProperties props//这个props是register的一个入参.protectedvoidloadSettings(LocatableProperties props, final Settings settings) {// We are calling the impl methods to get around the single instance of Settings that is expectedfor (Iterator i = settings.listImpl(); i.hasNext(); ) {
String name = (String) i.next();
props.setProperty(name, settings.getImpl(name), settings.getLocationImpl(name));
}
}
- 2:再来看第二步:init_TraditionalXmlConfigurations()
private void init_TraditionalXmlConfigurations() { //首先读取web.xml中的config初始参数值 //如果没有配置就使用默认的DEFAULT_CONFIGURATION_PATHS:"struts-default.xml,struts-plugin.xml,struts.xml", //这儿就可以看出为什么默认的配置文件必须取名为这三个名称了 //如果不想使用默认的名称,直接在web.xml中配置config初始参数即可
String configPaths = initParams.get("config");if (configPaths ==null) {
configPaths = DEFAULT_CONFIGURATION_PATHS;
}
String[]files = configPaths.split("\\s*[,]\\s*");for (Stringfile :files) {if (file.endsWith(".xml")) {if ("xwork.xml".equals(file)) { //XmlConfigurationProvider负责解析xwork.xml
configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file,false));
}else { //其它xml都是由StrutsXmlConfigurationProvider来解析
configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file,false, servletContext));
}
}else {
thrownew IllegalArgumentException("Invalid configuration file name");
}
}
}
对于其它配置文件只用StrutsXmlConfigurationProvider,此类继承XmlConfigurationProvider,而XmlConfigurationProvider又实现ConfigurationProvider接口。
类XmlConfigurationProvider负责配置文件的读取和解析,
首先通过init()中的loadDocuments(configFileName);
利用DomHelper中的 public static Document parse(InputSource inputSource,Map<String,String> dtdMappings) 将configFileName配置文件通过SAX解析方式按照DtdMappings解析成Document对象.
然后通过Provider的register()方法加载"bean"和"constant"属性,
再通过loadPackages()加载package及package中的属性
addAction()方法负责读取<action>标签,并将数据保存在ActionConfig中;addResultTypes()方法负责将<result-type>标签转化为ResultTypeConfig对象;loadInterceptors()方法负责将<interceptor>标签转化为InterceptorConfi对象;loadInterceptorStack()方法负责将<interceptor-ref>标签转化为InterceptorStackConfig对象;loadInterceptorStacks()方法负责将<interceptor-stack>标签转化成InterceptorStackConfig对象。
而上面的方法最终会被addPackage()方法调用,addPackage又会被Provider的loadPackages()调用,将所读取到的数据汇集到PackageConfig对象中。
protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {
PackageConfig.Builder newPackage = buildPackageContext(packageElement);if (newPackage.isNeedsRefresh()) {return newPackage.build();
}
// add result types (and default result) to this package
addResultTypes(newPackage, packageElement);
// load the interceptors and interceptor stacksfor this package
loadInterceptors(newPackage, packageElement);
// load the default interceptor referencefor this package
loadDefaultInterceptorRef(newPackage, packageElement);
// load the default class reffor this package
loadDefaultClassRef(newPackage, packageElement);
// load the global result listfor this package
loadGlobalResults(newPackage, packageElement);
// load the global exception handler listfor 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 referencefor this package
loadDefaultActionRef(newPackage, packageElement);
PackageConfig cfg = newPackage.build();
configuration.addPackageConfig(cfg.getName(), cfg);return cfg;
}
loadConfigurationFiles解析读取xml中的内容
private List<Document> loadConfigurationFiles(String fileName, Element includeElement) {...
//通过DomHelper调用SAX进行解析xml
doc = DomHelper.parse(in, dtdMappings);...
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");
//解析每个action配置,对于include文件可以使用通配符*来进行配置
//如Struts.xml中可配置成<include file="actions_*.xml"/>if (includeFileName.indexOf('*') != -1) {
ClassPathFinder wildcardFinder = new ClassPathFinder();
wildcardFinder.setPattern(includeFileName);
Vector<String> wildcardMatches = wildcardFinder.findMatches();for (String match : wildcardMatches) {
//递归Load子file中的<include/>
docs.addAll(loadConfigurationFiles(match, child));
}
}else {
docs.addAll(loadConfigurationFiles(includeFileName, child));
}
}
}
}
docs.add(doc);
loadedFileUrls.add(url.toString());...return docs;
}
- 3:接下来第三步:
init_LegacyStrutsProperties()调用的是LegacyPropertiesConfigurationProvider
通过比较前面DefaultPropertiesProvider也调用的是LegacyPropertiesConfigurationProvider,发现DefaultPropertiesProvider继承自后者,但重写了register()方法,
主要是生成PropertiesSetting的不同,前者是根据org/apache/struts2/default.properties,后者是根据struts.properties
我们展开register()中的Settings.getInstance(),最后是调用getDefaultInstance()
privatestatic SettingsgetDefaultInstance() {if (defaultImpl ==null) {// Create bootstrap implementation//不带参数的DefaultSettings(),区别与DefaultPropertiesProvider中直接带default.properties参数//不带参数就是默认为struts.propertes,并且加载struts.custom.properties所定义的properties文件
defaultImpl =new DefaultSettings();// Create default implementationtry {//STRUTS_CONFIGURATION为:struts.configuration//在struts.proterties中查找struts.configuration的值,这个值必须是org.apache.struts2.config.Configuration接口的实现类//所以我有个困惑就是在下面的转换当中怎么将Configuration转换成Setting类型的...//这一点先放下了,有时间再研究
String className =get(StrutsConstants.STRUTS_CONFIGURATION);if (!className.equals(defaultImpl.getClass().getName())) {try {// singleton instances shouldn't be built accessing request or session-specific context data
defaultImpl = (Settings) ObjectFactory.getObjectFactory().buildBean(Thread.currentThread().getContextClassLoader().loadClass(className),null);
}catch (Exception e) {
LOG.error("Settings: Could not instantiate the struts.configuration object, substituting the default implementation.", e);
}
}
}catch (IllegalArgumentException ex) {// ignore
去掉了第四步:init_ZeroConfiguration();
-5:第五步是自定义的configProviders
private void init_CustomConfigurationProviders() {
//从这里可以看到可以将自定义的Provider定义在web.xml中FilterDispatcher的param中:configProviders
String configProvs = initParams.get("configProviders");if (configProvs != null) {
String[] classes = configProvs.split("\\s*[,]\\s*");for (String cname : classes) {try {
Class cls = ClassLoaderUtils.loadClass(cname, this.getClass());
ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
configurationManager.addConfigurationProvider(prov);
}...
}
}
}
- 6:第六步:init_FilterInitParameters
//从这里可以看出struts.properties中的属性不仅可以在struts.xml中以constant形式定义,而且可以在FilterDispatcher的param中定义privatevoidinit_FilterInitParameters() {
configurationManager.addConfigurationProvider(new ConfigurationProvider() {publicvoiddestroy() {}publicvoidinit(Configuration configuration)throws ConfigurationException {}publicvoidloadPackages()throws ConfigurationException {}publicbooleanneedsReload() {returnfalse; }publicvoidregister(ContainerBuilder builder, LocatableProperties props)throws ConfigurationException {
props.putAll(initParams);//在这里实现滴~
}
});
}
第七步:init_AliasStandardObjects
使用BeanSelectionProvider这是将配置文件中定义的与实际的类相映射,就是注入bean的依赖关系,接下来是看怎样调用这些ConfigurationProviders,展开init_PreloadConfiguration()
private Containerinit_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;
}//再看getConfiguration()publicsynchronized ConfigurationgetConfiguration() {if (configuration ==null) {
setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName));try {//重点就是这个reloadContainer
configuration.reloadContainer(getContainerProviders());
}catch (ConfigurationException e) {
setConfiguration(null);thrownew ConfigurationException("Unable to load configuration.", e);
}
}else {
conditionalReload();
}return configuration;
}
展开DefaultConfiguration中的reloadContainer
publicsynchronized List<PackageProvider>reloadContainer(List<ContainerProvider> providers)throws ConfigurationException {
packageContexts.clear();
loadedFileNames.clear();
List<PackageProvider> packageProviders =new ArrayList<PackageProvider>();//Struts2(xwork2)用Container来完成依赖注入的功能//首先初始化一个ContainerBuilder,再由builder来保存接口与实现类或工厂类的对应关系//然后通过builder.create(boolean)方法产生container//由container.getInstance(Class);就可以得到接口的实现实例了//这一部分比较复杂,后面研究完成了,会单独拿出来讲,这里先弄清楚Xwork依赖注入的实现步骤就可以了
ContainerProperties props =new ContainerProperties();
ContainerBuilder builder =new ContainerBuilder();for (final ContainerProvider containerProvider : providers)
{//循环调用ConfigurationProvider的init和register方法,明白了吧,在这里统一循环调用
containerProvider.init(this);
containerProvider.register(builder, props);
}
props.setConstants(builder);//注入依赖关系,在这里并不产生实例
builder.factory(Configuration.class,new Factory<Configuration>() {public Configurationcreate(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);//create已经注入依赖关系的Container
container = builder.create(false);
setContext(container);
objectFactory = container.getInstance(ObjectFactory.class);// Process the configuration providers firstfor (final ContainerProvider containerProvider : providers)
{if (containerProviderinstanceof PackageProvider) {
container.inject(containerProvider);//调用PackageProvider的loadPackages()方法,这里主要是针对XmlConfigurationProvider和StrutsXmlConfigurationProvider
((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;
}