Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A little confused about how JRuby jar loading works #4628

Closed
hydrogen18 opened this issue May 26, 2017 · 3 comments
Closed

A little confused about how JRuby jar loading works #4628

hydrogen18 opened this issue May 26, 2017 · 3 comments

Comments

@hydrogen18
Copy link

I am loading Apache Log4J using initializers in my Ruby on Rails project

Environment

Provide at least:

  • jruby 9.1.8.0 (2.3.1) 2017-03-06 90fc7ab Java HotSpot(TM) 64-Bit Server VM 25.60-b23 on 1.8.0_60-b27 +jit [linux-x86_64]
  • Xubuntu 14.04
  • JRUBY_OPTS is empty

Expected Behavior

Using require or load on a JAR file adds the classes to the currently running JVM.

Actual Behavior

I'm not sure. They seem to be there but aren't? From Ruby code I can access the classes, but other Java code cannot access those classes

Based on the first line I get to the log4j classes from Ruby code just fine. If I go grab the JRuby class loader I get warnings about duplicate classes. Using java.lang.Class.forName gets me ClassNotFoundException.

Is JRuby maintaining its own class loader that is separated out from the JVM's normal class loader? Historically it seemed that when I did require somefile.jar the classes in that JAR were accesible to all java code.

[1] pry(main)> Java::org.apache.log4j::Level::FATAL.toString
=> "FATAL"
[3] pry(main)> Thread.current.to_java.getContext.getRuntime.getJRubyClassLoader.findClass('org.apache.log4j.Level')
Java::JavaLang::LinkageError: loader (instance of  org/jruby/util/JRubyClassLoader): attempted  duplicate class definition for name: "org/apache/log4j/Level"
from java.lang.ClassLoader.defineClass1(Native Method)
[5] pry(main)> Java::java.lang::Class.for_name('org.apache.log4j.Level')
Java::JavaLang::ClassNotFoundException: org/apache/log4j/Level
from java.lang.Class.forName0(Native Method)
[6] pry(main)> Java::java.lang::Class.for_name('org.apache.log4j.Level')
Java::JavaLang::ClassNotFoundException: org/apache/log4j/Level
from java.lang.Class.forName0(Native Method)
[7] pry(main)> Java::java.lang::ClassLoader.getSystemClassLoader.findClass('org.apache.log4j.Level')
Java::JavaLang::ClassNotFoundException: org.apache.log4j.Level
from java.net.URLClassLoader.findClass(java/net/URLClassLoader.java:381)
@kares
Copy link
Member

kares commented May 28, 2017

Is JRuby maintaining its own class loader that is separated out from the JVM's normal class loader?

yes it is. would be a bit difficult and confusing to change the CP in the system.

Historically it seemed that when I did require somefile.jar the classes in that JAR were accesible to all java code.

maybe in a .war scenario but otherwise JRuby has been using it JRubyClassLoader for a long time ...

if you move the log4j.jar on the 'normal' Java CP and not require/load the .jar from JRuby you would be able to access the classes from both Java and JRuby ... and avoid all of the gotchas of duplicate classes.

@kares kares closed this as completed May 28, 2017
@kares kares added this to the Invalid or Duplicate milestone May 28, 2017
@hydrogen18
Copy link
Author

What is the 'normal' Java CP? I have tried setting -J-classpath in JRUBY_OPTS to no effect. The Ruby code can find the classes from the jars when I do this, but the Java code cannot.

Consider the following example, using the Java::package... syntax provided by JRuby works as intended. Trying to use Java's reflection APIs to locate a class fails.

[ericu-destroyer-of-worlds] ~$ cat com/example/HelloWorld.java 
package com.example;

public class HelloWorld {
  public void hello(){ 
    System.out.println("hell from java code");
  } 
}
[ericu-destroyer-of-worlds] ~$ jar tvf example.jar
     0 Tue May 30 09:11:58 CDT 2017 META-INF/
    68 Tue May 30 09:11:58 CDT 2017 META-INF/MANIFEST.MF
   421 Tue May 30 09:11:36 CDT 2017 com/example/HelloWorld.class
[ericu-destroyer-of-worlds] ~$ cat example.rb
puts "JRUBY_OPTS:" + ENV['JRUBY_OPTS']

if ENV['ADD_CLASSPATH']
  puts "Adding to classpath" + ENV['ADD_CLASSPATH']
  $CLASSPATH << ENV['ADD_CLASSPATH']
end

puts 'using JRuby'
Java::com.example::HelloWorld.new.hello

puts 'using Java'
Java::java.lang::Class.for_name('com.example.HelloWorld').new_instance.hello


[ericu-destroyer-of-worlds] ~$ ruby -version
jruby 9.1.8.0 (2.3.1) 2017-03-06 90fc7ab Java HotSpot(TM) 64-Bit Server VM 25.60-b23 on 1.8.0_60-b27 +jit [linux-x86_64]
NameError: undefined local variable or method `rsion' for main:Object
  <main> at -e:1
[ericu-destroyer-of-worlds] ~$ ruby ./example.rb
JRUBY_OPTS:-J-classpath /home/ericu/example.jar
using JRuby
hell from java code
using Java
Unhandled Java exception: java.lang.ClassNotFoundException: com/example/HelloWorld
java.lang.ClassNotFoundException: com/example/HelloWorld
                           forName0 at java/lang/Class.java:-2
                            forName at java/lang/Class.java:264
                             invoke at java/lang/reflect/Method.java:497
  invokeDirectWithExceptionHandling at org/jruby/javasupport/JavaMethod.java:453
                 invokeStaticDirect at org/jruby/javasupport/JavaMethod.java:365
             invokeOther24:for_name at $_dot_/./example.rb:12
                             <main> at $_dot_/./example.rb:12
                invokeWithArguments at java/lang/invoke/MethodHandle.java:627
                          runScript at org/jruby/Ruby.java:827
                        runNormally at org/jruby/Ruby.java:746
                        runNormally at org/jruby/Ruby.java:764
                        runFromMain at org/jruby/Ruby.java:577
                      doRunFromMain at org/jruby/Main.java:417
                        internalRun at org/jruby/Main.java:305
                                run at org/jruby/Main.java:232
                               main at org/jruby/Main.java:204

@mkristian
Copy link
Member

@hydrogen18 java does not allow to add jars dynamically to the classloader which is associated with -J-classpath

JRuby allows to add jars into the JRubyClassloader which has the other classloader as parent. having said this Class.for_name is just asking the wrong classloader for the class. best pick any JRuby class and ask either
JRuby.runtime.jruby_class_loader.load_class('com.example.HelloWorld').new_instance.hello
or
Java::java.lang::Class.for_name('com.example.HelloWorld', true, JRuby.runtime.jruby_class_loader).new_instance.hello

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants