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]