nosqlcoco / springboot-weapp-demo

微信小程序服务端接口,支持普通Http请求、上传文件、长连接。
225 stars 126 forks source link

Dependency Conflict: duplicate classes "org.apache.commons.logging.LogFactory.getFactory" in different JARs, have different implementations #3

Open HelloCoCooo opened 4 years ago

HelloCoCooo commented 4 years ago

Hi, in springboot-weapp-demo, duplicate classes with the same fully-qualified name org.apache.commons.logging.LogFactory.getFactory are included in two different libraries, i.e., commons-logging:commons-logging:1.2 and org.slf4j:jcl-over-slf4j:1.7.21.

According to "first declaration wins" class loading strategy, only this class in org.slf4j:jcl-over-slf4j:1.7.21 can be loaded, and that in commons-logging:commons-logging:1.2 will be shadowed.

By further analyzing, your project expects to invoke method org.apache.commons.logging.LogFactory.getFactory in commons-logging:commons-logging:1.2. As it has been shadowed, so that this method defined in org.slf4j:jcl-over-slf4j:1.7.21 are actually forced to be referenced via the following invocation path:

<com.weapp.Application: main([Ljava/lang/String;)V> /root/sensor/unzip/springboot-weapp-demo-master/target/classes
<org.springframework.boot.SpringApplication: <clinit>()V> /root/.m2/repository/org/springframework/boot/spring-boot/1.4.2.RELEASE/spring-boot-1.4.2.RELEASE.jar
<org.apache.commons.logging.LogFactory: getLog(Ljava/lang/Class;)Lorg/apache/commons/logging/Log;> /root/.m2/repository/commons-logging/commons-logging/1.2/commons-logging-1.2.jar
<org.apache.commons.logging.LogFactory: getFactory()Lorg/apache/commons/logging/LogFactory;>

Workaround solution: An easy way to workaround the problem is reversing the declaration order of these two libraries (i.e., reverse the declaration order of httpclient and maven-resolver-transport-http) in pom file. Then, according to "first declaration wins" class loading strategy, class org.apache.commons.logging.LogFactory.getFactory in commons-logging:commons-logging:1.2 can be loaded (the version that springboot-weapp-demo expects to reference by static analysis). This fix will not affect other libraries or class, except the above duplicate class.

Dependency tree---

[INFO] com.wxapp:wxapp:jar:1.0.0 [INFO] +- org.springframework.boot:spring-boot-starter:jar:1.4.2.RELEASE:compile [INFO] | +- org.springframework.boot:spring-boot:jar:1.4.2.RELEASE:compile [INFO] | | - org.springframework:spring-context:jar:4.3.4.RELEASE:compile [INFO] | +- org.springframework.boot:spring-boot-autoconfigure:jar:1.4.2.RELEASE:compile [INFO] | +- org.springframework:spring-core:jar:4.3.4.RELEASE:compile [INFO] | - org.yaml:snakeyaml:jar:1.17:runtime [INFO] +- org.springframework.boot:spring-boot-starter-websocket:jar:1.4.2.RELEASE:compile [INFO] | +- org.springframework:spring-messaging:jar:4.3.4.RELEASE:compile [INFO] | | - org.springframework:spring-beans:jar:4.3.4.RELEASE:compile [INFO] | - org.springframework:spring-websocket:jar:4.3.4.RELEASE:compile [INFO] +- org.springframework.boot:spring-boot-starter-data-mongodb:jar:1.4.2.RELEASE:compile [INFO] | +- org.mongodb:mongodb-driver:jar:3.2.2:compile [INFO] | | +- org.mongodb:mongodb-driver-core:jar:3.2.2:compile [INFO] | | - org.mongodb:bson:jar:3.2.2:compile [INFO] | - org.springframework.data:spring-data-mongodb:jar:1.9.5.RELEASE:compile [INFO] | +- org.springframework:spring-tx:jar:4.3.4.RELEASE:compile [INFO] | +- org.springframework:spring-expression:jar:4.3.4.RELEASE:compile [INFO] | +- org.springframework.data:spring-data-commons:jar:1.12.5.RELEASE:compile [INFO] | - org.slf4j:jcl-over-slf4j:jar:1.7.21:runtime ...... [INFO] +- com.alibaba:fastjson:jar:1.2.31:compile [INFO] +- org.projectlombok:lombok:jar:1.16.10:compile [INFO] +- com.google.guava:guava:jar:19.0:compile [INFO] +- commons-logging:commons-logging:jar:1.2:compile ......

Thank you very much. Best, Coco

HelloCoCooo commented 4 years ago

Code snippet of org.apache.commons.logging.LogFactory.getFactory in commons-logging:commons-logging:1.2 (shadowed but expected to invoke method):

public static LogFactory getFactory() throws LogConfigurationException {    //line 417
  // Identify the class loader we will be using
  ClassLoader contextClassLoader = getContextClassLoaderInternal();

  if (contextClassLoader == null) {
    // This is an odd enough situation to report about. This
    // output will be a nuisance on JDK1.1, as the system
    // classloader is null in that environment.
    if (isDiagnosticsEnabled()) {
      logDiagnostic("Context classloader is null.");
    }
  }

  // Return any previously registered factory for this class loader
  LogFactory factory = getCachedFactory(contextClassLoader);
  if (factory != null) {
    return factory;
  }

  if (isDiagnosticsEnabled()) {
    logDiagnostic(
      "[LOOKUP] LogFactory implementation requested for the first time for context classloader " +
      objectId(contextClassLoader));
    logHierarchy("[LOOKUP] ", contextClassLoader);
  }

  // Load properties file.
  //
  // If the properties file exists, then its contents are used as
  // "attributes" on the LogFactory implementation class. One particular
  // property may also control which LogFactory concrete subclass is
  // used, but only if other discovery mechanisms fail..
  //
  // As the properties file (if it exists) will be used one way or
  // another in the end we may as well look for it first.

  Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);

  // Determine whether we will be using the thread context class loader to
  // load logging classes or not by checking the loaded properties file (if any).
  ClassLoader baseClassLoader = contextClassLoader;
  if (props != null) {
    String useTCCLStr = props.getProperty(TCCL_KEY);
    if (useTCCLStr != null) {
      // The Boolean.valueOf(useTCCLStr).booleanValue() formulation
      // is required for Java 1.2 compatibility.
      if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
        // Don't use current context classloader when locating any
        // LogFactory or Log classes, just use the class that loaded
        // this abstract class. When this class is deployed in a shared
        // classpath of a container, it means webapps cannot deploy their
        // own logging implementations. It also means that it is up to the
        // implementation whether to load library-specific config files
        // from the TCCL or not.
        baseClassLoader = thisClassLoader;
      }
    }
  }

  // Determine which concrete LogFactory subclass to use.
  // First, try a global system property
  if (isDiagnosticsEnabled()) {
    logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY +
                  "] to define the LogFactory subclass to use...");
  }

  try {
    String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
    if (factoryClass != null) {
      if (isDiagnosticsEnabled()) {
        logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass +
                      "' as specified by system property " + FACTORY_PROPERTY);
      }
      factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
    } else {
      if (isDiagnosticsEnabled()) {
        logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined.");
      }
    }
  } catch (SecurityException e) {
    if (isDiagnosticsEnabled()) {
      logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" +
                    " instance of the custom factory class" + ": [" + trim(e.getMessage()) +
                    "]. Trying alternative implementations...");
    }
    // ignore
  } catch (RuntimeException e) {
    // This is not consistent with the behaviour when a bad LogFactory class is
    // specified in a services file.
    //
    // One possible exception that can occur here is a ClassCastException when
    // the specified class wasn't castable to this LogFactory type.
    if (isDiagnosticsEnabled()) {
      logDiagnostic("[LOOKUP] An exception occurred while trying to create an" +
                    " instance of the custom factory class" + ": [" +
                    trim(e.getMessage()) +
                    "] as specified by a system property.");
    }
    throw e;
  }

  // Second, try to find a service by using the JDK1.3 class
  // discovery mechanism, which involves putting a file with the name
  // of an interface class in the META-INF/services directory, where the
  // contents of the file is a single line specifying a concrete class
  // that implements the desired interface.

  if (factory == null) {
    if (isDiagnosticsEnabled()) {
      logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID +
                    "] to define the LogFactory subclass to use...");
    }
    try {
      final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);

      if( is != null ) {
        // This code is needed by EBCDIC and other strange systems.
        // It's a fix for bugs reported in xerces
        BufferedReader rd;
        try {
          rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        } catch (java.io.UnsupportedEncodingException e) {
          rd = new BufferedReader(new InputStreamReader(is));
        }

        String factoryClassName = rd.readLine();
        rd.close();

        if (factoryClassName != null && ! "".equals(factoryClassName)) {
          if (isDiagnosticsEnabled()) {
            logDiagnostic("[LOOKUP]  Creating an instance of LogFactory class " +
                          factoryClassName +
                          " as specified by file '" + SERVICE_ID +
                          "' which was present in the path of the context classloader.");
          }
          factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
        }
      } else {
        // is == null
        if (isDiagnosticsEnabled()) {
          logDiagnostic("[LOOKUP] No resource file with name '" + SERVICE_ID + "' found.");
        }
      }
    } catch (Exception ex) {
      // note: if the specified LogFactory class wasn't compatible with LogFactory
      // for some reason, a ClassCastException will be caught here, and attempts will
      // continue to find a compatible class.
      if (isDiagnosticsEnabled()) {
        logDiagnostic(
          "[LOOKUP] A security exception occurred while trying to create an" +
          " instance of the custom factory class" +
          ": [" + trim(ex.getMessage()) +
          "]. Trying alternative implementations...");
      }
      // ignore
    }
  }

  // Third try looking into the properties file read earlier (if found)

  if (factory == null) {
    if (props != null) {
      if (isDiagnosticsEnabled()) {
        logDiagnostic(
          "[LOOKUP] Looking in properties file for entry with key '" + FACTORY_PROPERTY +
          "' to define the LogFactory subclass to use...");
      }
      String factoryClass = props.getProperty(FACTORY_PROPERTY);
      if (factoryClass != null) {
        if (isDiagnosticsEnabled()) {
          logDiagnostic(
            "[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'");
        }
        factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);

        // TODO: think about whether we need to handle exceptions from newFactory
      } else {
        if (isDiagnosticsEnabled()) {
          logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass.");
        }
      }
    } else {
      if (isDiagnosticsEnabled()) {
        logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from..");
      }
    }
  }

  // Fourth, try the fallback implementation class

  if (factory == null) {
    if (isDiagnosticsEnabled()) {
      logDiagnostic(
        "[LOOKUP] Loading the default LogFactory implementation '" + FACTORY_DEFAULT +
        "' via the same classloader that loaded this LogFactory" +
        " class (ie not looking in the context classloader).");
    }

    // Note: unlike the above code which can try to load custom LogFactory
    // implementations via the TCCL, we don't try to load the default LogFactory
    // implementation via the context classloader because:
    // * that can cause problems (see comments in newFactory method)
    // * no-one should be customising the code of the default class
    // Yes, we do give up the ability for the child to ship a newer
    // version of the LogFactoryImpl class and have it used dynamically
    // by an old LogFactory class in the parent, but that isn't
    // necessarily a good idea anyway.
    factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
  }

  if (factory != null) {
    /**
             * Always cache using context class loader.
             */
    cacheFactory(contextClassLoader, factory);

    if (props != null) {
      Enumeration names = props.propertyNames();
      while (names.hasMoreElements()) {
        String name = (String) names.nextElement();
        String value = props.getProperty(name);
        factory.setAttribute(name, value);
      }
    }
  }

  return factory;
}

Code snippet of org.apache.commons.logging.LogFactory.getFactory in org.slf4j:jcl-over-slf4j:1.7.21 (loaded version):

static LogFactory logFactory = new SLF4JLogFactory();   //line 41
......
public static LogFactory getFactory() throws LogConfigurationException {    //line 257
  return logFactory;
}

As a result, these conflicting method included in commons-logging:commons-logging:1.2 deals with different cases, which changes the control flows and data flows. So being forced to use these methods in org.slf4j:jcl-over-slf4j:1.7.21 may lead to inconsisitent semantic behaviors.

HelloCoCooo commented 4 years ago

@nosqlcoco May I pull a request to fix it?

nosqlcoco commented 4 years ago

@nosqlcoco May I pull a request to fix it?

Hi @HelloCoCooo. you can pull a request, I will testing in my machine. thanks.