2008-11-03 101 views
198

搜索帶註釋類的整個類路徑的最佳方法是什麼?在運行時掃描Java註釋

我正在做一個庫,我想允許用戶註釋他們的類,所以當Web應用程序啓動時,我需要掃描整個類路徑以獲取特定的註釋。

你知道一個圖書館或Java設施嗎?

編輯:我正在考慮像Java EE 5 Web服務或EJB的新功能。您使用@WebService@EJB註釋您的班級,系統在加載時會查找這些班級,以便遠程訪問這些班級。

回答

163

使用org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

掃描從基礎包類路徑A組分提供商。然後它應用排除並將篩選器應用於結果類以查找候選人。

ClassPathScanningCandidateComponentProvider scanner = 
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>); 

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class)); 

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>)) 
    System.out.println(bd.getBeanClassName()); 
+4

感謝您的信息。你是否也知道如何爲其字段具有自定義註釋的類掃描classpath? – Javatar 2012-12-12 14:10:40

+6

@Javatar使用Java的反射API。 .class.getFields()對於每個字段,調用getAnnotation() – 2013-01-21 02:59:57

+0

@ArthurRonaldFDGarcia,我該如何掃描`sub package`? – CycDemo 2014-01-30 08:30:42

1

Java沒有「發現」。我知道的唯一方法是掃描.class文件應該在的目錄,解析名稱並使用它。可怕的是醜陋的,也許現在有一個更好的包裹 - 我沒有看過幾年。

通常這個問題曾經通過包含一個屬性文件或一個帶有類名的.xml文件來解決。

我很想聽到更好的答案。

3

稍微偏離主題,但Spring也做類似的工作,使用<context:component-scan>,你可以研究一下它的源代碼?

Spring提供了自動檢測'刻板'類的能力[...]。要自動檢測這些類並註冊相應的bean,需要包含[context:component-scan元素]。

1

Classloader API沒有「枚舉」方法,因爲類加載是一個「按需」活動 - 通常在您的類路徑中有成千上萬個類,其中只有一小部分將會是需要(現在只有rt.jar是48MB!)。

所以,即使你可能枚舉所有類,這將是非常耗時間和內存。

簡單的方法是在安裝文件中列出相關的類(xml或任何適合您的幻想);如果您想自動執行此操作,請將自己限制爲一個JAR或一個類目錄。

2

我不確定它是否會幫助你,但你可以看看Apache公共發現項目。

discovery project

6

嘗試Scannotation

它可用於搜索類路徑或Web應用程序li​​b目錄中的特定註釋。

+0

-1它看起來這個庫已經過時了,它使用了一個非常老版本的javassist,它不能讀取java 8類文件。使用「反射」代替(參見喬納森的回答) – 2015-10-15 19:32:22

133

而另一種解決方案是Google reflections

快速回顧:

  • 春天的解決方案是,如果你使用Spring的路要走。否則它是一個很大的依賴。
  • 直接使用ASM有點麻煩。
  • 直接使用Java Assist也很笨重。
  • 宣傳是超級輕便,方便。沒有maven集成。
  • 谷歌思考拉谷歌收藏。索引一切,然後是超級快。
0

如果你想有一個Scala庫,使用Sclasner:如果你想要一個真正重量輕(無依賴性,簡單的API,15 kb的jar文件)和非常快解決方案 https://github.com/ngocdaothanh/sclasner

13

您可以使用Java Pluggable Annotation Processing API來編寫將在編譯過程中執行的註釋處理器,並將收集所有帶註釋的類並構建索引文件以供運行時使用。

這是執行註釋類發現的最快方法,因爲您不需要在運行時掃描類路徑,這通常非常緩慢。此方法也適用於任何類加載器,不僅適用於運行時掃描程序通常支持的URLClassLoaders。

上述機制已在ClassIndex庫中實現。

要使用它註釋您的自定義註釋與@IndexAnnotated元註釋。這將在編譯時創建一個索引文件:META-INF/annotations/com/test/YourCustomAnnotation列出所有帶註釋的類。你可以通過執行acccess在運行時指數:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class) 
1

谷歌Reflections似乎比春季要快得多。發現此功能要求,以滿足這種差異:http://www.opensaga.org/jira/browse/OS-738

這是使用Reflections作爲我的應用程序啓動時間的原因在開發過程中非常重要。思考似乎也很容易用於我的用例(找到一個接口的所有實現者)。

