Quét các chú thích Java khi chạy [đã đóng]


253

Cách tốt nhất để tìm kiếm toàn bộ đường dẫn cho một lớp chú thích là gì?

Tôi đang làm một thư viện và tôi muốn cho phép người dùng chú thích các lớp của họ, vì vậy khi ứng dụng Web khởi động, tôi cần quét toàn bộ đường dẫn để tìm chú thích nhất định.

Bạn có biết một thư viện hoặc một cơ sở Java để làm điều này không?

Chỉnh sửa: Tôi đang suy nghĩ về một cái gì đó giống như chức năng mới cho Dịch vụ web Java EE 5 hoặc EJB. Bạn chú thích lớp của bạn với @WebServicehoặc @EJBvà hệ thống tìm thấy các lớp này trong khi tải để chúng có thể truy cập từ xa.

Câu trả lời:


210

Sử dụng org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Một nhà cung cấp thành phần quét đường dẫn lớp từ gói cơ sở. Sau đó, nó áp dụng loại trừ và bao gồm các bộ lọc cho các lớp kết quả để tìm ứng viên.

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());

5
Cảm ơn đã cung cấp thông tin. Bạn có biết làm thế nào để quét đường dẫn lớp cho các lớp có trường có chú thích tùy chỉnh không?
Javatar

6
@Javatar Sử dụng API phản chiếu của Java. <Your_CLASS>. Class.getFields () Đối với mỗi trường, hãy gọi getAnnotation (<YOU_ANNOTATION>)
Arthur Ronald

1
LƯU Ý: Nếu bạn đang thực hiện việc này trong ứng dụng Spring, Spring vẫn sẽ đánh giá và hành động dựa trên @Conditionalcác chú thích. Vì vậy, nếu một lớp có @Conditionalgiá trị trả về sai, nó sẽ không được trả về findCandidateComponents, ngay cả khi nó khớp với bộ lọc của máy quét. Điều này đã ném tôi ngày hôm nay - cuối cùng tôi đã sử dụng giải pháp của Jonathan bên dưới.
Adam Burley

1
@ArthurRonald Xin lỗi, Arthur. Tôi có nghĩa là BeanDefinitionđối tượng không cung cấp một cách để có được lớp trực tiếp. Điều gần nhất dường như là getBeanClassNametrả về một tên lớp đủ điều kiện, nhưng hành vi chính xác của phương thức này không rõ ràng. Ngoài ra, không rõ trình tải lớp nào được tìm thấy.
Tối đa

3
@Max Hãy thử điều này: Class<?> cl = Class.forName(beanDef.getBeanClassName()); Ticketnda.com/spring/find-annotated-groupes
James Watkins

149

Và một giải pháp khác là phản ánh của Google .

Đánh giá nhanh:

  • Giải pháp mùa xuân là cách tốt nhất nếu bạn đang sử dụng Spring. Nếu không, nó là một sự phụ thuộc lớn.
  • Sử dụng ASM trực tiếp là một chút rườm rà.
  • Sử dụng Java Assistant trực tiếp cũng rất khó hiểu.
  • Thông báo là siêu nhẹ và thuận tiện. Không có tích hợp maven nào.
  • Google phản ánh kéo trong bộ sưu tập của Google. Lập chỉ mục mọi thứ và sau đó là siêu nhanh.

43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). chào hàng.
zapp

4
Tôi có cần chỉ định tên gói không? ký tự đại diện? Điều gì cho tất cả các lớp trong classpath?
nắng

1
Coi chừng rằng nó vẫn không có tiến triển nào về hỗ trợ Java 9: github.com/ronmamo/reflections/issues/186
Vadzim

thư viện org.reflections không hoạt động ngay trong java 13 (có thể sớm hơn). Lần đầu tiên nó được gọi là có vẻ ổn. các lần sử dụng và sử dụng không thành công cho biết các url tìm kiếm không được định cấu hình.
Evvo

44

