EricLuoisme commented on issue #5829:
URL: https://github.com/apache/dubbo/issues/5829#issuecomment-2308658942

   For those still using Dubbo 2.7.x and encountering similar issues, there is 
a **workaround** involving **Pre-loading of necessary SPIs by a thread with the 
'correct' class loader**:
   
   If you're using **SpringBoot** integrated with Dubbo, you can add the 
following code right after the service is ready(e.g. attach it with the 
SmartLiftcycle interface). This will ensure that the code is executed by a 
Spring-managed thread, which uses the same class loader as Dubbo:
   
   ## Pre-load solution
   > You may also need to set serialization manually: 
**dubbo.protocol.serialization=hessian2**
   
   ```java
   @Slf4j
   @Configuration
   public class CustomLifeCycle implements SmartLifecycle {
   
       @Override
       public void start() {
           // under executable .jar file, the classloader for this thread would 
be 'LaunchedURLClassLoader', 
           // the top classloader for Spring Application
           ExtensionLoader.getExtensionLoader(LoadBalance.class)
                   .getDefaultExtensionName();
       }
   
       @Override
       public void stop() {
       }
   
       @Override
       public boolean isRunning() {
           return false;
       }
   }
   
   ```
   
   ## What cause this?
   Under Dubbo 2.7.17 version, the source code of 
org.apache.dubbo.common.extension.ExtensionLoader#getExtensionClasses is taking 
the Dubbo-Check-Lock(DCL) strategy, and Dubbo only loads needed SPI when needed 
(similar to _lazy initialization_):
   ```java
   private Map<String, Class<?>> getExtensionClasses() {
       Map<String, Class<?>> classes = cachedClasses.get();
       if (classes == null) {
           synchronized (cachedClasses) {
               classes = cachedClasses.get();
               if (classes == null) {
                   classes = loadExtensionClasses(); // loadExtensioClasses() 
would never return NULL
                   cachedClasses.set(classes);
               }
           }
       }
       return classes;
   }
   
   ```
   As the replies above mentioned, inner logic for 
org.apache.dubbo.common.extension.ExtensionLoader#loadExtensionClasses
       
   ```java
   /**
    * synchronized in getExtensionClasses
    */
   private Map<String, Class<?>> loadExtensionClasses() {
       cacheDefaultExtensionName();
   
       // never return NULL
       Map<String, Class<?>> extensionClasses = new HashMap<>();
   
       for (LoadingStrategy strategy : strategies) {
           // inside the loadDirectory function's logic, it would call 
classloader
           loadDirectory(extensionClasses, strategy.directory(), 
type.getName(), strategy.preferExtensionClassLoader(),
                   strategy.overridden(), strategy.excludedPackages());
           loadDirectory(extensionClasses, strategy.directory(), 
type.getName().replace("org.apache", "com.alibaba"),
                   strategy.preferExtensionClassLoader(), 
strategy.overridden(), strategy.excludedPackages());
       }
   
       return extensionClasses;
   }
   ```
   would use calling thread's class loader in 
   org.apache.dubbo.common.utils.ClassUtils#getClassLoader(java.lang.Class<?>)
       
   ```java
   public static ClassLoader getClassLoader(Class<?> clazz) {
       ClassLoader cl = null;
       try {
           // For forkJoinPool's thread, the contextClassLoader would be: 
AppClassLoader (JDK9+)
           // But Dubbo with Spring integration is under Spring's handling, 
which means we could load Dubbo-implemented SPIs with classloader: 
LaunchedURLClassLoader (or classloaders's parent is it)
           cl = Thread.currentThread().getContextClassLoader();
       } catch (Throwable ex) {
           // Cannot access thread context ClassLoader - falling back to system 
class loader...
       }
       if (cl == null) {
           // No thread context class loader -> use class loader of this class.
           cl = clazz.getClassLoader();
           if (cl == null) {
               // getClassLoader() returning null indicates the bootstrap 
ClassLoader
               try {
                   cl = ClassLoader.getSystemClassLoader();
               } catch (Throwable ex) {
                   // Cannot access system ClassLoader - oh well, maybe the 
caller can live with null...
               }
           }
       }
   
       return cl;
   }
   ```
   So the **first Thread call the getExtensionClasses is crucial**, or else 
incorrect classloader return would affect all later requests 
(loadExtensionClasses would never return null). 
   
   ## Why the workaround works?
   Thus, the above workaround is using the Spring-handled thread (classloader 
would be LaunchedURLClassLoader in the executable jar file) to initialise that 
cachedClasses because:
   
   ```java
   public String getDefaultExtensionName() {
       // Let the Spring-handled thread init the DCL code
       getExtensionClasses();
       return cachedDefaultName;
   }
   ```
   
   ## Did Dubbo 3.x solve this?
   They do make some changes in the ClassUtils (3.2 ver), by the class which 
start with "org.apache.dubbo", they just use the target class's classloader 
rather than get if from current Thread.
   ```java
   public static ClassLoader getClassLoader(Class<?> clazz) {
           ClassLoader cl = null;
           if (!clazz.getName().startsWith("org.apache.dubbo")) {
               cl = clazz.getClassLoader();
           }
           if (cl == null) {
               try {
                   cl = Thread.currentThread().getContextClassLoader();
               } catch (Exception ignored) {
                   // Cannot access thread context ClassLoader - falling back 
to system class loader...
               }
               if (cl == null) {
                   // No thread context class loader -> use class loader of 
this class.
                   cl = clazz.getClassLoader();
                   if (cl == null) {
                       // getClassLoader() returning null indicates the 
bootstrap ClassLoader
                       try {
                           cl = ClassLoader.getSystemClassLoader();
                       } catch (Exception ignored) {
                           // Cannot access system ClassLoader - oh well, maybe 
the caller can live with null...
                       }
                   }
               }
           }
   
           return cl;
       }
   ```
   
   Hope this comment helps anyone struggling with this :)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to