2010-10-19 22 views
17

Bối cảnh:
Tôi có một lớp MainTest có nhiều nút, mỗi nút khởi tạo một lớp mà tôi đang mã hóa/kiểm tra. Tôi muốn chu kỳ mã/kiểm tra cho các lớp này được nhanh chóng, và thấy hiệu quả của những thay đổi của tôi một cách nhanh chóng, một vài lần một phút. MainTest mà là ổn định mất khoảng 20 giây để tải, mà sẽ không là một vấn đề tôi đã không cần thiết để tải lại nó cho mỗi thay đổi trong các lớp học nó instantiates. Tôi muốn tải MainTest một lần, và khi nó khởi tạo một lớp khác, hãy gọi nó là ChildTest, nhiều lần (khi sự kiện nút), nó sẽ tải lại phiên bản mới nhất của ChildTest.

Câu hỏi ngắn gọn:
Làm cách nào để bạn biết lệnh java 'mới' để tải lại lớp từ đĩa chứ không phải từ bộ nhớ cache jvm?


Tôi đã thử Class.ForName nhưng nó không tạo sự khác biệt.
Tôi cũng đã thử sử dụng trình nạp lớp tùy chỉnh (được sao chép từ nguồn mở), không có kết quả.Làm cách nào để buộc Java tải lại lớp khi khởi tạo?

+1

bạn đang sử dụng một khung kiểm tra như JUnit hay nó là một testbench trong nước? –

+0

lớp thử nghiệm trong nước. – nat101

Trả lời

25

Không có hy vọng "quá tải" toán tử new nhưng bạn chắc chắn có thể viết trình nạp lớp tùy chỉnh mà chỉ cần tải lại bytecode mỗi lần bạn yêu cầu tải lớp. Không có trình nạp lớp ngoài hộp nào sẽ làm những gì bạn đang tìm kiếm vì tất cả chúng đều cho rằng định nghĩa lớp sẽ không thay đổi trong suốt vòng đời của JVM.

Nhưng dưới đây là cách bạn thực hiện điều đó. Tạo trình nạp lớp có tên là Reloader ghi đè phương pháp loadClassfindClass để chúng đơn giản tải lại tệp lớp từ đĩa mỗi khi chúng được gọi (thay vì "lưu bộ nhớ cache" để sử dụng sau này). Sau đó, bạn chỉ cần gọi new Reloader().loadClass("foo.bar.MyClassName") bất kỳ lúc nào bạn nghi ngờ định nghĩa lớp đã thay đổi (ví dụ như một phần trong các phương pháp vòng đời của khung thử nghiệm của bạn).

This article điền vào một số chi tiết nhưng bỏ lỡ một số điểm quan trọng, đặc biệt là về việc sử dụng các phiên bản mới của trình nạp lớp để tải lại sau đó và ủy quyền cho trình nạp lớp mặc định khi thích hợp. Dưới đây là một ví dụ làm việc đơn giản mà nhiều lần nạp lớp MyClass và giả định tập tin lớp học tồn tại trong tương đối "./bin" thư mục:

public class Reloader extends ClassLoader { 
    public static void main(String[] args) throws Exception { 
     do { 
      Object foo = new Reloader().loadClass("MyFoo").newInstance(); 
      System.out.println("LOADED: " + foo); // Overload MyFoo#toString() for effect 
      System.out.println("Press <ENTER> when MyFoo.class has changed"); 
      System.in.read(); 
     } while (true); 
    } 

    @Override 
    public Class<?> loadClass(String s) { 
     return findClass(s); 
    } 

    @Override 
    public Class<?> findClass(String s) { 
     try { 
      byte[] bytes = loadClassData(s); 
      return defineClass(s, bytes, 0, bytes.length); 
     } catch (IOException ioe) { 
      try { 
       return super.loadClass(s); 
      } catch (ClassNotFoundException ignore) { } 
      ioe.printStackTrace(System.out); 
      return null; 
     } 
    } 

    private byte[] loadClassData(String className) throws IOException { 
     File f = new File("bin/" + className.replaceAll("\\.", "/") + ".class"); 
     int size = (int) f.length(); 
     byte buff[] = new byte[size]; 
     FileInputStream fis = new FileInputStream(f); 
     DataInputStream dis = new DataInputStream(fis); 
     dis.readFully(buff); 
     dis.close(); 
     return buff; 
    } 
} 

Tại mỗi lời gọi của 'làm/trong khi' khối trong phương thức main , một Trình tải lại mới được khởi tạo để tải lớp từ đĩa và trả về cho người gọi. Vì vậy, nếu bạn ghi đè tệp bin/MyClass.class để chứa triển khai mới với phương thức khác nhau, quá tải toString, thì bạn sẽ thấy việc triển khai mới mỗi lần.

+0

