ninia / jep

Embed Python in Java
Other
1.33k stars 150 forks source link

jep.JepException: <class 'ModuleNotFoundError'>: No module named '<java package name>' #347

Open sbhkmndl opened 3 years ago

sbhkmndl commented 3 years ago

The method loadClassPath() in ClassList class gets class paths from System.getProperty("java.class.path"), which only gives the class paths from the system class loader. What if there is a custom classloader, rather than the system classloader, that has loaded the Jep class? In that case loadClassPath() method will not find the required classes in the system classloader’s classpath. The python code, that has a java import for those classes, will fail to execute.

In my case, I have the same issue. I have a custom classloader that loads all the classes of jep dependency and a couple of other dependencies dynamically. The python file, which tries to import the classes loaded by the custom classloader, fails with the exception: jep.JepException: <class 'ModuleNotFoundError'>.

I fixed it with a hack, by appending the classpath from the custom classloader to the java.class.path before initializing the jep. Here is the code snippet:

StringBuilder classPathBuilder = new StringBuilder();
for (URL url : ((URLClassLoader) (Thread.currentThread().getContextClassLoader())).getURLs()) {
    classPathBuilder.append(new File(url.getPath()));
    classPathBuilder.append(System.getProperty("path.separator"));
}
classPathBuilder.append(System.getProperty("java.class.path"));
String classpath = classPathBuilder.toString();
System.setProperty("java.class.path", classpath);

So I suggest the loadClassPath() method should consider the classpath of the custom classloader(or to be specific, the classloader that loaded the Jep.class) along with java.class.path. By doing this, we will be enhancing the functionality of ClassList class. I am interested in making this change and sending a PR. Kindly let me know if I am going in the right direction.

bsteffensmeier commented 3 years ago

We've had issues with custom classloaders before especially for OSGi, to improve flexibility we made the ClassEnquirer interface so you aren't stuck with ClassList, you can specify any ClassEnquirer in your JepConfig when you make your interpreter. If you need custom behavior I would recommend extending ClassList or implementing your own ClassEnquirer rather than modifying java.class.path.

As for your proposed changes to jep, I think it sounds reasonable and should be an overall improvement. I worry a little about special URLs or URLs that aren't files but that code is already set up to ignore anything unusual so I don't think it would be likely to break any currently working cases. My goal for ClassList is not to perfectly handle every conceivable use case but to handle the common use cases.

Since the JepConfig may contain a custom ClassLoader I would expect the ClassList to be using the same ClassLoader as Jep for finding URLs, not necessarily the context class loader.

If you plan to submit a PR please review our Contributing Guide, you will need to merge into the dev_4.0 branch. I am slowly moving towards a release of version 4.0 in a couple weeks so if you would like that change in the released version of jep it will need to be in soonish.