28

我剛剛發佈了一個超級快速且輕量級的類路徑掃描器(git repo here),它不會調用類加載器來加載類路徑上的類以確定子類,超類,註釋等,而是直接讀取類文件二進制頭文件通過,但比rmueller的類路徑掃描器更簡單,鏈接在另一個評論中)。

我的類路徑掃描程序可以在類路徑中找到擴展給定超類,實現給定接口或具有給定類標註的類,並且可以在任何類型的路徑匹配給定正則表達式的類路徑中找到文件。

下面是使用的例子:

new FastClasspathScanner(new String[] 
     { "com.xyz.widget", "com.xyz.gizmo" }) // Whitelisted package prefixes 
    .matchSubclassesOf(DBModel.class, 
     // c is a subclass of DBModel 
     c -> System.out.println("Found subclass of DBModel: " + c.getName())) 
    .matchClassesImplementing(Runnable.class, 
     // c is a class that implements Runnable 
     c -> System.out.println("Found Runnable: " + c.getName())) 
    .matchClassesWithAnnotation(RestHandler.class, 
     // c is a class annotated with @RestHandler 
     c -> System.out.println("Found RestHandler annotation on class: " 
       + c.getName())) 
    .matchFilenamePattern("^template/.*\\.html", 
     // templatePath is a path on the classpath that matches the above pattern; 
     // inputStream is a stream opened on the file or zipfile entry. 
     // No need to close inputStream before exiting, it is closed by caller. 
     (templatePath, inputStream) -> { 
      try { 
       String template = IOUtils.toString(inputStream, "UTF-8"); 
       System.out.println("Found template: " + absolutePath 
         + " (size " + template.length() + ")"); 
      } catch (IOException e) { 
       throw new RuntimeException(e); 
      } 
     }) 
    .scan(); // Actually perform the scan 

掃描儀也記錄遇到的任何文件或目錄的最新上次修改的時間戳,你可以看到,如果是最新的最後一次修改的時間戳增加(指示在類路徑上的東西已經被更新)致電:

boolean classpathContentsModified = 
    fastClassPathScanner.classpathContentsModifiedSinceScan(); 

這可以用來啓用動態類重裝如果在classpath中的東西被更新,例如支持的路由處理類的熱替換網絡服務器。上面的調用比原來的scan()調用快幾倍,因爲只需要修改時間戳。

1

是不是太晚了回答。 我會說,它更好地庫去像ClassPathScanningCandidateComponentProvider或類似Scannotations

但有人想嘗試一些手放在它的類加載器,甚至後,我已經寫了一些我自己打印從類註釋在包:

public class ElementScanner { 

public void scanElements(){ 
    try { 
    //Get the package name from configuration file 
    String packageName = readConfig(); 

    //Load the classLoader which loads this class. 
    ClassLoader classLoader = getClass().getClassLoader(); 

    //Change the package structure to directory structure 
    String packagePath = packageName.replace('.', '/'); 
    URL urls = classLoader.getResource(packagePath); 

    //Get all the class files in the specified URL Path. 
    File folder = new File(urls.getPath()); 
    File[] classes = folder.listFiles(); 

    int size = classes.length; 
    List<Class<?>> classList = new ArrayList<Class<?>>(); 

    for(int i=0;i<size;i++){ 
     int index = classes[i].getName().indexOf("."); 
     String className = classes[i].getName().substring(0, index); 
     String classNamePath = packageName+"."+className; 
     Class<?> repoClass; 
     repoClass = Class.forName(classNamePath); 
     Annotation[] annotations = repoClass.getAnnotations(); 
     for(int j =0;j<annotations.length;j++){ 
      System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName()); 
     } 
     classList.add(repoClass); 
    } 
    } catch (ClassNotFoundException e) { 
     e.printStackTrace(); 
    } 
} 

/** 
* Unmarshall the configuration file 
* @return 
*/ 
public String readConfig(){ 
    try{ 
     URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml"); 
     JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class); 
     Unmarshaller um = jContext.createUnmarshaller(); 
     RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile())); 
     return rc.getRepository().getPackageName(); 
     }catch(Exception e){ 
      e.printStackTrace(); 
     } 
    return null; 

} 
} 

而在配置文件中,你把包的名稱和解組到一個類。