关于Struts2.3.16 struts.xml 位置的坑,你肯定掉过~~~
最近在整一个简单的框架,Spring4.0.6-Mybatis3-Struts2.3.16. 当中出现了一些坑让人费解。
本来Spring配置都是完美ok的,但是栽在了struts的配置文件上。下面边听虾米,边开写了 --!
背景:Spring4.0.6-Mybatis3-Struts2.3.16 + maven3
文件结构:
工程文件目录结构
配置文件目录结构
web.xml 中的struts配置
1 <filter> 2 <filter-name>struts2</filter-name> 4 <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> 5 <init-param> 6 <param-name>config</param-name> 7 <param-value>struts-default.xml,struts-plugin.xml,/WEB-INF/classes/struts/action/struts.xml</param-value> 8 </init-param> 9 </filter>
按照spring的Listener的习惯,给配置文件前面加上了路径/WEB-INF/classes/
跑到chrome里面一输入地址,蹦出个404 action not found ...
what happened ? 明明控制台没有错误输出的啊......
辗转反侧,又加上了log4j来输出日志,难道struts2这货要依靠log4j来替自己说话?
果然,在配置了log4j之后,struts把他的不痛快吐了一地....
关于log4j的配置,童鞋们可以上百度,上谷歌,很详细的有木有啊...
1 2014-08-07 17:06:02 INFO [com.opensymphony.xwork2.config.providers.XmlConfigurationProvider] Parsing configuration file [struts-default.xml] 2 2014-08-07 17:06:02 INFO [com.opensymphony.xwork2.config.providers.XmlConfigurationProvider] Unable to locate configuration files of the name struts-plugin.xml, skipping 3 2014-08-07 17:06:02 INFO [com.opensymphony.xwork2.config.providers.XmlConfigurationProvider] Parsing configuration file [struts-plugin.xml] 4 2014-08-07 17:06:02 INFO [com.opensymphony.xwork2.config.providers.XmlConfigurationProvider] Unable to locate configuration files of the name \WEB-INF\classes\struts/action/struts.xml, skipping 5 2014-08-07 17:06:02 INFO [com.opensymphony.xwork2.config.providers.XmlConfigurationProvider] Parsing configuration file [\WEB-INF\classes\struts/action/struts.xml]
很明显,没有加载到struts.xml文件,然后把百度和谷歌翻了个遍,大部分答案都是一样的
1.当自定义struts的配置文件的时候需要加载 struts-default.xml,struts-plugin.xml 这两个
2.struts2 加载配置文件是基于src目录
当然上面这两条是正确的. 但是有人说把/WEB-INF/classes/struts.xml 改成 ../struts.xml ,我还真没成功....
why ?
试了网上的几种方法都不成功都,决定自己看sruts的源码,断点进入struts的内部一看究竟
(这里介绍一个eclipse的插件--Java Attach Source :jar包源码下载插件 , 直接在eclipse market里搜索安装后,找到你想到下载源码的jar包,
右键->Attach Java Source 然后看到progress 完成后就可以点开源码了.)
(当想要研究某个框架的时候,需要用容器启动断点怎么办?最好的办法就是你写一个类继续它,然后配置里写这个类,打上断点就ok了。)
好直接上struts org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter的代码
1 public void init(FilterConfig filterConfig) throws ServletException { 2 InitOperations init = new InitOperations(); 3 Dispatcher dispatcher = null; 4 try { 5 //初始化filterConfig 6 FilterHostConfig config = new FilterHostConfig(filterConfig); 7 //加载与log4j关联的日志类 8 init.initLogging(config); 9 //初始化配置文件 大头来了 10 dispatcher = init.initDispatcher(config); 11 //设置配置文件中的内容 12 init.initStaticContentLoader(config, dispatcher); 13 //后面这些是对 url 的解析与 action method 的执行 14 prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher); 15 execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher); 16 this.excludedPatterns = init.buildExcludedPatternsList(dispatcher); 17 18 postInit(dispatcher, filterConfig); 19 } finally { 20 if (dispatcher != null) { 21 dispatcher.cleanUpAfterInit(); 22 } 23 init.cleanup(); 24 } 25 }
当启动进入到StrutsPrepareAndExecuteFilter 类时就进入init方法.
看到这里的 filterConfig , 网上就有一大帮人说,自定义 struts.xml 时, param-name 要写成filterConfig.
这是个坑,这里的filterConfig 和 param-name 中的值木有半毛钱关系.... 关于why写config后面再细说.
1 <filter> 2 <filter-name>struts2</filter-name> 3 <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> 4 <init-param> 5 <param-name>filterConfig</param-name> <!-- 这里绝对是错误的,各位小心 --> 6 <param-value>struts-default.xml,struts-plugin.xml,\WEB-INF\classes\struts/action/struts.xml</param-value> 7 </init-param> 8 </filter>
好,既然我们找到了加载配置文件的大头,那么我们进入到 org.apache.struts2.dispatcher.ng.InitOperations
dispatcher = init.initDispatcher(config); 一探天地吧.
1 public Dispatcher initDispatcher( HostConfig filterConfig ) { 2 //创建Dispatcher filter 3 Dispatcher dispatcher = createDispatcher(filterConfig); 4 //类的初始化 5 dispatcher.init(); 6 return dispatcher; 7 }
进入到org.apache.struts2.dispatcher.Dispatcher dispatcher.init(); ok , 凶手出来鸟~~~
1 /** 2 * Load configurations, including both XML and zero-configuration strategies, 3 * and update optional settings, including whether to reload configurations and resource files. 4 *注释中有说到加载配置的顺序 5 * 1.加载xml文件 6 * 2.加载 类似 struts.properties 文件 7 * 3.更新操作设置 8 * 4.配置是否自动reload加载 9 */ 10 public void init() { 11 12 if (configurationManager == null) { 13 configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME); 14 } 15 16 try { 17 //初始化FileManer 18 init_FileManager(); 19 //加载struts.properties 文件 一般都在struts.xml文件中配置了 20 init_DefaultProperties(); // [1] 21 //加载struts.xml 文件 22 init_TraditionalXmlConfigurations(); // [2] 23 //解析配置文件、设置变量等等 24 init_LegacyStrutsProperties(); // [3] //加载默认配置文件解析器 PropertiesConfigurationProvider configg provider 25 init_CustomConfigurationProviders(); // [5] 加载用户自定义的 configuration provider 26 init_FilterInitParameters() ; // [6] //struts.xml 重新加载相关 27 init_AliasStandardObjects() ; // [7] //国际化相关 28 29 Container container = init_PreloadConfiguration();// 30 container.inject(this); 31 init_CheckWebLogicWorkaround(container); 32 33 if (!dispatcherListeners.isEmpty()) { 34 for (DispatcherListener l : dispatcherListeners) { 35 l.dispatcherInitialized(this); 36 } 37 } 38 } catch (Exception ex) { 39 if (LOG.isErrorEnabled()) 40 LOG.error("Dispatcher initialization failed", ex); 41 throw new StrutsException(ex); 42 } 43 }
我们具体到org.apache.struts2.dispatcher.Dispatcher init_TraditionalXmlConfigurations() 看怎么加载struts.xml的
1 private void init_TraditionalXmlConfigurations() { 2 //这里就是我们在web.xml中需要些的param-name 的 name -> config 而不是网上所说的 filterConfig 3 String configPaths = initParams.get("config"); 4 if (configPaths == null) { 5 configPaths = DEFAULT_CONFIGURATION_PATHS; 6 } 7 // 将我们要加载的struts 相关的 配置文件 如 struts.xml 但不支持struts*.xml 8 String[] files = configPaths.split("\\s*[,]\\s*"); 9 for (String file : files) { 10 if (file.endsWith(".xml")) { 11 if ("xwork.xml".equals(file)) { 12 configurationManager.addContainerProvider(createXmlConfigurationProvider(file, false)); 13 } else { 14 //加载 xml 解释器 , 加载 struts.xml 到 configurationManager 中 15 configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext)); 16 } 17 } else { 18 throw new IllegalArgumentException("Invalid configuration file name"); 19 } 20 } 21 }
关键时刻到来了
1 Container container = init_PreloadConfiguration(); // 加载 struts.xml 配置文件
1 public static Iterator<URL> getResources(String resourceName, Class callingClass, boolean aggregate) throws IOException { 2 //这里的resourceName 即 struts.xml 3 // callingClass 指类加载器的class 4 AggregateIterator<URL> iterator = new AggregateIterator<URL>(); 5 6 iterator.addEnumeration(Thread.currentThread().getContextClassLoader().getResources(resourceName)); 7 8 if (!iterator.hasNext() || aggregate) { 9 iterator.addEnumeration(ClassLoaderUtil.class.getClassLoader().getResources(resourceName)); 10 } 11 12 if (!iterator.hasNext() || aggregate) { 13 ClassLoader cl = callingClass.getClassLoader(); 14 15 if (cl != null) { 16 iterator.addEnumeration(cl.getResources(resourceName)); 17 } 18 } 19 20 if (!iterator.hasNext() && (resourceName != null) && ((resourceName.length() == 0) || (resourceName.charAt(0) != '/'))) { 21 //获取 struts.xml 所在的根路径 22 return getResources('/' + resourceName, callingClass, aggregate); 23 } 24 25 return iterator; 26 } 27 28 29 30 package org.apache.struts2.dispatcher.Dispatcher 31 32 private Container init_PreloadConfiguration() { 33 // 获取 xml 配置 34 Configuration config = configurationManager.getConfiguration(); 35 Container container = config.getContainer(); 36 37 boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD)); 38 LocalizedTextUtil.setReloadBundles(reloadi18n); 39 40 ContainerHolder.store(container); 41 42 return container; 43 }
44 package com.opensymphony.xwork2.config.ConfigurationManager 45 46 public synchronized Configuration getConfiguration() { 47 if (configuration == null) { 48 setConfiguration(createConfiguration(defaultFrameworkBeanName)); 49 try { 50 // 加载 配置解析器 providers 51 configuration.reloadContainer(getContainerProviders()); 52 } catch (ConfigurationException e) { 53 setConfiguration(null); 54 throw new ConfigurationException("Unable to load configuration.", e); 55 } 56 } else { 57 conditionalReload(); 58 } 59 60 return configuration; 61 } 62 63 package com.opensymphony.xwork2.config.impl.DefaultConfiguration implements Configuration 64 65 public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException { 66 packageContexts.clear(); 67 loadedFileNames.clear(); 68 List<PackageProvider> packageProviders = new ArrayList<PackageProvider>(); 69 70 ContainerProperties props = new ContainerProperties(); 71 ContainerBuilder builder = new ContainerBuilder(); 72 Container bootstrap = createBootstrapContainer(providers); 73 for (final ContainerProvider containerProvider : providers) 74 { 75 //类加载器注入 xml 解析器 76 bootstrap.inject(containerProvider); 77 // 执行各个解析器的init方法 , 有我们前面加载的proerties,xml 的解析器等等 -- 即加载文件操作 78 containerProvider.init(this); 79 // 注册 xml 即 解析操作 80 containerProvider.register(builder, props); 81 }
先执行 XmlConfigurationProvider 中的 init()
1 com.opensymphony.xwork2.config.providers 2 public class XmlConfigurationProvider implements ConfigurationProvider { 3 public void init(Configuration configuration) { 4 this.configuration = configuration; 5 this.includedFileNames = configuration.getLoadedFileNames(); 6 //加载 struts.xml 文档 7 loadDocuments(configFileName); 8 } 9 10 private void loadDocuments(String configFileName) { 11 try { 12 loadedFileUrls.clear(); 13 //继续加载文档 14 documents = loadConfigurationFiles(configFileName, null); 15 } catch (ConfigurationException e) { 16 throw e; 17 } catch (Exception e) { 18 throw new ConfigurationException("Error loading configuration file " + configFileName, e); 19 } 20 } 21 22 private List<Document> loadConfigurationFiles(String fileName, Element includeElement) { 23 List<Document> docs = new ArrayList<Document>(); 24 List<Document> finalDocs = new ArrayList<Document>(); 25 if (!includedFileNames.contains(fileName)) { 26 if (LOG.isDebugEnabled()) { 27 LOG.debug("Loading action configurations from: " + fileName); 28 } 29 30 includedFileNames.add(fileName); 31 32 Iterator<URL> urls = null; 33 InputStream is = null; 34 35 IOException ioException = null; 36 try { 37 // 亮点在此 38 urls = getConfigurationUrls(fileName); 39 } catch (IOException ex) { 40 ioException = ex; 41 } 42 43 if (urls == null || !urls.hasNext()) { 44 if (errorIfMissing) { 45 throw new ConfigurationException("Could not open files of the name " + fileName, ioException); 46 } else { 47 if (LOG.isInfoEnabled()) { 48 // 没有找到 配置文件的 日志输出在此 49 LOG.info("Unable to locate configuration files of the name " 50 + fileName + ", skipping"); 51 } 52 return docs; 53 } 54 } 55 56 URL url = null; 57 while (urls.hasNext()) { 58 try { 59 url = urls.next(); 60 //找到配置文件,并存在的 加载文件 61 is = fileManager.loadFile(url); 62 63 InputSource in = new InputSource(is); 64 65 in.setSystemId(url.toString()); 66 67 docs.add(DomHelper.parse(in, dtdMappings)); 68 } catch (XWorkException e) { 69 if (includeElement != null) { 70 throw new ConfigurationException("Unable to load " + url, e, includeElement); 71 } else { 72 throw new ConfigurationException("Unable to load " + url, e); 73 } 74 } catch (Exception e) { 75 throw new ConfigurationException("Caught exception while loading file " + fileName, e, includeElement); 76 } finally { 77 if (is != null) { 78 try { 79 is.close(); 80 } catch (IOException e) { 81 LOG.error("Unable to close input stream", e); 82 } 83 } 84 } 85 }
好,直接来最终的判断struts,xml的文件路径的方法,终于等到你...
1 public static Iterator<URL> getResources(String resourceName, Class callingClass, boolean aggregate) throws IOException { 2 //这里的resourceName 即 struts.xml 3 // callingClass 指类加载器的class 4 AggregateIterator<URL> iterator = new AggregateIterator<URL>(); 5 // 在这里就获取到了struts.xml 所在的path了 6 iterator.addEnumeration(Thread.currentThread().getContextClassLoader().getResources(resourceName)); 7 ///E:/Soft/tomcat-6.0.39-x64/apache-tomcat-6.0.39/webapps/TestDemos/WEB-INF/classes/struts/action/struts.xml
//原来他是从WEB-INF/classes 开始滴.... 真是愁死我了... 8 if (!iterator.hasNext() || aggregate) { 9 iterator.addEnumeration(ClassLoaderUtil.class.getClassLoader().getResources(resourceName)); 10 } 11 12 if (!iterator.hasNext() || aggregate) { 13 ClassLoader cl = callingClass.getClassLoader(); 14 15 if (cl != null) { 16 iterator.addEnumeration(cl.getResources(resourceName)); 17 } 18 } 19 20 if (!iterator.hasNext() && (resourceName != null) && ((resourceName.length() == 0) || (resourceName.charAt(0) != '/'))) { 21 22 return getResources('/' + resourceName, callingClass, aggregate); 23 } 24 25 return iterator; 26 }
ok, struts2 加载配置文件到此结束了,接下来的就是 register 解析 注册 配置文件了,看了近一天的源码终于弄明白了......各种泪奔啊
下面简单贴下 解析过程的代码了,解析的部分就类型xml的解析,获取关键字,mapping配置文件中的action和result
1 // 各种做事情都是 这个provider 这里就是适配器模式来适配各种配置文件.xml,.propertis,.dtd等等 2 // N 个provider 循环执行 3 // for (final ContainerProvider containerProvider : providers) 4 { 5 bootstrap.inject(containerProvider); 6 containerProvider.init(this); 7 containerProvider.register(builder, props); 8 } 9 10 package com.opensymphony.xwork2.config.providers.XmlConfigurationProvider implements ConfigurationProvider 11 12 public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { 13 if (LOG.isInfoEnabled()) { 14 LOG.info("Parsing configuration file [" + configFileName + "]"); 15 } 16 Map<String, Node> loadedBeans = new HashMap<String, Node>(); 17 for (Document doc : documents) { 18 Element rootElement = doc.getDocumentElement(); 19 NodeList children = rootElement.getChildNodes(); 20 int childSize = children.getLength(); 21 22 for (int i = 0; i < childSize; i++) { 23 Node childNode = children.item(i); 24 25 if (childNode instanceof Element) { 26 Element child = (Element) childNode; 27 28 final String nodeName = child.getNodeName(); 29 // 加载spring中的bean 30 if ("bean".equals(nodeName)) { 31 String type = child.getAttribute("type"); 32 String name = child.getAttribute("name"); 33 String impl = child.getAttribute("class"); 34 String onlyStatic = child.getAttribute("static"); 35 String scopeStr = child.getAttribute("scope"); 36 boolean optional = "true".equals(child.getAttribute("optional")); 37 Scope scope = Scope.SINGLETON; 38 if ("default".equals(scopeStr)) { 39 scope = Scope.DEFAULT; 40 } else if ("request".equals(scopeStr)) { 41 scope = Scope.REQUEST; 42 } else if ("session".equals(scopeStr)) { 43 scope = Scope.SESSION; 44 } else if ("singleton".equals(scopeStr)) { 45 scope = Scope.SINGLETON; 46 } else if ("thread".equals(scopeStr)) { 47 scope = Scope.THREAD; 48 } 49 50 if (StringUtils.isEmpty(name)) { 51 name = Container.DEFAULT_NAME; 52 } 53 54 try { 55 Class cimpl = ClassLoaderUtil.loadClass(impl, getClass()); 56 Class ctype = cimpl; 57 if (StringUtils.isNotEmpty(type)) { 58 ctype = ClassLoaderUtil.loadClass(type, getClass()); 59 } 60 if ("true".equals(onlyStatic)) { 61 // Force loading of class to detect no class def found exceptions 62 cimpl.getDeclaredClasses(); 63 containerBuilder.injectStatics(cimpl); 64 } else { 65 if (containerBuilder.contains(ctype, name)) { 66 Location loc = LocationUtils.getLocation(loadedBeans.get(ctype.getName() + name)); 67 if (throwExceptionOnDuplicateBeans) { 68 throw new ConfigurationException("Bean type " + ctype + " with the name " + 69 name + " has already been loaded by " + loc, child); 70 } 71 } 72 73 // Force loading of class to detect no class def found exceptions 74 cimpl.getDeclaredConstructors(); 75 76 if (LOG.isDebugEnabled()) { 77 LOG.debug("Loaded type:" + type + " name:" + name + " impl:" + impl); 78 } 79 containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope); 80 } 81 loadedBeans.put(ctype.getName() + name, child); 82 } catch (Throwable ex) { 83 if (!optional) { 84 throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, childNode); 85 } else { 86 if (LOG.isDebugEnabled()) { 87 LOG.debug("Unable to load optional class: #0", impl); 88 } 89 } 90 } 91 //解析struts.xml 中的action 92 } else if ("constant".equals(nodeName)) { 93 String name = child.getAttribute("name"); 94 String value = child.getAttribute("value"); 95 props.setProperty(name, value, childNode); 96 } else if (nodeName.equals("unknown-handler-stack")) { 97 List<UnknownHandlerConfig> unknownHandlerStack = new ArrayList<UnknownHandlerConfig>(); 98 NodeList unknownHandlers = child.getElementsByTagName("unknown-handler-ref"); 99 int unknownHandlersSize = unknownHandlers.getLength(); 100 101 for (int k = 0; k < unknownHandlersSize; k++) { 102 Element unknownHandler = (Element) unknownHandlers.item(k); 103 unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name"))); 104 } 105 106 if (!unknownHandlerStack.isEmpty()) 107 configuration.setUnknownHandlerStack(unknownHandlerStack); 108 } 109 } 110 } 111 } 112 }
ok,现在struts 加载完了配置文件、就要进行后面的工作了,后面的内容如果有机会,将在下一篇文章中发出了
这篇有点小长,但是总体还是比较精细,欢迎各位来点评、拍砖.
http://i.cnblogs.com/EditPosts.aspx?postid=3897813