你的位置:首页 > Java教程

[Java教程]struts中action名称重复导致的奇异事件


  最近由于项目需求变更,需要本人对其中的某个业务功能进行修改。本人按照前台页面找action,根据action找代码的逻辑进行了修改(公司项目是ssh框架,struts配置全部是通过注解的方式进行,配置简单方便)。当然测试人员也成功的进行了测试,发现没有任何问题,成功发版。奇葩事情来了,在发版环境中,修改的代码总是没用!

  没办法,问题还是要解决,在确认了发版环境的确是最新代码之后,回自己座位找原因。这次我用action名称全局搜索项目工程,尼玛发现两个重名action,当然我只修改了其中一个文件,另一个文件的action中的代码逻辑没有修改,找到原因之后,发现两个action之前的逻辑一模一样,不同之处只在于我刚修改的部分,以防万一,先把这个没有修改的文件中新增我的逻辑代码,再次提交发布,ok。

  那么问题来了,两个重名的action,会导致什么问题呢,为何在我电脑上修改之后就生效。打版发tag版本后就没有用呢?百思不得其解,上网搜了一下action文件名称重复的问题,结果网上全是struts如何避免struts重复,无非就是说struts通过文件名称及命名空间包名称来避免action重复,标记唯一。但是action文件名称重复会导致什么问题却没有搜到相关技术文章。然后问了项目的相关技术人员,也不知道根本原因。没办法,自己动手,丰衣足食。只能自己猜测是struts可能在自己搜索action名称对应的action的时候具有随机性质吧。那么具体什么原因呢,回家后下载struts源码看看看呗。

  下面把看到的代码讲解一下为何在我的电脑上可以,发布后不可以。

  1.首先我们都知道,struts项目中,我们都会在web.

 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.

  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  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_Traditional// [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.

  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 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  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     this.isAbstract = orig.isAbstract; 76     this.needsRefresh = orig.needsRefresh; 77     this.actionConfigs = new LinkedHashMap<String, ActionConfig>(orig.actionConfigs); 78     this.globalResultConfigs = new LinkedHashMap<String, ResultConfig>(orig.globalResultConfigs); 79     this.interceptorConfigs = new LinkedHashMap<String, Object>(orig.interceptorConfigs); 80     this.resultTypeConfigs = new LinkedHashMap<String, ResultTypeConfig>(orig.resultTypeConfigs); 81     this.globalExceptionMappingConfigs = new ArrayList<ExceptionMappingConfig>(orig.globalExceptionMappingConfigs); 82     this.parents = new ArrayList<PackageConfig>(orig.parents); 83     this.location = orig.location; 84   } 85  86   public boolean isAbstract() { 87     return isAbstract; 88   } 89  90   public Map<String, ActionConfig> getActionConfigs() { 91     return actionConfigs; 92   } 93  94   /** 95    * returns the Map of all the ActionConfigs available in the current package. 96    * ActionConfigs defined in ancestor packages will be included in this Map. 97    * 98    * @return a Map of ActionConfig Objects with the action name as the key 99    * @see ActionConfig100   */101   public Map<String, ActionConfig> getAllActionConfigs() {102     Map<String, ActionConfig> retMap = new LinkedHashMap<String, ActionConfig>();103 104     if (!parents.isEmpty()) {105       for (PackageConfig parent : parents) {106         retMap.putAll(parent.getAllActionConfigs());107       }108     }109 110     retMap.putAll(getActionConfigs());111 112     return retMap;113   }114 115   /**116    * returns the Map of all the global ResultConfigs available in the current package.117    * Global ResultConfigs defined in ancestor packages will be included in this Map.118    *119    * @return a Map of Result Objects with the result name as the key120    * @see ResultConfig121   */122   public Map<String, ResultConfig> getAllGlobalResults() {123     Map<String, ResultConfig> retMap = new LinkedHashMap<String, ResultConfig>();124 125     if (!parents.isEmpty()) {126       for (PackageConfig parentConfig : parents) {127         retMap.putAll(parentConfig.getAllGlobalResults());128       }129     }130 131     retMap.putAll(getGlobalResultConfigs());132 133     return retMap;134   }135 136   /**137    * returns the Map of all InterceptorConfigs and InterceptorStackConfigs available in the current package.138    * InterceptorConfigs defined in ancestor packages will be included in this Map.139    *140    * @return a Map of InterceptorConfig and InterceptorStackConfig Objects with the ref-name as the key141    * @see InterceptorConfig142    * @see InterceptorStackConfig143   */144   public Map<String, Object> getAllInterceptorConfigs() {145     Map<String, Object> retMap = new LinkedHashMap<String, Object>();146 147     if (!parents.isEmpty()) {148       for (PackageConfig parentContext : parents) {149         retMap.putAll(parentContext.getAllInterceptorConfigs());150       }151     }152 153     retMap.putAll(getInterceptorConfigs());154 155     return retMap;156   }157 158   /**159    * returns the Map of all the ResultTypeConfigs available in the current package.160    * ResultTypeConfigs defined in ancestor packages will be included in this Map.161    *162    * @return a Map of ResultTypeConfig Objects with the result type name as the key163    * @see ResultTypeConfig164   */165   public Map<String, ResultTypeConfig> getAllResultTypeConfigs() {166     Map<String, ResultTypeConfig> retMap = new LinkedHashMap<String, ResultTypeConfig>();167 168     if (!parents.isEmpty()) {169       for (PackageConfig parentContext : parents) {170         retMap.putAll(parentContext.getAllResultTypeConfigs());171       }172     }173 174     retMap.putAll(getResultTypeConfigs());175 176     return retMap;177   }178 179   /**180    * returns the List of all the ExceptionMappingConfigs available in the current package.181    * ExceptionMappingConfigs defined in ancestor packages will be included in this list.182    *183    * @return a List of ExceptionMappingConfigs Objects with the result type name as the key184    * @see ExceptionMappingConfig185   */186   public List<ExceptionMappingConfig> getAllExceptionMappingConfigs() {187     List<ExceptionMappingConfig> allExceptionMappings = new ArrayList<ExceptionMappingConfig>();188 189     if (!parents.isEmpty()) {190       for (PackageConfig parentContext : parents) {191         allExceptionMappings.addAll(parentContext.getAllExceptionMappingConfigs());192       }193     }194 195     allExceptionMappings.addAll(getGlobalExceptionMappingConfigs());196 197     return allExceptionMappings;198   }199 200 201   public String getDefaultInterceptorRef() {202     return defaultInterceptorRef;203   }204 205   public String getDefaultActionRef() {206     return defaultActionRef;207   }208 209   public String getDefaultClassRef() {210     if ((defaultClassRef == null) && !parents.isEmpty()) {211       for (PackageConfig parent : parents) {212         String parentDefault = parent.getDefaultClassRef();213         if (parentDefault != null) {214           return parentDefault;215         }216       }217     }218     return defaultClassRef;219   }220 221   /**222    * Returns the default result type for this package.223   */224   public String getDefaultResultType() {225     return defaultResultType;226   }227 228   /**229    * gets the default interceptor-ref name. If this is not set on this PackageConfig, it searches the parent230    * PackageConfigs in order until it finds one.231   */232   public String getFullDefaultInterceptorRef() {233     if ((defaultInterceptorRef == null) && !parents.isEmpty()) {234       for (PackageConfig parent : parents) {235         String parentDefault = parent.getFullDefaultInterceptorRef();236 237         if (parentDefault != null) {238           return parentDefault;239         }240       }241     }242 243     return defaultInterceptorRef;244   }245 246   /**247    * gets the default action-ref name. If this is not set on this PackageConfig, it searches the parent248    * PackageConfigs in order until it finds one.249   */250   public String getFullDefaultActionRef() {251     if ((defaultActionRef == null) && !parents.isEmpty()) {252       for (PackageConfig parent : parents) {253         String parentDefault = parent.getFullDefaultActionRef();254 255         if (parentDefault != null) {256           return parentDefault;257         }258       }259     }260     return defaultActionRef;261   }262 263   /**264    * Returns the default result type for this package.265    * <p/>266    * If there is no default result type, but this package has parents - we will try to267    * look up the default result type of a parent.268   */269   public String getFullDefaultResultType() {270     if ((defaultResultType == null) && !parents.isEmpty()) {271       for (PackageConfig parent : parents) {272         String parentDefault = parent.getFullDefaultResultType();273 274         if (parentDefault != null) {275           return parentDefault;276         }277       }278     }279 280     return defaultResultType;281   }282 283   /**284    * gets the global ResultConfigs local to this package285    *286    * @return a Map of ResultConfig objects keyed by result name287    * @see ResultConfig288   */289   public Map<String, ResultConfig> getGlobalResultConfigs() {290     return globalResultConfigs;291   }292 293   /**294    * gets the InterceptorConfigs and InterceptorStackConfigs local to this package295    *296    * @return a Map of InterceptorConfig and InterceptorStackConfig objects keyed by ref-name297    * @see InterceptorConfig298    * @see InterceptorStackConfig299   */300   public Map<String, Object> getInterceptorConfigs() {301     return interceptorConfigs;302   }303 304   public String getName() {305     return name;306   }307 308   public String getNamespace() {309     return namespace;310   }311 312   public List<PackageConfig> getParents() {313     return new ArrayList<PackageConfig>(parents);314   }315 316   /**317    * gets the ResultTypeConfigs local to this package318    *319    * @return a Map of ResultTypeConfig objects keyed by result name320    * @see ResultTypeConfig321   */322   public Map<String, ResultTypeConfig> getResultTypeConfigs() {323     return resultTypeConfigs;324   }325 326 327   public boolean isNeedsRefresh() {328     return needsRefresh;329   }330 331   /**332    * gets the ExceptionMappingConfigs local to this package333    *334    * @return a Map of ExceptionMappingConfig objects keyed by result name335    * @see ExceptionMappingConfig336   */337   public List<ExceptionMappingConfig> getGlobalExceptionMappingConfigs() {338     return globalExceptionMappingConfigs;339   }340 341   @Override342   public boolean equals(Object o) {343     if (this == o) {344       return true;345     }346 347     if (!(o instanceof PackageConfig)) {348       return false;349     }350 351     final PackageConfig packageConfig = (PackageConfig) o;352 353     if (isAbstract != packageConfig.isAbstract) {354       return false;355     }356 357     if ((actionConfigs != null) ? (!actionConfigs.equals(packageConfig.actionConfigs)) : (packageConfig.actionConfigs != null)) {358       return false;359     }360 361     if ((defaultResultType != null) ? (!defaultResultType.equals(packageConfig.defaultResultType)) : (packageConfig.defaultResultType != null)) {362       return false;363     }364 365     if ((defaultClassRef != null) ? (!defaultClassRef.equals(packageConfig.defaultClassRef)) : (packageConfig.defaultClassRef != null)) {366       return false;367     }368 369     if ((globalResultConfigs != null) ? (!globalResultConfigs.equals(packageConfig.globalResultConfigs)) : (packageConfig.globalResultConfigs != null)) {370       return false;371     }372 373     if ((interceptorConfigs != null) ? (!interceptorConfigs.equals(packageConfig.interceptorConfigs)) : (packageConfig.interceptorConfigs != null)) {374       return false;375     }376 377     if ((name != null) ? (!name.equals(packageConfig.name)) : (packageConfig.name != null)) {378       return false;379     }380 381     if ((namespace != null) ? (!namespace.equals(packageConfig.namespace)) : (packageConfig.namespace != null)) {382       return false;383     }384 385     if ((parents != null) ? (!parents.equals(packageConfig.parents)) : (packageConfig.parents != null)) {386       return false;387     }388 389     if ((resultTypeConfigs != null) ? (!resultTypeConfigs.equals(packageConfig.resultTypeConfigs)) : (packageConfig.resultTypeConfigs != null)) {390       return false;391     }392 393     if ((globalExceptionMappingConfigs != null) ? (!globalExceptionMappingConfigs.equals(packageConfig.globalExceptionMappingConfigs)) : (packageConfig.globalExceptionMappingConfigs != null)) {394       return false;395     }396 397     return true;398   }399 400   @Override401   public int hashCode() {402     int result;403     result = ((name != null) ? name.hashCode() : 0);404     result = (29 * result) + ((parents != null) ? parents.hashCode() : 0);405     result = (29 * result) + ((actionConfigs != null) ? actionConfigs.hashCode() : 0);406     result = (29 * result) + ((globalResultConfigs != null) ? globalResultConfigs.hashCode() : 0);407     result = (29 * result) + ((interceptorConfigs != null) ? interceptorConfigs.hashCode() : 0);408     result = (29 * result) + ((resultTypeConfigs != null) ? resultTypeConfigs.hashCode() : 0);409     result = (29 * result) + ((globalExceptionMappingConfigs != null) ? globalExceptionMappingConfigs.hashCode() : 0);410     result = (29 * result) + ((defaultResultType != null) ? defaultResultType.hashCode() : 0);411     result = (29 * result) + ((defaultClassRef != null) ? defaultClassRef.hashCode() : 0);412     result = (29 * result) + ((namespace != null) ? namespace.hashCode() : 0);413     result = (29 * result) + (isAbstract ? 1 : 0);414 415     return result;416   }417 418   @Override419   public String toString() {420     return "PackageConfig: [" + name + "] for namespace [" + namespace + "] with parents [" + parents + "]";421   }422 423   public int compareTo(Object o) {424     PackageConfig other = (PackageConfig) o;425     String full = namespace + "!" + name;426     String otherFull = other.namespace + "!" + other.name;427 428     // note, this isn't perfect (could come from different parents), but it is "good enough"429     return full.compareTo(otherFull);430   }431 432   public Object getInterceptorConfig(String name) {433     return getAllInterceptorConfigs().get(name);434   }435 436   /**437    * The builder for this object. An instance of this object is the only way to construct a new instance. The438    * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining.439    * After setting any values you need, call the {@link #build()} method to create the object.440   */441   public static class Builder implements InterceptorLocator {442 443     protected PackageConfig target;444     private boolean strictDMI;445 446     public Builder(String name) {447       target = new PackageConfig(name);448     }449 450     public Builder(PackageConfig config) {451       target = new PackageConfig(config);452     }453 454     public Builder name(String name) {455       target.name = name;456       return this;457     }458 459     public Builder isAbstract(boolean isAbstract) {460       target.isAbstract = isAbstract;461       return this;462     }463 464     public Builder defaultInterceptorRef(String name) {465       target.defaultInterceptorRef = name;466       return this;467     }468 469     public Builder defaultActionRef(String name) {470       target.defaultActionRef = name;471       return this;472     }473 474     public Builder defaultClassRef(String defaultClassRef) {475       target.defaultClassRef = defaultClassRef;476       return this;477     }478 479     /**480      * sets the default Result type for this package481      *482      * @param defaultResultType483     */484     public Builder defaultResultType(String defaultResultType) {485       target.defaultResultType = defaultResultType;486       return this;487     }488 489     public Builder namespace(String namespace) {490       if (namespace == null) {491         target.namespace = "";492       } else {493         target.namespace = namespace;494       }495       return this;496     }497 498     public Builder needsRefresh(boolean needsRefresh) {499       target.needsRefresh = needsRefresh;500       return this;501     }502 503     public Builder addActionConfig(String name, ActionConfig action) {504       target.actionConfigs.put(name, action);505       return this;506     }507 508     public Builder addParents(List<PackageConfig> parents) {509       for (PackageConfig config : parents) {510         addParent(config);511       }512       return this;513     }514 515     public Builder addGlobalResultConfig(ResultConfig resultConfig) {516       target.globalResultConfigs.put(resultConfig.getName(), resultConfig);517       return this;518     }519 520     public Builder addGlobalResultConfigs(Map<String, ResultConfig> resultConfigs) {521       target.globalResultConfigs.putAll(resultConfigs);522       return this;523     }524 525     public Builder addExceptionMappingConfig(ExceptionMappingConfig exceptionMappingConfig) {526       target.globalExceptionMappingConfigs.add(exceptionMappingConfig);527       return this;528     }529 530     public Builder addGlobalExceptionMappingConfigs(List<ExceptionMappingConfig> exceptionMappingConfigs) {531       target.globalExceptionMappingConfigs.addAll(exceptionMappingConfigs);532       return this;533     }534 535     public Builder addInterceptorConfig(InterceptorConfig config) {536       target.interceptorConfigs.put(config.getName(), config);537       return this;538     }539 540     public Builder addInterceptorStackConfig(InterceptorStackConfig config) {541       target.interceptorConfigs.put(config.getName(), config);542       return this;543     }544 545     public Builder addParent(PackageConfig parent) {546       target.parents.add(0, parent);547       return this;548     }549 550     public Builder addResultTypeConfig(ResultTypeConfig config) {551       target.resultTypeConfigs.put(config.getName(), config);552       return this;553     }554 555     public Builder location(Location loc) {556       target.location = loc;557       return this;558     }559 560     public boolean isNeedsRefresh() {561       return target.needsRefresh;562     }563 564     public String getDefaultClassRef() {565       return target.defaultClassRef;566     }567 568     public String getName() {569       return target.name;570     }571 572     public String getNamespace() {573       return target.namespace;574     }575 576     public String getFullDefaultResultType() {577       return target.getFullDefaultResultType();578     }579 580     public ResultTypeConfig getResultType(String type) {581       return target.getAllResultTypeConfigs().get(type);582     }583 584     public Object getInterceptorConfig(String name) {585       return target.getAllInterceptorConfigs().get(name);586     }587 588     public Builder strictMethodInvocation(boolean strict) {589       strictDMI = strict;590       return this;591     }592 593     public boolean isStrictMethodInvocation() {594       return strictDMI;595     }596 597     public PackageConfig build() {598       embalmTarget();599       PackageConfig result = target;600       target = new PackageConfig(result);601       return result;602     }603 604     protected void embalmTarget() {605       target.actionConfigs = Collections.unmodifiableMap(target.actionConfigs);606       target.globalResultConfigs = Collections.unmodifiableMap(target.globalResultConfigs);607       target.interceptorConfigs = Collections.unmodifiableMap(target.interceptorConfigs);608       target.resultTypeConfigs = Collections.unmodifiableMap(target.resultTypeConfigs);609       target.globalExceptionMappingConfigs = Collections.unmodifiableList(target.globalExceptionMappingConfigs);610       target.parents = Collections.unmodifiableList(target.parents);611     }612 613     @Override614     public String toString() {615       return "[BUILDER] " + target.toString();616     }617   }618 619 }

    比如上面这个类,是struts对于package的一个封装,并同时在内部提供了一个builder的静态内部类,同时将外部类的构造函数声明为protected这样避免外部随便定义对象,并通过内部类中创建一个外部类的对象。这种方式很普遍。

 

  那么总结下来,struts对于相同包,相同命名空间的,同名action,在项目启动的时候是不进行报错提示的,不知道算不算是个bug,这样对于注解方式的项目,假如目前现在有一个类文件,其中定义了很多action,这时候直接把该文件复制一下,然后修改名字后,在新文件中添加新的action,原先的action不删除的话,这样就存在两套了,假如只改了一套逻辑,那么问题就会出现了,就会好出现我们这次遇到的问题,修改的代码可能不生效(当然这次我遇到的问题就是这么来的,之前的同事做这块的时候由于新增了一个与原先的页面类似的功能,但是会有额外的逻辑,于是便直接复制粘贴了一份,也没有进行重新命名,这样便导致了后面的事情的发生,虽然定位到两个重名的action猜测会是这个原因,但是具体的道理就很难找了,只能看源码了,不过看源码学会了很多东西,窃喜),明白了struts用hashset存储之后进行的解析,也就是说明会存在偶然性,于是在自己的机器上实验,不停的重启测试重启测试重启测试,终于先,的确两套逻辑中的代码都可能进入的。到这里也就彻底大悟了。

  总结:struts对于相同包,相同命名空间的,同名action,不会报错,并且用注解方式实现的话,具体用哪个类中的action具有偶然性,因为struts是用hashset存储后进行解析的。