2009-01-27 23 views
35

Có thể biết liệu một lớp Java đã được tải chưa, mà không cần tìm cách tải nó? Class.forName cố gắng tải lớp, nhưng tôi không muốn tác dụng phụ này. Có cách nào khác không?Trong Java, có thể biết liệu một lớp đã được tải chưa?

(Tôi không muốn ghi đè lên các bộ nạp lớp. Tôi đang tìm kiếm một phương pháp tương đối đơn giản.)

Trả lời

36

(Nhờ Aleksi) Mã này:

public class TestLoaded { 
    public static void main(String[] args) throws Exception { 
      java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[] { String.class }); 
      m.setAccessible(true); 
      ClassLoader cl = ClassLoader.getSystemClassLoader(); 
      Object test1 = m.invoke(cl, "TestLoaded$ClassToTest"); 
      System.out.println(test1 != null); 
      ClassToTest.reportLoaded(); 
      Object test2 = m.invoke(cl, "TestLoaded$ClassToTest"); 
      System.out.println(test2 != null); 
    } 
    static class ClassToTest { 
      static { 
       System.out.println("Loading " + ClassToTest.class.getName()); 
      } 
      static void reportLoaded() { 
       System.out.println("Loaded"); 
      } 
    } 
} 

Tạo:

false 
Loading TestLoaded$ClassToTest 
Loaded 
true 

Lưu ý rằng các lớp mẫu không nằm trong gói. Cần có binary name đầy đủ.

Một ví dụ về một tên nhị phân là "java.security.KeyStore$Builder$FileBuilder$1"

+1

Chỉ cần một lưu ý phụ: Hãy nhớ sử dụng tên chuẩn package.subpackage.ClassName khi gọi phương thức. Câu trả lời này không thể hiện yêu cầu này vì lớp không nằm trong gói. –

+3

Cảm ơn bạn đã thêm ghi chú đó. Trong khi bạn cần gói, nó là "tên nhị phân" được yêu cầu. Tên chuẩn của lớp được hiển thị trong ví dụ là "TestLoaded.ClassToTest", khác với tên nhị phân ... Tôi sẽ chỉnh sửa câu trả lời để làm rõ/liên kết. –

+0

Cảm ơn @spdenne và @Aleksi. Tôi đã cố gắng làm điều này nhưng nó thất bại, mặc dù tôi không chắc tại sao. Mã của bạn ở trên không hoạt động cho tôi. Là một lưu ý phụ, bạn có thể muốn kiểm tra câu trả lời của McDowell về thiết bị đo đạc. Cảm ơn rất nhiều. –

25

Bạn có thể sử dụng phương pháp findLoadedClass(String) trong ClassLoader. Nó trả về null nếu lớp không được nạp.

+1

Thật không may, findLoadedClasses được bảo vệ, có nghĩa là bạn sẽ phải phân lớp ClassLoader để có thể truy cập nó. – staffan

+0

Có thể gọi phương thức này qua phản xạ không? (Kiểm tra kiểm tra an ninh) –

+1

có, Đó là, bạn có thể làm với sự phản chiếu – Elbek

5

Nếu bạn kiểm soát nguồn của các lớp mà bạn quan tâm cho dù chúng được tải hay không (mà tôi nghi ngờ, nhưng bạn không nêu trong câu hỏi của bạn), thì bạn có thể đăng ký tải của bạn trong bộ khởi tạo tĩnh.

public class TestLoaded { 
    public static boolean loaded = false; 
    public static void main(String[] args) throws ClassNotFoundException { 
     System.out.println(loaded); 
     ClassToTest.reportLoaded(); 
     System.out.println(loaded); 
    } 
    static class ClassToTest { 
     static { 
      System.out.println("Loading"); 
      TestLoaded.loaded = true; 
     } 
     static void reportLoaded() { 
      System.out.println("Loaded"); 
     } 
    } 
} 

Output:

false 
Loading 
Loaded 
true 
+0

Cảm ơn bạn. Đây thực sự là một cách hay để sử dụng cho các lớp học của riêng tôi. Nhưng tôi đã hy vọng tìm thấy một cái gì đó chung chung hơn, trong trường hợp tôi không kiểm soát các lớp học. –

+3

Bạn đang gây nhầm lẫn khi tải lớp và khởi tạo. Khi bạn làm một cái gì đó như 'Object o = ClassToTest.class;' thì lớp được nạp nhưng không được khởi tạo. Tôi đoán, OP không quan tâm; chỉ muốn làm cho nó thẳng. – maaartinus

