2009-08-21 17 views
9

Tôi đang phát triển một ứng dụng tự động tải một JAR, chứa định nghĩa của một loạt các lớp mà nó sử dụng. Mọi thứ diễn ra tốt đẹp cho đến khi tôi cố gắng nắm bắt một lớp có nguồn gốc ngoại lệ trong JAR được nạp động.Trong Java, tại sao các lớp ngoại lệ cần phải có sẵn cho trình nạp lớp trước khi chúng cần thiết?

Đoạn sau đây cho thấy vấn đề này (DynamicJarLoader là lớp mà thực sự tải JAR, cả hai TestClassMyException đang trong JAR bên ngoài):

public static void main(String[] args) { 
    DynamicJarLoader.loadFile("../DynamicTestJar.jar"); 
    try { 
     String foo = new TestClass().testMethod("42"); 
    } catch(MyException e) { } 
} 

Khi tôi cố gắng chạy nó, tôi có được điều này:

Exception in thread "main" java.lang.NoClassDefFoundError: dynamictestjar/MyException 
Caused by: java.lang.ClassNotFoundException: dynamictestjar.MyException 
     at java.net.URLClassLoader$1.run(URLClassLoader.java:200) 
     at java.security.AccessController.doPrivileged(Native Method) 
     at java.net.URLClassLoader.findClass(URLClassLoader.java:188) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:307) 
     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:252) 
     at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320) 
Could not find the main class: dynamicjartestapp.Main. Program will exit. 

Nếu tôi thay thế catch(MyException e) bằng catch(Exception e), chương trình sẽ hoạt động tốt. Điều này có nghĩa là Java có thể tìm thấy TestClass sau khi JAR đã được tải. Vì vậy, có vẻ như JVM cần tất cả các lớp Ngoại lệ được xác định khi chương trình bắt đầu chạy, và không phải khi chúng cần thiết (tức là khi đạt được khối try-catch cụ thể).

Tại sao điều này lại xảy ra?

EDIT

tôi đã chạy một số xét nghiệm bổ sung, và điều này thực sự là khá kỳ quặc. Đây là nguồn gốc đầy đủ của MyException:

package dynamictestjar; 
public class MyException extends RuntimeException { 
    public void test() { 
     System.out.println("abc"); 
    } 
} 

Mã này chạy:

public static void main(String[] args) { 
    DynamicJarLoader.loadFile("../DynamicTestJar.jar"); 
    String foo = new TestClass().testMethod("42"); 
    new MyException().test(); //prints "abc" 
} 

này không:

public static void main(String[] args) { 
    DynamicJarLoader.loadFile("../DynamicTestJar.jar"); 
    String foo = new TestClass().testMethod("42"); 
    new MyException().printStackTrace(); // throws NoClassDefFoundError 
    } 

Tôi phải chỉ ra rằng bất cứ khi nào tôi chạy thử nghiệm của tôi từ NetBeans , mọi thứ diễn ra theo kế hoạch. Sự kỳ quặc chỉ bắt đầu khi tôi mạnh mẽ loại bỏ Jar bên ngoài khỏi mắt của Java và chạy ứng dụng thử nghiệm từ dòng lệnh.

EDIT # 2

Dựa trên các câu trả lời, tôi đã viết này, mà tôi nghĩ chứng tỏ người tôi chấp nhận là thực sự đúng:

public static void main(String[] args) { 
    DynamicJarLoader.loadFile("../DynamicTestJar.jar"); 
    String foo = new TestClass().testMethod("42"); 
    class tempClass { 
     public void test() { 
      new MyException().printStackTrace(); 
     } 
    } 
    new tempClass().test(); // prints the stack trace, as expected 
    } 
+0

Tôi có thể thấy điều này xảy ra nếu mã byte sắp xếp lại tham chiếu Ngoại lệ trước câu lệnh tải tệp, nhưng tôi không đủ quen thuộc với những vấn đề đó để chắc chắn. – Yishai

+0

Tôi không thấy gì ở đây để chứng minh việc tải JAR theo cách này. Đó là một ví dụ đơn giản, nhưng lý do trong mã toàn diện của bạn là gì? – duffymo

+0

@duffymo: Đó là một applet được chia làm hai. Phần lớn hơn được cài đặt trên máy tính cục bộ để người dùng không tải xuống nó mọi lúc (nó khá lớn); applet thực sự chịu trách nhiệm tải một ứng dụng khác, và sau đó thực hiện logic cần thiết. Bằng cách "tải", tôi thực sự có nghĩa là tự động thay đổi classpath để nó cũng trỏ đến Jar đó. Có lẽ có một cách tốt hơn để đạt được điều này? –

Trả lời

1

Bạn đang tải JAR DynamicTestJar.jar động trong thời gian chạy nhưng bạn thêm nó vào đường dẫn lớp khi bạn biên dịch mã.

Vì vậy, khi trình nạp lớp mặc định cố gắng tải mã bytecode cho main(), nó không thể tìm thấy MyException trên đường dẫn lớp và ném ngoại lệ. Điều này xảy ra trước `` DynamicJarLoader.loadFile ("../ DynamicTestJar.jar"); `được thực hiện!

Vì vậy, bạn phải đảm bảo rằng các lớp từ JAR động của bạn có sẵn trong trình nạp lớp hiện đang hoạt động khi một lớp được tải cần chúng. Bạn có thể thêm JAR vào classpath sau đó, đặc biệt là không có trong một lớp mà nhập một cái gì đó từ nó.

6

"Vì vậy, có vẻ như JVM cần tất cả Các lớp ngoại lệ được xác định khi chương trình bắt đầu chạy, và không khi chúng cần thiết "- không, tôi không nghĩ điều này là đúng.

Tôi cược rằng vấn đề của bạn là do những lỗi tiềm năng từ phía bạn, không ai trong số đó có liên quan gì với JVM:

  1. Bạn đang thiếu một tuyên bố gói ở phía trên cùng của bạn Tệp MyException.java. Dường như bạn có nghĩa là nó là "gói dynamictestjar", nhưng nó không có ở đó.
  2. Bạn đã có tuyên bố gói đúng trong tệp MyException.java, nhưng khi bạn biên dịch, bạn không kết thúc với tệp MyException.class trong thư mục có tên "dynamictestjar".
  3. Khi bạn đóng gói mã của mình vào DynamicTestJar.jar (Star Wars fan, bạn là) bạn đã không nhận được đường dẫn đúng bởi vì bạn đã không nén lên thư mục chứa thư mục "dynamictestjar", do đó đường dẫn mà trình tải lớp thấy không chính xác.

Không có điều nào trong số này là do những bí ẩn lớn về trình tải lớp. Bạn cần phải kiểm tra và đảm bảo rằng bạn đang làm công cụ của mình đúng cách.

Tại sao bạn cần tải JAR động như thế này? Bạn đang làm gì mà không thể thực hiện được bằng cách đặt JAR vào CLASSPATH và cho phép trình nạp lớp chọn nó?

Tôi hy vọng đây chỉ là một ví dụ và không chỉ mang tính "thực hành tốt nhất" của bạn trong Java:

catch(MyException e) { } 

Bạn ít nhất nên in stack trace hoặc sử dụng Log4J đăng nhập ngoại lệ.

UPDATE:

Tôi phải chỉ ra rằng bất cứ khi nào tôi chạy thử nghiệm của tôi từ NetBeans, mọi thứ diễn ra theo đúng kế hoạch. Sự kỳ quặc chỉ bắt đầu khi tôi mạnh mẽ loại bỏ Jar bên ngoài khỏi mắt của Java và chạy ứng dụng thử nghiệm từ dòng lệnh.

Điều này cho thấy rằng đường dẫn bạn đã kết nối với đường dẫn đến JAR không hài lòng khi bạn chạy từ dòng lệnh.

JVM không hoạt động một cách với NetBeans và một cách khác mà không có nó. Đó là tất cả về sự hiểu biết CLASSPATH, bộ nạp lớp và các vấn đề đường dẫn. Khi bạn sắp xếp ra, nó sẽ hoạt động.

+0

Cảm ơn bạn đã trả lời! Có thể tôi đã làm hỏng thứ gì đó, nhưng hãy xem xét hai sự kiện này: i) 'TestClass' được tìm thấy, và nó nằm trong cùng một gói với' MyException'. Tôi vừa kiểm tra tệp jar và 'MyException.class' là nơi mà nó được cho là. ii) Khi tôi rời 'DynamicTestJar.jar', nơi Java có thể tìm thấy nó (ví dụ: lib /), chương trình sẽ chạy (chỉ cần kiểm tra nó). Nó cũng chạy từ NetBeans. Vấn đề chỉ thể hiện chính nó khi tôi xóa các jar và đặt nó ở đâu đó Java không nhìn (trong trường hợp này, ".."). –

+0

Ồ, vâng, tôi không nuốt Các ngoại lệ như thế này trong mã thực! Đây chỉ là đoạn trích đơn giản nhất có thể minh họa vấn đề, nhưng cảm ơn những người đứng đầu. –

+0

"... nơi Java có thể tìm thấy nó ..." - không có giả thiết về việc tìm kiếm trong thư mục/lib được tích hợp trong Java. Thiên Chúa cấm, bạn không thêm mã của bạn vào thư mục JDK, phải không? Tin tôi đi Pedro, đó vẫn là bạn. Mã và cấu hình của bạn vẫn là vấn đề. Đừng thành công trong việc tìm kiếm TestClass như một dấu hiệu cho thấy bạn "đúng". Khi bạn làm đúng, JVM sẽ tìm ra ngoại lệ của bạn và vấn đề CNF sẽ biến mất. – duffymo

3

MyException là một lớp ngoại lệ trong JAR mà bạn đang tải động không?

Lưu ý rằng bạn đang static sử dụng lớp MyException, như bạn có nghĩa đen trong mã của bạn.

Bạn đang đặt DynamicTestJar.jar trong đường dẫn lớp học trong khi đang biên dịch, nhưng không phải trong khi bạn đang chạy chương trình? Đừng đặt nó trong classpath trong khi biên dịch, do đó trình biên dịch sẽ cho bạn thấy nơi bạn đang sử dụng JAR theo cách tĩnh.

+0

"tĩnh"? Bạn không chắc chắn ý bạn là gì. Anh ta gọi "mới" để tạo một cá thể và gọi các phương thức trên nó. – duffymo

+1

@duffymo: khi lớp chứa phương thức 'main' được tải, nó sẽ cố gắng tải lớp' MyException' và không thành công vì mã nạp tự động jar chứa 'MyException' sẽ không được chạy. Đó là điều Jesper có nghĩa là 'sử dụng tĩnh'. –

+0

+1 - điểm đẹp Jesper –

0

Trong Java, tất cả các lớp được nhận dạng duy nhất bởi trình nạp lớp và FQN của lớp.

Trình nạp lớp có thứ bậc, nghĩa là lớp được tải động của bạn có quyền truy cập vào tất cả các lớp của cha mẹ, nhưng cha mẹ không có quyền truy cập trực tiếp vào các lớp của nó.

Bây giờ, nếu trình nạp lớp con của bạn định nghĩa một lớp mở rộng cha mẹ của bạn, mã của cha mẹ bạn chỉ có thể tham chiếu đến lớp đó thông qua sự phản chiếu hoặc bởi lớp cha.

Nếu bạn nghĩ về tất cả mã Java là phản chiếu, điều này trở nên dễ dàng hơn. IE:

Class.forName("MyException") 

Lớp hiện tại không có quyền truy cập trình tải lớp con, vì vậy không thể thực hiện việc này.

Các vấn đề liên quan