Bạn có thể tìm thấy các lớp với bất kỳ chú thích cụ thể nào với ClassGraph , cũng như tìm kiếm các tiêu chí quan tâm khác, ví dụ các lớp thực hiện giao diện đã cho. (Tuyên bố miễn trừ trách nhiệm, tôi là tác giả của ClassGraph.) ClassGraph có thể xây dựng một biểu diễn trừu tượng của toàn bộ biểu đồ lớp (tất cả các lớp, chú thích, phương thức, tham số phương thức và trường) trong bộ nhớ, cho tất cả các lớp trên đường dẫn lớp hoặc cho các lớp trong các gói trong danh sách trắng và bạn có thể truy vấn biểu đồ lớp đó theo cách bạn muốn. ClassGraph hỗ trợ nhiều cơ chế đặc tả và trình tải lớp hơn bất kỳ trình quét nào khác và cũng hoạt động trơn tru với hệ thống mô-đun JPMS mới, vì vậy nếu bạn dựa vào mã của mình trên ClassGraph, mã của bạn sẽ có khả năng di động tối đa. Xem API tại đây.


1
Cái này có cần Java 8 để chạy không?
David George

1
Cập nhật để sử dụng Java7, không có vấn đề. Chỉ cần xóa các chú thích và chuyển đổi các hàm để sử dụng các lớp bên trong ẩn danh. Tôi thích kiểu tập tin 1. Mã này rất sạch, vì vậy mặc dù nó không hỗ trợ một vài điều tôi muốn (lớp + chú thích cùng một lúc) Tôi nghĩ rằng nó sẽ khá dễ dàng để thêm vào. Công việc tuyệt vời Nếu ai đó không thể quản lý để thực hiện công việc sửa đổi cho v7, có lẽ họ nên đi cùng Reflections. Ngoài ra, nếu bạn đang sử dụng ổi / vv và muốn thay đổi các bộ sưu tập, dễ dàng như chiếc bánh. Ý kiến ​​tuyệt vời bên trong quá.
Andrew Backer

2
@Alexandros cảm ơn, bạn nên kiểm tra ClassGraph, nó được cải thiện đáng kể so với FastClasspathScanner.
Luke Hutchison

2
@AndrewBacker ClassGraph (phiên bản mới của FastClasspathScanner) có hỗ trợ đầy đủ cho các hoạt động Boolean, thông qua các bộ lọc hoặc thiết lập các hoạt động. Xem các ví dụ về mã tại đây: github.com/ classgraph / classgraph / wiki /
Luke Hutchison

1
@Luke Hutchison Đã sử dụng ClassGraph. Đã giúp tôi chuyển sang Java 10. Thư viện thực sự hữu ích.
Alexandros

25

Nếu bạn muốn có trọng lượng thực sự nhẹ (không phụ thuộc, API đơn giản, tệp jar 15 kb) và giải pháp rất nhanh , hãy xem annotation-detectortại https://github.com/rmuller/infomas-asl

Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả.


20

Bạn có thể sử dụng API xử lý chú thích có thể cắm Java để viết bộ xử lý chú thích sẽ được thực thi trong quá trình biên dịch và sẽ thu thập tất cả các lớp chú thích và xây dựng tệp chỉ mục để sử dụng thời gian chạy.

Đây là cách nhanh nhất có thể để thực hiện khám phá lớp có chú thích vì bạn không cần quét đường dẫn lớp của mình khi chạy, thường hoạt động rất chậm. Ngoài ra, cách tiếp cận này hoạt động với bất kỳ trình nạp lớp nào và không chỉ với URLClassLoaders thường được hỗ trợ bởi các trình quét thời gian chạy.

Cơ chế trên đã được triển khai trong thư viện Class Index .

Để sử dụng nó chú thích chú thích tùy chỉnh của bạn với chú thích meta @ IndexAnnotated . Điều này sẽ tạo tại thời điểm biên dịch một tệp chỉ mục: META-INF / annotations / com / test / YourCustomAnnotation liệt kê tất cả các lớp chú thích. Bạn có thể xử lý chỉ mục trong thời gian chạy bằng cách thực hiện:

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

5

Có quá muộn để trả lời. Tôi có thể nói, tốt hơn là đi bằng các Thư viện như ClassPathScanningCandidateComponentProvider hoặc thích Scannotations

Nhưng ngay cả sau khi ai đó muốn thử một số thao tác với classLoader, tôi đã tự mình viết một số để in các chú thích từ các lớp trong một gói:

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;

}
}

