idae父子项目Test执行报Result Maps collection already contains value for xxx

现象:同一个springmvc工程使用eclipse和idea用Tomcat启动都没问题,但是如果走单元测试使用到了@ContextConfiguration这个spring的上下文注解idea出问题了,eclipse没问题;由于最近才使用idea;只能先百度一下根据现象,发现天下文章一大抄。。。还得自己慢慢追!

第一阶段:根据提示基本断定这个map的id被加载了两次,但是全文搜索了一下确实只有一个id,排除传统的手残;eclipse没问题也能证明这点,这里就猜测很大可能是idea哪里需要设置一下,但是网上没找到

第二阶段:看日志追源码,出错定位在parse map阶段,如图:

初步判断在idea执行test的时候,加载了2遍spring的相关mapper.xml,继续追,根据异常定位到 PathMatchingResourcePatternResolver 这个类,他主要进行资源的匹配,主要入口方法

他基本做了两个件事情,1)把所有的不重复的资源用set收集起来,2)针对收集的资源进行一次xml的筛选(set),再接下去就是解析parse不在这个类(根据这里提供的xml进行逐一(xml里面的相关id)解析并记录,如果出现重复的则报异常题目异常)

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
        String rootDirPath = determineRootDir(locationPattern);
        String subPattern = locationPattern.substring(rootDirPath.length());
     //getResources()这个方法收集所有的资源 Resource[] rootDirResources
= getResources(rootDirPath); Set<Resource> result = new LinkedHashSet<Resource>(16); for (Resource rootDirResource : rootDirResources) { rootDirResource = resolveRootDirResource(rootDirResource); URL rootDirURL = rootDirResource.getURL(); if (equinoxResolveMethod != null) { if (rootDirURL.getProtocol().startsWith("bundle")) { rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirURL); rootDirResource = new UrlResource(rootDirURL); } } if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher())); } else if (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) { result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern)); } else { result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern)); } } if (logger.isDebugEnabled()) { logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result); } return result.toArray(new Resource[result.size()]);
Resource[] rootDirResources = getResources(rootDirPath);发现在执行完这个方法的时候里面出现了2次相同的jar包,表现形式不一样而已,例如:
URL [jar:file:/E:/Program%20Files/JetBrains/IntelliJ%20IDEA%202018.3/lib/idea_rt.jar!/]
URL [jar:file:E:Program FilesJetBrainsIntelliJ IDEA 2018.3libidea_rt.jar!/]

看到这里傻眼了,为什么位置相同的包会都能加进去,这里就想:要么是我那里没注意到idea配置,要么就是spring4.3.8的bug;回过头来想,这两条居然都言中了

第三阶段:定位、分析,进入getResources看了一下,第一次通过classLoader(AppClasssLoader顶级的)得到的url集合((URLClassLoader) classLoader).getURLs()进行条件筛选,第二次通过判断是否是系统的calssloader进行再次的add结果,如下:
protected void addAllClassLoaderJarRoots(ClassLoader classLoader, Set<Resource> result) {
        if (classLoader instanceof URLClassLoader) {
            try {
                for (URL url : ((URLClassLoader) classLoader).getURLs()) {
                    try {
                        UrlResource jarResource = new UrlResource(
                   //url自动在文件根目录前加/,例如:file:/E:/Program%20Files/JetBrains/IntelliJ%20IDEA%202018.3/lib/idea_rt.jar ResourceUtils.JAR_URL_PREFIX
+ url.toString() + ResourceUtils.JAR_URL_SEPARATOR); if (jarResource.exists()) { result.add(jarResource); } } catch (MalformedURLException ex) { if (logger.isDebugEnabled()) { logger.debug("Cannot search for matching files underneath [" + url + "] because it cannot be converted to a valid 'jar:' URL: " + ex.getMessage()); } } } } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Cannot introspect jar files since ClassLoader [" + classLoader + "] does not support 'getURLs()': " + ex); } } }      //如果是系统的loader继续添加 if (classLoader == ClassLoader.getSystemClassLoader()) { // "java.class.path" manifest evaluation... addClassPathManifestEntries(result); }
。。。

protected void addClassPathManifestEntries(Set<Resource> result) {
try {
String javaClassPathProperty = System.getProperty("java.class.path");
for (String path : StringUtils.delimitedListToStringArray(
javaClassPathProperty, System.getProperty("path.separator"))) {
try {
File file = new File(path);
UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
ResourceUtils.FILE_URL_PREFIX + file.getAbsolutePath() +
ResourceUtils.JAR_URL_SEPARATOR);
if (jarResource.exists()) {
         //继续添加,这里会判断hashCode,因为set底层是一个hashMap,UrlResource有一个cleanedurl,通过它获取hashcode
         //能添加进去说明确实hashcode不一致,即他们的cleanedurl(就是一个文件路路径,类似jar:file:/E:/Program%20Files/JetBrains/IntelliJ%20IDEA%202018.3/lib/idea_rt.jar)不一样
result.add(jarResource);
}
}

上面这两个方法都是先new一个UrlResource,然后放到result中去,但是有个细节这里决定了两个对象的hashcode不一样:url.toString()就想我注释里写得一样它会在路径开始加一个'/',最后导致出现以下的结果:

jar:file:/E:/Program Files/JetBrains/IntelliJ IDEA 2018.3/lib/idea_rt.jar

jar:file:E:Program FilesJetBrainsIntelliJ IDEA 2018.3libidea_rt.jar

第四阶段:破局,好了,idea的原因找到了,为什么idea用tomcat启动没问题,eclipse怎么启动都没问题,先从简单入手,idea直接用tomcat启动发现他们的类加载器是tomcat自带的parallelxxX不属于系统类加载器,所以不执行addClassPathManifestEntries这个方法,

那eclipse又为什么test也能执行呢?继续追,发现eclipse在执行方法addClassPathManifestEntries中 String javaClassPathProperty = System.getProperty("java.class.path");代码的时候javaClassPathProperty这里面父类的路径居然是eclipse的路径(xxx/classs)而不是我的maven仓库的jar包路径(xxx/xx.jar),不是jar自然就不放到result中去了,到这里有点懵,但是知道有个方向,那就是去找eclipse和idea的管理项目的不同,eclipse父子项目是不需要打jar包的,但是我的idea是开了2个窗口,父项目必须打jar包才能运行,可不可以idea也不用打jar包,果然有解决方案(父子都在同一个窗口中),我就不写了随便找个说明(https://blog.csdn.net/nianbingsihan/article/details/82772801)这里注意一下,父项目如果是多个pom文件要全部引入,如果只引入最顶层的pom的话,其它的子项目是灰色表示没起作用,还是会走jar包,验证一下,把maven仓库中的jar包删除,果然不报错,运行test程序,终于见到了久违的通过!

原文地址:https://www.cnblogs.com/aishangyizhihu/p/11333692.html