I have a multimodule project called k-sdk
. It has modules like
---> agent
---> core
---> api
---> integration
---> sdk
where (sdk module contains core, api, integration modules and acting as a shaded jar). This jar can be imported as a dependency in the user application.
Here k-sdk
is acting as a middleware to capture request
and response
of the api calls (both external and internal) and do some custom processing. And to intercept the internal calls i am using servlet-filter
& for external calls, i am using ByteBuddy and wrote the required code in the premain
method. The corresponding interceptors are written in integration module. User can use this k-sdk
by running agent jar(present in agent module) of the k-sdk
.
Now comes to the actual problem. Suppose I have written an Interceptor for OkHttp
client version 4.10.0
in the integration
module and the user is using the same client of version 3.14.9
, now these two versions share completely same package name. And there are some uncommon apis, methods etc, which is being used in the OkHttpInterceptor
which results in java.lang.NoSuchMethodError because maven only resolves that version of dependency which has the shortest path. And only 1 version of a dependency will be loaded in the classloader.
Now, To resolve this version conflict what i was thinking that i will use the below custom classloader. (ref) as i know that the solution lies in this approach only.
public class ChildFirstClassLoader extends URLClassLoader {
private final ClassLoader sysClzLoader;
public ChildFirstClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
sysClzLoader = getSystemClassLoader();
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// has the class loaded already?
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
try {
if (sysClzLoader != null) {
loadedClass = sysClzLoader.loadClass(name);
}
} catch (ClassNotFoundException ex) {
// class not found in system class loader... silently skipping
}
try {
// find the class from given jar urls as in first constructor parameter.
if (loadedClass == null) {
loadedClass = findClass(name);
}
} catch (ClassNotFoundException e) {
// class is not found in the given urls.
// Let's try it in parent classloader.
// If class is still not found, then this method will throw class not found ex.
loadedClass = super.loadClass(name, resolve);
}
}
if (resolve) { // marked to resolve
resolveClass(loadedClass);
}
return loadedClass;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
List<URL> allRes = new LinkedList<>();
// load resources from sys class loader
Enumeration<URL> sysResources = sysClzLoader.getResources(name);
if (sysResources != null) {
while (sysResources.hasMoreElements()) {
allRes.add(sysResources.nextElement());
}
}
// load resource from this classloader
Enumeration<URL> thisRes = findResources(name);
if (thisRes != null) {
while (thisRes.hasMoreElements()) {
allRes.add(thisRes.nextElement());
}
}
// then try finding resources from parent classloaders
Enumeration<URL> parentRes = super.findResources(name);
if (parentRes != null) {
while (parentRes.hasMoreElements()) {
allRes.add(parentRes.nextElement());
}
}
return new Enumeration<URL>() {
Iterator<URL> it = allRes.iterator();
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public URL nextElement() {
return it.next();
}
};
}
@Override
public URL getResource(String name) {
URL res = null;
if (sysClzLoader != null) {
res = sysClzLoader.getResource(name);
}
if (res == null) {
res = findResource(name);
}
if (res == null) {
res = super.getResource(name);
}
return res;
}
}
And to improve the user experience, i have to make changes only in the premain
method. Now i don't know how to use this classloader to resolve this conflicting issue.
The above classloader is extending URLClassLoader
, and the application class loader is no longer an instance of java.net.URLClassLoader
in java 9+.
I am very new to this complex solution, Please! help me out.
Before this solution, i tried to repackage classes using maven-shade-plugin
but that solution was not working because in premain
method using ByteBuddy i was targeting a class of actual package and was using different package in the interceptor so the flow was not going to the interceptor as the class was not matching.