7

Một cách để làm điều này sẽ được để viết một tác nhân Java sử dụng instrumentation API. Điều này sẽ cho phép bạn ghi lại tải các lớp của JVM.

public class ClassLoadedAgent implements ClassFileTransformer { 

    private static ClassLoadedAgent AGENT = null; 

    /** Agent "main" equivalent */ 
    public static void premain(String agentArguments, 
      Instrumentation instrumentation) { 
     AGENT = new ClassLoadedAgent(); 
     for (Class<?> clazz : instrumentation.getAllLoadedClasses()) { 
      AGENT.add(clazz); 
     } 
     instrumentation.addTransformer(AGENT); 
    } 

    private final Map<ClassLoader, Set<String>> classMap = new WeakHashMap<ClassLoader, Set<String>>(); 

    private void add(Class<?> clazz) { 
     add(clazz.getClassLoader(), clazz.getName()); 
    } 

    private void add(ClassLoader loader, String className) { 
     synchronized (classMap) { 
      System.out.println("loaded: " + className); 
      Set<String> set = classMap.get(loader); 
      if (set == null) { 
       set = new HashSet<String>(); 
       classMap.put(loader, set); 
      } 
      set.add(className); 
     } 
    } 

    private boolean isLoaded(String className, ClassLoader loader) { 
     synchronized (classMap) { 
      Set<String> set = classMap.get(loader); 
      if (set == null) { 
       return false; 
      } 
      return set.contains(className); 
     } 
    } 

    @Override 
    public byte[] transform(ClassLoader loader, String className, 
      Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 
      byte[] classfileBuffer) throws IllegalClassFormatException { 
     add(loader, className); 
     return classfileBuffer; 
    } 

    public static boolean isClassLoaded(String className, ClassLoader loader) { 
     if (AGENT == null) { 
      throw new IllegalStateException("Agent not initialized"); 
     } 
     if (loader == null || className == null) { 
      throw new IllegalArgumentException(); 
     } 
     while (loader != null) { 
      if (AGENT.isLoaded(className, loader)) { 
       return true; 
      } 
      loader = loader.getParent(); 
     } 
     return false; 
    } 

} 

META-INF/MANIFEST.MF:

Manifest-Version: 1.0 
Premain-Class: myinstrument.ClassLoadedAgent 

Nhược điểm là bạn phải tải các đại lý khi bạn khởi động JVM:

java -javaagent:myagent.jar ....etcetera 
+0

Xin cảm ơn! Đây là lần đầu tiên tôi được giới thiệu về API thiết bị đo đạc. Vậy bạn sẽ tư vấn cho cách nào? Thiết bị đo đạc hoặc phản chiếu trên ClassLoader.findLoadedClass? –

+0

Sử dụng ứng dụng phù hợp nhất với ứng dụng của bạn. Tôi sẽ không mong đợi làm việc trong 100% tình huống. – McDowell

+0

Bạn có thể giải thích 'myinstrument' có nghĩa là gì trong tệp kê khai không? – user489041

2

Tôi đã có một vấn đề tương tự gần đây, nơi tôi nghi ngờ rằng các lớp học đã được nạp (có lẽ là do cách thức -classpath hoặc một cái gì đó tương tự) bởi người dùng của tôi mâu thuẫn với các lớp mà tôi đã tải sau này vào trình nạp lớp của riêng tôi.

Sau khi thử một vài trong số những điều được đề cập ở đây, những điều sau đây dường như làm điều đó cho tôi. Tôi không chắc chắn nếu nó hoạt động cho mọi hoàn cảnh, nó chỉ có thể làm việc cho các lớp java được nạp từ các tệp jar.

InputStream is = getResourceAsStream(name); 

đâu name là đường dẫn đến tập tin lớp như com/blah/blah/blah/foo.class.

getResourceAsStream trả về null khi lớp học chưa được tải vào trình nạp lớp của tôi hoặc trình nạp lớp hệ thống và trả về không null khi lớp đã được tải.

+2

Điều này sẽ kiểm tra xem tài nguyên lớp có nằm trên đường dẫn lớp hay không (mặc dù thiếu '/' hàng đầu). Nó không kiểm tra xem lớp đó có được nạp bởi trình nạp lớp hiện tại hay không –

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