最近在整一個簡單的框架,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