最近由于項目需求變更,需要本人對其中的某個業務功能進行修改。本人按照前臺頁面找action,根據action找代碼的邏輯進行了修改(公司項目是ssh框架,struts配置全部是通過注解的方式進行,配置簡單方便)。當然測試人員也成功的進行了測試,發現沒有任何問題,成功發版。奇葩事情來了,在發版環境中,修改的代碼總是沒用!
沒辦法,問題還是要解決,在確認了發版環境的確是最新代碼之后,回自己座位找原因。這次我用action名稱全局搜索項目工程,尼瑪發現兩個重名action,當然我只修改了其中一個文件,另一個文件的action中的代碼邏輯沒有修改,找到原因之后,發現兩個action之前的邏輯一模一樣,不同之處只在于我剛修改的部分,以防萬一,先把這個沒有修改的文件中新增我的邏輯代碼,再次提交發布,ok。
那么問題來了,兩個重名的action,會導致什么問題呢,為何在我電腦上修改之后就生效。打版發tag版本后就沒有用呢?百思不得其解,上網搜了一下action文件名稱重復的問題,結果網上全是struts如何避免struts重復,無非就是說struts通過文件名稱及命名空間包名稱來避免action重復,標記唯一。但是action文件名稱重復會導致什么問題卻沒有搜到相關技術文章。然后問了項目的相關技術人員,也不知道根本原因。沒辦法,自己動手,豐衣足食。只能自己猜測是struts可能在自己搜索action名稱對應的action的時候具有隨機性質吧。那么具體什么原因呢,回家后下載struts源碼看看看唄。
下面把看到的代碼講解一下為何在我的電腦上可以,發布后不可以。
1.首先我們都知道,struts項目中,我們都會在web.xml中配置struts封裝的過濾器,為何呢,因為struts為我們封裝了很多現成的東西,這樣我們才能通過action來請求相關,而不必用傳統的servlet方式請求。既然我們配置的StrutsPRepareAndExecuteFilter是一個過濾器,那么我們在項目啟動的時候必然會執行過濾器的init方法。
1 public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter { 2 protected PrepareOperations prepare; 3 protected ExecuteOperations execute; 4 protected List<Pattern> excludedPatterns = null; 5 6 public void init(FilterConfig filterConfig) throws ServletException { 7 InitOperations init = new InitOperations(); 8 Dispatcher dispatcher = null; 9 try {10 FilterHostConfig config = new FilterHostConfig(filterConfig);11 init.initLogging(config);12 dispatcher = init.initDispatcher(config);13 init.initStaticContentLoader(config, dispatcher);14 15 prepare = new PrepareOperations(dispatcher);16 execute = new ExecuteOperations(dispatcher);17 this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);18 19 postInit(dispatcher, filterConfig);20 } finally {21 if (dispatcher != null) {22 dispatcher.cleanUpAfterInit();23 }24 init.cleanup();25 }26 }
看上面源代碼會明白,第10行會把web.xml配置的一些基本配置給創建進來,12行通過這些配置創建dispatcher,當然,很多核心的功能都是圍繞著dispaatcher進行的,創建這個dispatcher會發生什么呢,我們看initDispatcher的源碼
2.下面是關于第12行InitOperations中創建dispatcher的源碼
1 public class InitOperations { 2 3 public InitOperations() { 4 } 5 6 /** 7 * Creates and initializes the dispatcher 8 */ 9 public Dispatcher initDispatcher( HostConfig filterConfig ) {10 Dispatcher dispatcher = createDispatcher(filterConfig);11 dispatcher.init();12 return dispatcher;13 }14 }
看上面第10行創建了dispatcher之后,11行立馬進行了初始化,那么都干了些什么呢,繼續往下深入。
3.下面是關于dispatcher中init方法的實現
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 public void init() { 6 7 if (configurationManager == null) { 8 configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME); 9 }10 11 try {12 init_FileManager();13 init_DefaultProperties(); // [1]14 init_TraditionalXmlConfigurations(); // [2]15 init_LegacyStrutsProperties(); // [3]16 init_CustomConfigurationProviders(); // [5]17 init_FilterInitParameters() ; // [6]18 init_AliasStandardObjects() ; // [7]19 20 Container container = init_PreloadConfiguration();21 container.inject(this);22 init_CheckWebLogicWorkaround(container);23 24 if (!dispatcherListeners.isEmpty()) {25 for (DispatcherListener l : dispatcherListeners) {26 l.dispatcherInitialized(this);27 }28 }29 errorHandler.init(servletContext);30 31 } catch (Exception ex) {32 if (LOG.isErrorEnabled())33 LOG.error("Dispatcher initialization failed", ex);34 throw new StrutsException(ex);35 }36 }
從上面的12-18行我們看到這些代碼都是一些基本的初始化操作,無非就是講struts默認封裝的一些properties文件,默認格式的struts.xml,struts-plugin.xml之類的文件的解析類放入配置管理器中,通過這幾部之后將下面幾個重要的DefaultPropertiesProvider(默認有個jboss),DefaultPropertiesProvider,StrutsXmlConfigurationProvider(默認三個,分別是解析struts默認掃描文件struts-default.xml,struts-plugin.xml及struts.xml,如果在web.xml中配置filter時傳入config配置時,會默認掃描config的配置,而不會掃面上面這三個配置了)全部添加到配置管理器中的containerProviders屬性中,真正核心的代碼部分在20行,這里面包含了解析的全過程,下面看代碼。
4.
1 private Container init_PreloadConfiguration() { 2 Container container = getContainer(); 3 4 boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD)); 5 LocalizedTextUtil.setReloadBundles(reloadi18n); 6 7 boolean devMode = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_DEVMODE)); 8 LocalizedTextUtil.setDevMode(devMode); 9 10 return container;11 }12 13 14 /**15 * Expose the dependency injection container.16 * @return Our dependency injection container17 */18 public Container getContainer() {19 if (ContainerHolder.get() != null) {20 return ContainerHolder.get();21 }22 ConfigurationManager mgr = getConfigurationManager();23 if (mgr == null) {24 throw new IllegalStateException("The configuration manager shouldn't be null");25 } else {26 Configuration config = mgr.getConfiguration();27 if (config == null) {28 throw new IllegalStateException("Unable to load configuration");29 } else {30 Container container = config.getContainer();31 ContainerHolder.store(container);32 return container;33 }34 }35 }
上面源代碼中第2行,首先獲取container,當然也是用的ThreadLocal模式,不懂得可以百度,這里不說了,直接說重點22行在獲取容器container的過程中,首先創建配置管理器ConfigurationManager,通過配置管理器獲取容器Configuration,那么26行究竟干了什么呢,繼續往下看。
5.
1 public class ConfigurationManager { 2 3 protected static final Logger LOG = LoggerFactory.getLogger(ConfigurationManager.class); 4 protected Configuration configuration; 5 protected Lock providerLock = new ReentrantLock(); 6 private List<ContainerProvider> containerProviders = new CopyOnWriteArrayList<ContainerProvider>(); 7 private List<PackageProvider> packageProviders = new CopyOnWriteArrayList<PackageProvider>(); 8 protected String defaultFrameworkBeanName; 9 private boolean providersChanged = false;10 private boolean reloadConfigs = true; // for the first time11 12 public ConfigurationManager() {13 this("xwork");14 }15 16 public ConfigurationManager(String name) {17 this.defaultFrameworkBeanName = name;18 }19 20 /**21 * Get the current XWork configuration object. By default an instance of DefaultConfiguration will be returned22 *23 * @see com.opensymphony.xwork2.config.impl.DefaultConfiguration24 */25 public synchronized Configuration getConfiguration() {26 if (configuration == null) {27 setConfiguration(createConfiguration(defaultFrameworkBeanName));28 try {29 configuration.reloadContainer(getContainerProviders());30 } catch (ConfigurationException e) {31 setConfiguration(null);32 throw new ConfigurationException("Unable to load configuration.", e);33 }34 } else {35 conditionalReload();36 }37 38 return configuration;39 }40 41 protected Configuration createConfiguration(String beanName) {42 return new DefaultConfiguration(beanName);43 }
27行沒有configuration先創建一個,創建的是42行的默認配置DefaultConfiguration,然后在29行調用了reloadContainer方法,通過名稱應該能明白,意思為重新加載容器,這個方法應該是統一封裝的,個人猜測開發修改代碼后重新加載不用重啟服務應該也是調用的這個方法,這個方法的參數為containerProviders,也就是在3中的那些解析處理類,那么該方法都有什么東東呢,繼續。。
6.
1 public class DefaultConfiguration implements Configuration { 2 /** 3 * Calls the ConfigurationProviderFactory.getConfig() to tell it to reload the configuration and then calls 4 * buildRuntimeConfiguration(). 5 * 6 * @throws ConfigurationException 7 */ 8 public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException { 9 packageContexts.clear();10 loadedFileNames.clear();11 List<PackageProvider> packageProviders = new ArrayList<PackageProvider>();12 13 ContainerProperties props = new ContainerProperties();14 ContainerBuilder builder = new ContainerBuilder();15 Container bootstrap = createBootstrapContainer(providers);16 for (final ContainerProvider containerProvider : providers)17 {18 bootstrap.inject(containerProvider);19 containerProvider.init(this);20 containerProvider.register(builder, props);21 }22 props.setConstants(builder);23 24 builder.factory(Configuration.class, new Factory<Configuration>() {25 public Configuration create(Context context) throws Exception {26 return DefaultConfiguration.this;27 }28 });29 30 ActionContext oldContext = ActionContext.getContext();31 try {32 // Set the bootstrap container for the purposes of factory creation33 34 setContext(bootstrap);35 container = builder.create(false);36 setContext(container);37 objectFactory = container.getInstance(ObjectFactory.class);38 39 // Process the configuration providers first40 for (final ContainerProvider containerProvider : providers)41 {42 if (containerProvider instanceof PackageProvider) {43 container.inject(containerProvider);44 ((PackageProvider)containerProvider).loadPackages();45 packageProviders.add((PackageProvider)containerProvider);46 }47 }48 49 // Then process any package providers from the plugins50 Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);51 for (String name : packageProviderNames) {52 PackageProvider provider = container.getInstance(PackageProvider.class, name);53 provider.init(this);54 provider.loadPackages();55 packageProviders.add(provider);56 }57 58 rebuildRuntimeConfiguration();59 } finally {60 if (oldContext == null) {61 ActionContext.setContext(null);62 }63 }64 return packageProviders;65 }66 }
上面這塊代碼的核心部分為40-46,以及50-56部分,40-46行部分代碼中心邏輯是遍歷之前放入configurationManager中的containerProviders,當然struts的三個默認配置文件或者自己在filter中配置的文件會在這里解析,而50行-56行的部分便是struts針對相關插件進行的處理,比如我們公司項目用注解的方式進行配置,那么就需要在項目中加入struts-convention.jar的jar包,因此這里先分析注解方式下的action解析。核心重點在54行
7.那么54行的provider的具體實現類是什么呢?我們可以在struts-convention的jar包中找到實現了PackageProvider的ClasspathPackageProvider類,該類包含了ActionConfigBuilder的一個引用,而ActionConfigBuilder的實現類為PackageBasedActionConfigBuilder,源碼如下
/** * <p> * This class is a configuration provider for the XWork configuration * system. This is really the only way to truly handle loading of the * packages, actions and results correctly. This doesn't contain any * logic and instead delegates to the configured instance of the * {@link ActionConfigBuilder} interface. * </p> */public class ClasspathPackageProvider implements PackageProvider { private ActionConfigBuilder actionConfigBuilder; @Inject public ClasspathPackageProvider(Container container) { this.actionConfigBuilder = container.getInstance(ActionConfigBuilder.class, container.getInstance(String.class, ConventionConstants.CONVENTION_ACTION_CONFIG_BUILDER)); } public void init(Configuration configuration) throws ConfigurationException { } public boolean needsReload() { return actionConfigBuilder.needsReload(); } public void loadPackages() throws ConfigurationException { actionConfigBuilder.buildActionConfigs(); }}
1 public interface ActionConfigBuilder { 2 /** 3 * Builds all the action configurations and stores them into the XWork configuration instance 4 * via XWork dependency injetion. 5 */ 6 void buildActionConfigs(); 7 8 boolean needsReload(); 9 10 void destroy();11 }
1 public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { 2 /** 3 * Builds the action configurations by loading all classes in the packages specified by the 4 * property <b>struts.convention.action.packages</b> and then figuring out which classes implement Action 5 * or have Action in their name. Next, if this class is in a java package that hasn't been 6 * inspected a new PackageConfig (XWork) is created for that Java package using the Java package 7 * name. This will contain all the ActionConfigs for all the Action classes that are discovered 8 * within that Java package. Next, each class is inspected for the {@link ParentPackage} 9 * annotation which is used to control the parent package for a specific action. Lastly, the 10 * {@link ResultMapBuilder} is used to create ResultConfig instances of the action. 11 */ 12 public void buildActionConfigs() { 13 //setup reload class loader based on dev settings 14 initReloadClassLoader(); 15 16 if (!disableActionScanning) { 17 if (actionPackages == null && packageLocators == null) { 18 throw new ConfigurationException("At least a list of action packages or action package locators " + 19 "must be given using one of the properties [struts.convention.action.packages] or " + 20 "[struts.convention.package.locators]"); 21 } 22 23 if (LOG.isTraceEnabled()) { 24 LOG.trace("Loading action configurations"); 25 if (actionPackages != null) 26 LOG.trace("Actions being loaded from action packages " + Arrays.asList(actionPackages)); 27 if (packageLocators != null) 28 LOG.trace("Actions being loaded using package locators " + Arrays.asList(packageLocators)); 29 if (excludePackages != null) 30 LOG.trace("Excluding actions from packages " + Arrays.asList(excludePackages)); 31 } 32 33 Set<Class> classes = findActions(); 34 buildConfiguration(classes); 35 } 36 } 37 38 @SuppressWarnings("unchecked") 39 protected Set<Class> findActions() { 40 Set<Class> classes = new HashSet<Class>(); 41 try { 42 if (actionPackages != null || (packageLocators != null && !disablePackageLocatorsscanning)) { 43 44 // By default, ClassFinder scans EVERY class in the specified 45 // url set, which can produce spurious warnings for non-action 46 // classes that can't be loaded. We pass a package filter that 47 // only considers classes that match the action packages 48 // specified by the user 49 Test<String> classPackageTest = getClassPackageTest(); 50 List<URL> urls = readUrls(); 51 ClassFinder finder = new ClassFinder(getClassLoaderInterface(), urls, EXTRACT_BASE_INTERFACES, fileProtocols, classPackageTest); 52 53 Test<ClassFinder.ClassInfo> test = getActionClassTest(); 54 classes.addAll(finder.findClasses(test)); 55 } 56 } catch (Exception ex) { 57 if (LOG.isErrorEnabled()) 58 LOG.error("Unable to scan named packages", ex); 59 } 60 61 return classes; 62 } 63 64 65 /** 66 * Construct a {@link Test} Object that determines if a specified class 67 * should be included in the package scan based on the full {@link ClassInfo} 68 * of the class. At this point, the class has been loaded, so it's ok to 69 * perform tests such as checking annotations or looking at interfaces or 70 * super-classes of the specified class. 71 * 72 * @return a {@link Test} object that returns true if the specified class 73 * should be included in the package scan 74 */ 75 protected Test<ClassFinder.ClassInfo> getActionClassTest() { 76 return new Test<ClassFinder.ClassInfo>() { 77 public boolean test(ClassFinder.ClassInfo classInfo) { 78 79 // Why do we call includeClassNameInActionScan here, when it's 80 // already been called to in the initial call to ClassFinder? 81 // When some action class passes our package filter in that step, 82 // ClassFinder automatically includes parent classes of that action, 83 // such as com.opensymphony.xwork2.ActionSupport. We repeat the 84 // package filter here to filter out such results. 85 boolean inPackage = includeClassNameInActionScan(classInfo.getName()); 86 boolean nameMatches = classInfo.getName().endsWith(actionSuffix); 87 88 try { 89 return inPackage && (nameMatches || (checkImplementsAction && com.opensymphony.xwork2.Action.class.isAssignableFrom(classInfo.get()))); 90 } catch (ClassNotFoundException ex) { 91 if (LOG.isErrorEnabled()) 92 LOG.error("Unable to load class [#0]", ex, classInfo.getName()); 93 return false; 94 } 95 } 96 }; 97 } 98 @SuppressWarnings("unchecked") 99 protected void buildConfiguration(Set<Class> classes) {100 Map<String, PackageConfig.Builder> packageConfigs = new HashMap<String, PackageConfig.Builder>();101 102 for (Class<?> actionClass : classes) {103 Actions actionsAnnotation = actionClass.getAnnotation(Actions.class);104 Action actionAnnotation = actionClass.getAnnotation(Action.class);105 106 // Skip classes that can't be instantiated107 if (cannotInstantiate(actionClass)) {108 if (LOG.isTraceEnabled())109 LOG.trace("Class [#0] did not pass the instantiation test and will be ignored", actionClass.getName());110 continue;111 }112 113 if (eagerLoading) {114 // Tell the ObjectFactory about this class115 try {116 objectFactory.getClassInstance(actionClass.getName());117 } catch (ClassNotFoundException e) {118 if (LOG.isErrorEnabled())119 LOG.error("Object Factory was unable to load class [#0]", e, actionClass.getName());120 throw new StrutsException("Object Factory was unable to load class " + actionClass.getName(), e);121 }122 }123 124 // Determine the action package125 String actionPackage = actionClass.getPackage().getName();126 if (LOG.isDebugEnabled()) {127 LOG.debug("Processing class [#0] in package [#1]", actionClass.getName(), actionPackage);128 }129 130 // Determine the default namespace and action name131 List<String> namespaces = determineActionNamespace(actionClass);132 for (String namespace : namespaces) {133 String defaultActionName = determineActionName(actionClass);134 PackageConfig.Builder defaultPackageConfig = getPackageConfig(packageConfigs, namespace,135 actionPackage, actionClass, null);136 137 // Verify that the annotations have no errors and also determine if the default action138 // configuration should still be built or not.139 Map<String, List<Action>> map = getActionAnnotations(actionClass);140 Set<String> actionNames = new HashSet<String>();141 boolean hasDefaultMethod = ReflectionTools.containsMethod(actionClass, DEFAULT_METHOD);142 if (!map.containsKey(DEFAULT_METHOD)143 && hasDefaultMethod144 && actionAnnotation == null && actionsAnnotation == null145 && (alwaysMapExecute || map.isEmpty())) {146 boolean found = false;147 for (String method : map.keySet()) {148 List<Action> actions = map.get(method);149 for (Action action : actions) {150 151 // Check if there are duplicate action names in the annotations.152 String actionName = action.value().equals(Action.DEFAULT_VALUE) ? defaultActionName : action.value();153 if (actionNames.contains(actionName)) {154 throw new ConfigurationException("The action class [" + actionClass +155 "] contains two methods with an action name annotation whose value " +156 "is the same (they both might be empty as well).");157 } else {158 actionNames.add(actionName);159 }160 161 // Check this annotation is the default action162 if (action.value().equals(Action.DEFAULT_VALUE)) {163 found = true;164 }165 }166 }167 168 // Build the default169 if (!found) {170 createActionConfig(defaultPackageConfig, actionClass, defaultActionName, DEFAULT_METHOD, null);171 }172 }173 174 // Build the actions for the annotations175 for (String method : map.keySet()) {176 List<Action> actions = map.get(method);177 for (Action action : actions) {178 PackageConfig.Builder pkgCfg = defaultPackageConfig;179 if (action.value().contains("/") && !slashesInActionNames) {180 pkgCfg = getPackageConfig(packageConfigs, namespace, actionPackage,181 actionClass, action);182 }183 184 createActionConfig(pkgCfg, actionClass, defaultActionName, method, action);185 }186 }187 188 // some actions will not have any @Action or a default method, like the rest actions189 // where the action mapper is the one that finds the right method at runtime190 if (map.isEmpty() && mapAllMatches && actionAnnotation == null && actionsAnnotation == null) {191 createActionConfig(defaultPackageConfig, actionClass, defaultActionName, null, actionAnnotation);192 }193 194 //if there are @Actions or @Action at the class level, create the mappings for them195 String methodName = hasDefaultMethod ? DEFAULT_METHOD : null;196 if (actionsAnnotation != null) {197 List<Action> actionAnnotations = checkActionsAnnotation(actionsAnnotation);198 for (Action actionAnnotation2 : actionAnnotations)199 createActionConfig(defaultPackageConfig, actionClass, defaultActionName, methodName, actionAnnotation2);200 } else if (actionAnnotation != null)201 createActionConfig(defaultPackageConfig, actionClass, defaultActionName, methodName, actionAnnotation);202 }203 }204 205 buildIndexActions(packageConfigs);206 207 // Add the new actions to the configuration208 Set<String> packageNames = packageConfigs.keySet();209 for (String packageName : packageNames) {210 configuration.addPackageConfig(packageName, packageConfigs.get(packageName).build());211 }212 }
那么我們看上面的代碼buildActionConfigs方法中核心代碼33行中的findActions,該方法的意思是找出jar包及class文件中所有匹配指定格式的文件列表,然后過濾有action,actions,struts,struts2的所有類文件,54行就是匹配符合條件的action,也就是我們一般情況下寫在action包中的那些所有的class文件列表,從這里可以看出findClasses文件返回的是一個列表,然后將列表放入一個hashset中,我們知道,hashset的一個特點就是不保證我們存放的順序,看到這里我們也就恍然大悟了,所有在符合action,actions,struts,struts2路徑下的定義的文件,都會在hashset中存儲,那么存放的地址是不確定的,這樣在后面34行的解析所有的action時候的訪問順序就不是固定的,訪問的時候同名action對應的class文件誰后被訪問到,誰將會真正的放入容器中,我們看到102行的循環遍歷hashset,201行的主要邏輯代碼如下
1 /** 2 * Creates a single ActionConfig object. 3 * 4 * @param pkgCfg The package the action configuration instance will belong to. 5 * @param actionClass The action class. 6 * @param actionName The name of the action. 7 * @param actionMethod The method that the annotation was on (if the annotation is not null) or 8 * the default method (execute). 9 * @param annotation The ActionName annotation that might override the action name and possibly10 */11 protected void createActionConfig(PackageConfig.Builder pkgCfg, Class<?> actionClass, String actionName,12 String actionMethod, Action annotation) {13 String className = actionClass.getName();14 if (annotation != null) {15 actionName = annotation.value() != null && annotation.value().equals(Action.DEFAULT_VALUE) ? actionName : annotation.value();16 actionName = StringUtils.contains(actionName, "/") && !slashesInActionNames ? StringUtils.substringAfterLast(actionName, "/") : actionName;17 if(!Action.DEFAULT_VALUE.equals(annotation.className())){18 className = annotation.className();19 }20 }21 22 ActionConfig.Builder actionConfig = new ActionConfig.Builder(pkgCfg.getName(), actionName, className);23 actionConfig.methodName(actionMethod);24 25 if (LOG.isDebugEnabled()) {26 LOG.debug("Creating action config for class [#0], name [#1] and package name [#2] in namespace [#3]",27 actionClass.toString(), actionName, pkgCfg.getName(), pkgCfg.getNamespace());28 }29 30 //build interceptors31 List<InterceptorMapping> interceptors = interceptorMapBuilder.build(actionClass, pkgCfg, actionName, annotation);32 actionConfig.addInterceptors(interceptors);33 34 //build results35 Map<String, ResultConfig> results = resultMapBuilder.build(actionClass, annotation, actionName, pkgCfg.build());36 actionConfig.addResultConfigs(results);37 38 //add params39 if (annotation != null)40 actionConfig.addParams(StringTools.createParameterMap(annotation.params()));41 42 //add exception mappings from annotation43 if (annotation != null && annotation.exceptionMappings() != null)44 actionConfig.addExceptionMappings(buildExceptionMappings(annotation.exceptionMappings(), actionName));45 46 //add exception mapping from class47 ExceptionMappings exceptionMappings = actionClass.getAnnotation(ExceptionMappings.class);48 if (exceptionMappings != null)49 actionConfig.addExceptionMappings(buildExceptionMappings(exceptionMappings.value(), actionName));50 51 //add52 pkgCfg.addActionConfig(actionName, actionConfig.build());53 54 //check if an action with the same name exists on that package (from XML config probably)55 PackageConfig existingPkg = configuration.getPackageConfig(pkgCfg.getName());56 if (existingPkg != null) {57 // there is a package already with that name, check action58 ActionConfig existingActionConfig = existingPkg.getActionConfigs().get(actionName);59 if (existingActionConfig != null && LOG.isWarnEnabled())60 LOG.warn("Duplicated action definition in package [#0] with name [#1].", pkgCfg.getName(), actionName);61 }62 63 //watch class file64 if (isReloadEnabled()) {65 URL classFile = actionClass.getResource(actionClass.getSimpleName() + ".class");66 fileManager.monitorFile(classFile);67 loadedFileUrls.add(classFile.toString());68 }69 }
上面的52行解釋了這里的道理,因為PackageConfig中封裝了一個名為actionConfigs的hashmap,protected Map<String, ActionConfig> actionConfigs;,key為action的名字,value為class的名字,這樣誰后被訪問,后面的class將起到作用。
另外說一點關于struts中很多類似于下面的構造
1 /* 2 * Copyright 2002-2006,2009 The Apache Software Foundation. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.opensymphony.xwork2.config.entities; 17 18 import com.opensymphony.xwork2.util.location.Located; 19 import com.opensymphony.xwork2.util.location.Location; 20 import com.opensymphony.xwork2.util.logging.Logger; 21 import com.opensymphony.xwork2.util.logging.LoggerFactory; 22 23 import java.io.Serializable; 24 import java.util.ArrayList; 25 import java.util.Collections; 26 import java.util.LinkedHashMap; 27 import java.util.List; 28 import java.util.Map; 29 30 31 /** 32 * Configuration for Package. 33 * <p/> 34 * In the xml configuration file this is defined as the <code>package</code> tag. 35 * 36 * @author Rainer Hermanns 37 * @version $Revision$ 38 */ 39 public class PackageConfig extends Located implements Comparable, Serializable, InterceptorLocator { 40 41 private static final Logger LOG = LoggerFactory.getLogger(PackageConfig.class); 42 43 protected Map<String, ActionConfig> actionConfigs; 44 protected Map<String, ResultConfig> globalResultConfigs; 45 protected Map<String, Object> interceptorConfigs; 46 protected Map<String, ResultTypeConfig> resultTypeConfigs; 47 protected List<ExceptionMappingConfig> globalExceptionMappingConfigs; 48 protected List<PackageConfig> parents; 49 protected String defaultInterceptorRef; 50 protected String defaultActionRef; 51 protected String defaultResultType; 52 protected String defaultClassRef; 53 protected String name; 54 protected String namespace = ""; 55 protected boolean isAbstract = false; 56 protected boolean needsRefresh; 57 58 protected PackageConfig(String name) { 59 this.name = name; 60 actionConfigs = new LinkedHashMap<String, ActionConfig>(); 61 globalResultConfigs = new LinkedHashMap<String, ResultConfig>(); 62 interceptorConfigs = new LinkedHashMap<String, Object>(); 63 resultTypeConfigs = new LinkedHashMap<String, ResultTypeConfig>(); 64 globalExceptionMappingConfigs = new ArrayList<ExceptionMappingConfig>(); 65 parents = new ArrayList<PackageConfig>(); 66 } 67 68 protected PackageConfig(PackageConfig orig) { 69 this.defaultInterceptorRef = orig.defaultInterceptorRef; 70 this.defaultActionRef = orig.defaultActionRef; 71 this.defaultResultType = orig.defaultResultType; 72 this.defaultClassRef = orig.defaultClassRef; 73 this.name = orig.name; 74 this.namespace = orig.namespace; 75
新聞熱點
疑難解答