Và trong tập tin cấu hình, bạn đặt tên gói và sắp xếp nó vào một lớp.


3

Với Spring, bạn cũng có thể viết những điều sau bằng lớp AnnotationUtils. I E:

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null);

Để biết thêm chi tiết và tất cả các phương pháp khác nhau, hãy kiểm tra tài liệu chính thức: https://docs.spring.io/spring/docs/civerse/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html


4
Ý tưởng hay, nhưng nếu bạn đặt một nullgiá trị làm tham số thứ hai (xác định lớp, trong đó hệ thống phân cấp thừa kế Spring sẽ quét một lớp sử dụng Chú thích), bạn sẽ luôn nullquay trở lại, theo cách triển khai.
jonashackt

3

Có một nhận xét tuyệt vời của zapp chìm trong tất cả những câu trả lời:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)

2

API Classloader không có phương thức "liệt kê", bởi vì tải lớp là một hoạt động "theo yêu cầu" - bạn thường có hàng ngàn lớp trong đường dẫn lớp của mình, chỉ cần một phần trong số đó (rt.jar một mình là 48MB ngày nay!).

Vì vậy, ngay cả khi bạn có thể liệt kê tất cả các lớp, điều này sẽ rất tốn thời gian và bộ nhớ.

Cách tiếp cận đơn giản là liệt kê các lớp liên quan trong tệp thiết lập (xml hoặc bất cứ thứ gì phù hợp với sở thích của bạn); nếu bạn muốn làm điều này tự động, hãy giới hạn bản thân trong một thư mục JAR hoặc một lớp.



1

Google Reflecting dường như nhanh hơn nhiều so với Spring. Tìm thấy tính năng này yêu cầu nhấn mạnh sự khác biệt này: http://www.opensaga.org/jira/browse/OS-738

Đây là một lý do để sử dụng Reflection vì thời gian khởi động ứng dụng của tôi thực sự quan trọng trong quá trình phát triển. Phản xạ dường như cũng rất dễ sử dụng cho trường hợp sử dụng của tôi (tìm tất cả những người triển khai giao diện).


1
Nếu bạn nhìn vào vấn đề JIRA, có ý kiến ​​cho rằng họ đã rời khỏi Refl Refl vì vấn đề ổn định.
Wim Deblauwe

1

Nếu bạn đang tìm kiếm một giải pháp thay thế cho phản xạ, tôi muốn giới thiệu Panda Utility - AnnotationsScanner . Đó là máy quét không có ổi (Guava có ~ 3MB, Panda Utility có ~ 200kb) dựa trên mã nguồn của thư viện phản chiếu.

Nó cũng dành riêng cho các tìm kiếm dựa trên tương lai. Nếu bạn muốn quét nhiều lần bao gồm các nguồn hoặc thậm chí cung cấp API, cho phép ai đó quét đường dẫn hiện tại, AnnotationsScannerProcesslưu trữ tất cả các lần tìm nạp ClassFiles, vì vậy nó thực sự nhanh.

Ví dụ đơn giản về AnnotationsScannercách sử dụng:

AnnotationsScanner scanner = AnnotationsScanner.createScanner()
        .includeSources(ExampleApplication.class)
        .build();

AnnotationsScannerProcess process = scanner.createWorker()
        .addDefaultProjectFilters("net.dzikoysk")
        .fetch();

Set<Class<?>> classes = process.createSelector()
        .selectTypesAnnotatedWith(AnnotationTest.class);

1

Mùa xuân có một cái gì đó gọi là một AnnotatedTypeScannerlớp học.
Lớp này sử dụng nội bộ

ClassPathScanningCandidateComponentProvider

Lớp này có mã để quét thực tế các tài nguyên đường dẫn . Nó thực hiện điều này bằng cách sử dụng siêu dữ liệu lớp có sẵn trong thời gian chạy.

Người ta có thể chỉ cần mở rộng lớp này hoặc sử dụng cùng một lớp để quét. Dưới đây là định nghĩa của nhà xây dựng.

/**
     * Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
     * 
     * @param considerInterfaces whether to consider interfaces as well.
     * @param annotationTypes the annotations to scan for.
     */
    public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {

        this.annotationTypess = Arrays.asList(annotationTypes);
        this.considerInterfaces = considerInterfaces;
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.