当前位置: 代码迷 >> Java相关 >> struts中action姓名重复导致的奇异事件
  详细解决方案

struts中action姓名重复导致的奇异事件

热度:51   发布时间:2016-04-22 19:56:33.0
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.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         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存储后进行解析的。

1楼CoderToSurvive
目前为止研究过的源码最深的一次,先自己顶一顶
  相关解决方案