Tôi thực sự đang làm việc với CustomClassLoader của bài viết đó, nhưng tôi đang gặp sự cố khi làm điều đó để làm những gì tôi muốn. Nghe có vẻ giống như cách tiếp cận đúng, nhưng tôi sẽ phải đào sâu vào bài báo, tôi đoán vậy. – nat101

+0

Bạn sẽ cần phải cẩn thận không sử dụng toán tử "mới" cho lớp thay đổi hoặc trình quản lý lớp hệ thống mặc định sẽ được sử dụng thay cho của bạn. Hãy thử sử dụng một cái gì đó như thế này thay vì 'MyClass foo = myReloader.loadClass (" MyClass "). NewInstance();' – maerics

+0

Có! Nó thực hiện công việc. Ngay cả đối với một gui. Cảm ơn. – nat101

0

Tôi không chắc chắn cách thực hiện điều này bằng mã, nhưng NetBeans có nút "Áp dụng thay đổi mã" sẽ thực hiện việc này.

Tuy nhiên, nó chỉ có thể thay đổi việc triển khai lớp, nó không thể thay đổi chữ ký của nó. Điều này có nghĩa là bạn không thể thêm, xóa hoặc thay đổi các biến hoặc phương thức của cá thể. Điều này là do thiết kế của JVM không cho phép chúng được thay đổi khi một lớp đã được nạp một lần.

Vì NetBeans có thể thực hiện, phải có cách, nhưng tôi không biết cách thực hiện thủ công.

Nếu bạn chạy trong một IDE hỗ trợ tính năng này, bạn có thể chỉ cần nhấp vào nút mỗi lần bạn sửa đổi một lớp. Sau đó nó sẽ biên dịch lại lớp đó và tải lại nó.

+0

Tôi đang sử dụng Eclipse tự động biên dịch khi lưu. Câu hỏi là làm thế nào để có được lớp vừa được biên dịch nạp vào một jvm mà alread có phiên bản trước đó của cùng một lớp được nạp. – nat101

1

Có vẻ như bạn muốn ot sử dụng triển khai nóng khi có thể được sử dụng với trình gỡ lỗi. Khi bạn gỡ lỗi một vấn đề và recomiple một số các lớp học của nó, bạn có thể nhận được tùy chọn để tải lại các lớp học đã thay đổi.

EDIT: Ngoài việc sử dụng API gỡ lỗi, bạn có thể sử dụng Thiết bị đo đạc. http://download-llnw.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html

Tuy nhiên, vì việc sử dụng trình gỡ lỗi là cách đơn giản nhất để thực hiện việc này, nếu cách này không hiệu quả, bạn có thể gặp phải sự cố tương tự.

Nghe có vẻ giống như những gì bạn cần để kiểm tra các tác phẩm nhỏ hơn, vì vậy phải mất ít hơn một lần chạy một số tập con của ứng dụng của bạn.

Hoặc bạn có thể tải ứng dụng của mình nhanh hơn bằng cách cung cấp cơ sở đổ và tải lại cho bộ nhớ. Bằng cách này, bạn có thể bắt đầu ứng dụng của mình từ ảnh chụp nhanh (có thể ngay lập tức)

+1

Tôi thực sự không thể sử dụng trình gỡ lỗi cho mục đích này, quá chậm. (gui rộng rãi.) Nhưng câu hỏi của tôi là, nếu trình gỡ rối có thể làm điều đó, tại sao tôi không thể? – nat101

+0

Bạn có thể nếu bạn sử dụng API gỡ lỗi để làm điều đó. Tuy nhiên, điều này không đơn giản để sử dụng. Việc thay đổi trình nạp lớp sẽ không thay đổi một lớp đã được nạp thay đổi mã của nó. xem chỉnh sửa. –

1

Nghe có vẻ hơi đáng sợ, nhưng điều này sẽ hữu ích.

http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ClassLoader.html

ClassLoader có thể tự động tải các lớp học trong thời gian chạy, tôi sẽ đọc các api để xác định nếu tải nó một lần nữa ghi đè các phiên bản trước.

+1

Tôi sẽ cần phải viết lớp con ClassLoader của riêng mình để thực hiện việc này. – nat101

+0

Điều này sẽ không thực sự khó khăn, chỉ cần mở rộng ClassLoader và chọn luôn tạo một cá thể mới từ tệp nguồn. API cung cấp tổng quan ngắn gọn về cách bạn có thể thực hiện việc này để tự động nhận tệp từ mạng. Trường hợp của bạn sẽ là từ một nguồn tập tin không đổi. – Valchris

1

Bạn có thể thử xem Java Rebel. Nó là một trình tải lớp "chỉ phát triển" được thiết kế để làm chính xác những gì bạn cần nhưng, có lẽ, nó có thể tốn kém cho nhu cầu của bạn. Trong trường hợp của bạn, giải pháp của Peter Lawrey có thể là đủ.

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