การสแกนหมายเหตุประกอบ Java เมื่อรันไทม์ [ปิด]


253

วิธีที่ดีที่สุดในการค้นหา classpath ทั้งหมดสำหรับคลาสที่มีคำอธิบายประกอบคืออะไร

ฉันทำห้องสมุดและฉันต้องการอนุญาตให้ผู้ใช้ใส่คำอธิบายประกอบในชั้นเรียนของพวกเขาดังนั้นเมื่อเว็บแอปพลิเคชันเริ่มต้นฉันต้องสแกน classpath ทั้งหมดเพื่อหาคำอธิบายประกอบบางอย่าง

คุณรู้ว่าห้องสมุดหรือสิ่งอำนวยความสะดวก Java ทำเช่นนี้?

แก้ไข: ฉันกำลังคิดถึงบางสิ่งบางอย่างเช่นฟังก์ชั่นใหม่สำหรับ Java EE 5 Web Services หรือ EJB คุณใส่คำอธิบายประกอบชั้นเรียนของคุณด้วย@WebServiceหรือ@EJBและระบบค้นหาชั้นเรียนเหล่านี้ในขณะที่โหลดเพื่อให้สามารถเข้าถึงได้จากระยะไกล

คำตอบ:


210

ใช้org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

ผู้ให้บริการส่วนประกอบที่สแกน classpath จากแพ็กเกจฐาน จากนั้นจะใช้การยกเว้นและรวมตัวกรองกับคลาสที่เป็นผลลัพธ์เพื่อค้นหาผู้สมัคร

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
ขอบคุณสำหรับข้อมูล. คุณรู้วิธีสแกน classpath สำหรับคลาสที่ฟิลด์ใดมีหมายเหตุประกอบแบบกำหนดเองหรือไม่
Javatar

6
@Javatar ใช้ API การสะท้อนของ Java <YOUR_CLASS> .class.getFields () สำหรับแต่ละฟิลด์ให้เรียกใช้ getAnnotation (<YOUR_ANNOTATION>)
Arthur Ronald

1
หมายเหตุ: หากคุณทำสิ่งนี้ภายในแอปพลิเคชัน Spring สปริงจะยังคงประเมินและดำเนินการตาม@Conditionalคำอธิบายประกอบ ดังนั้นหากคลาสมี@Conditionalค่าที่คืนค่าเป็นเท็จคลาสจะไม่ถูกส่งคืนfindCandidateComponentsแม้ว่าจะตรงกับตัวกรองของสแกนเนอร์ก็ตาม สิ่งนี้ทำให้ฉันในวันนี้ - ฉันลงเอยด้วยการใช้วิธีแก้ปัญหาของโจนาธานด้านล่างแทน
Adam Burley

1
@ArthurRonald ขออภัย Arthur ฉันหมายความว่าBeanDefinitionวัตถุนั้นไม่ได้มีวิธีในการรับชั้นเรียนโดยตรง สิ่งที่ใกล้เคียงที่สุดดูเหมือนจะเป็นgetBeanClassNameที่ส่งกลับชื่อคลาสที่ผ่านการรับรองโดยสมบูรณ์ แต่พฤติกรรมที่แน่นอนของวิธีนี้ไม่ชัดเจน นอกจากนี้ยังไม่ชัดเจนว่าคลาสตัวโหลดคลาสใดถูกพบ
Max

3
@ Max ลองทำสิ่งนี้: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins

149

และวิธีอื่นคือการสะท้อนความเห็นของ Google

ตรวจสอบด่วน:

  • Spring solution เป็นวิธีที่จะไปหากคุณใช้ Spring ไม่อย่างนั้นมันก็เป็นเรื่องใหญ่
  • การใช้ ASM โดยตรงนั้นค่อนข้างยุ่งยาก
  • การใช้ Java Assist โดยตรงเช่นกัน
  • สิ่งที่มีน้ำหนักเบาสุด ๆ และสะดวกสบาย ยังไม่มีการรวม Maven
  • การสะท้อนของ Google ดึงเข้ามาในชุดสะสมของ Google จัดทำดัชนีทุกอย่างแล้วเร็วสุด

43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). c'est tout
zapp

4
ฉันต้องระบุชื่อแพ็คเกจหรือไม่ สัญลักษณ์แทน? สิ่งที่เรียนทั้งหมดใน classpath?
Sunnyday

1
ระวังว่ายังไม่มีความคืบหน้าในการรองรับ Java 9: github.com/ronmamo/reflections/issues/186
Vadzim

ไลบรารี org.reflections ไม่ทำงานภายใต้ java 13 (อาจเร็วกว่านี้เช่นกัน) ครั้งแรกที่มันถูกเรียกมันดูเหมือนว่าจะโอเค อินสแตนซ์ที่ตามมาและการใช้งานล้มเหลวโดยบอกว่า URL การค้นหาไม่ได้รับการกำหนดค่า
Evvo

44

คุณสามารถค้นหาคลาสที่มีคำอธิบายประกอบที่กำหนดด้วยClassGraphรวมถึงค้นหาเกณฑ์ความสนใจอื่น ๆ เช่นคลาสที่ใช้อินเทอร์เฟซที่กำหนด (ข้อจำกัดความรับผิดชอบฉันเป็นผู้เขียน ClassGraph) ClassGraph สามารถสร้างการแสดงนามธรรมของกราฟคลาสทั้งหมด (ทุกคลาส, คำอธิบายประกอบ, วิธีการ, พารามิเตอร์วิธีการและเขตข้อมูล) ในหน่วยความจำสำหรับชั้นเรียนทั้งหมดใน classpath หรือสำหรับชั้นเรียนใน แพ็คเกจที่อยู่ในรายการที่อนุญาตและคุณสามารถค้นหากราฟของชั้นเรียนได้ตามต้องการ ClassGraph สนับสนุนกลไกข้อมูลจำเพาะ classpath และ classloadersมากกว่าสแกนเนอร์อื่น ๆ และยังทำงานร่วมกับระบบโมดูล JPMS ใหม่ได้อย่างราบรื่นดังนั้นหากคุณยึดรหัสของคุณกับ ClassGraph รหัสของคุณจะพกพาได้มากที่สุด ดู API ที่นี่


1
จำเป็นต้องใช้ Java 8 เพื่อให้ทำงานได้หรือไม่
David George

1
อัปเดตเพื่อใช้ Java7 ไม่มีปัญหา เพียงแค่ลบคำอธิบายประกอบและแปลงฟังก์ชั่นเพื่อใช้คลาสภายในที่ไม่ระบุชื่อ ฉันชอบสไตล์ไฟล์ 1 ไฟล์ รหัสสะอาดดีดังนั้นถึงแม้ว่ามันจะไม่รองรับบางสิ่งที่ฉันต้องการ (คำอธิบายประกอบชั้น + ในเวลาเดียวกัน) ฉันคิดว่ามันจะเพิ่มได้ง่ายมาก การทำงานที่ดี! ถ้ามีคนไม่สามารถจัดการจะทำผลงานในการปรับเปลี่ยนสำหรับ v7 Reflectionsพวกเขาอาจจะไปกับ นอกจากนี้หากคุณใช้ฝรั่ง / etc และต้องการเปลี่ยนคอลเล็กชันเป็นเรื่องง่ายเหมือนพาย มีความคิดเห็นที่ยอดเยี่ยมเช่นกัน
Andrew Backer

2
@Alexandros ขอบคุณคุณควรตรวจสอบ ClassGraph มันได้รับการปรับปรุงอย่างมีนัยสำคัญผ่าน FastClasspathScanner
ลุคฮัทชิสัน

2
@AndrewBacker ClassGraph (เวอร์ชันใหม่ของ FastClasspathScanner) ได้รับการสนับสนุนอย่างเต็มที่สำหรับการดำเนินการบูลีนผ่านตัวกรองหรือการดำเนินการที่กำหนดไว้ ดูตัวอย่างโค้ดได้ที่นี่: github.com/classgraph/classgraph/wiki/Code-examples
ลุคฮัทชิสัน

1
@Luke Hutchison ใช้ ClassGraph อยู่แล้ว ช่วยฉันในการโยกย้ายไปยัง Java 10 ห้องสมุดที่มีประโยชน์จริงๆ
Alexandros

25

หากคุณต้องการน้ำหนักเบาจริงๆ(ไม่มีการพึ่งพา, API แบบง่าย, ไฟล์ jar 15 kb) และวิธีแก้ปัญหาที่รวดเร็วมากดูannotation-detectorที่https://github.com/rmuller/infomas-asl

ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้เขียน


20

คุณสามารถใช้ Java Pluggable Annotation Processing API เพื่อเขียนตัวประมวลผลคำอธิบายประกอบซึ่งจะถูกดำเนินการในระหว่างกระบวนการรวบรวมและจะรวบรวมคลาสที่มีคำอธิบายประกอบทั้งหมดและสร้างไฟล์ดัชนีสำหรับการใช้งานจริง

นี่เป็นวิธีที่เร็วที่สุดที่จะทำได้ในการค้นหาคลาสที่มีคำอธิบายประกอบเพราะคุณไม่จำเป็นต้องสแกน classpath ของคุณที่ runtime ซึ่งโดยปกติแล้วจะทำงานช้ามาก นอกจากนี้วิธีการนี้ใช้งานได้กับ classloader และไม่เพียง แต่กับ URLClassLoaders มักจะได้รับการสนับสนุนโดยสแกนเนอร์รันไทม์

กลไกข้างต้นถูกนำไปใช้ในไลบรารีClassIndexแล้ว

หากต้องการใช้คำอธิบายประกอบแบบกำหนดเองของคุณด้วย @IndexAnnotated meta-annotation สิ่งนี้จะสร้างในเวลารวบรวมไฟล์ดัชนี: META-INF / หมายเหตุประกอบ / com / test / YourCustomAnnotation แสดงรายการคลาสที่มีคำอธิบายประกอบทั้งหมด คุณสามารถเพิ่มดัชนีที่รันไทม์โดยดำเนินการ:

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

5

สายเกินไปที่จะตอบ ฉันจะบอกว่าดีกว่าที่จะไปโดยห้องสมุดเช่นClassPathScanningCandidateComponentProviderหรือชอบScannotations

แต่แม้หลังจากที่ใครบางคนต้องการลองใช้มือกับ classLoader ฉันได้เขียนเองเพื่อพิมพ์คำอธิบายประกอบจากคลาสในแพ็คเกจ:

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;

}
}

และในไฟล์กำหนดค่าคุณใส่ชื่อแพ็กเกจแล้วยกเลิกการแบ่งเป็นคลาส


3

ด้วย Spring คุณสามารถเขียนสิ่งต่อไปนี้โดยใช้คลาส AnnotationUtils เช่น:

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

สำหรับรายละเอียดเพิ่มเติมและวิธีการต่าง ๆ ให้ตรวจสอบเอกสารอย่างเป็นทางการ: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html


4
แนวคิดที่ดี แต่ถ้าคุณใส่nullค่าเป็นพารามิเตอร์ตัวที่สอง (ซึ่งกำหนดคลาสซึ่งลำดับชั้นการสืบทอดสปริงจะสแกนคลาสที่ใช้คำอธิบายประกอบ) คุณจะได้รับnullกลับมาตามการใช้งาน
jonashackt

3

มีความคิดเห็นที่ยอดเยี่ยมโดยzappที่เก็บคำตอบเหล่านั้นทั้งหมด:

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

2

Classloader API ไม่มีวิธี "แจกแจง" เนื่องจากการโหลดคลาสเป็นกิจกรรม "ตามต้องการ" โดยปกติคุณจะมีคลาสนับพันในคลาสพา ธ ของคุณเพียงเศษเสี้ยวที่จะต้องใช้ (rt.jar อยู่คนเดียว 48MB ในปัจจุบัน!)

ดังนั้นแม้ว่าคุณจะสามารถระบุคลาสได้ทั้งหมดก็จะใช้เวลามากและใช้หน่วยความจำมาก

วิธีการง่าย ๆ คือการแสดงคลาสที่เกี่ยวข้องในไฟล์ติดตั้ง (xml หรืออะไรก็ตามที่เหมาะกับความต้องการของคุณ); ถ้าคุณต้องการทำสิ่งนี้โดยอัตโนมัติ จำกัด ตัวเองไว้ที่หนึ่ง JAR หรือไดเรกทอรีชั้นหนึ่ง



1

Google Reflectionsนั้นเร็วกว่า Spring มาก พบคำขอคุณลักษณะนี้ที่อยู่ความแตกต่างนี้: http://www.opensaga.org/jira/browse/OS-738

นี่คือเหตุผลที่ใช้ Reflections เนื่องจากเวลาเริ่มต้นของแอปพลิเคชันของฉันมีความสำคัญในระหว่างการพัฒนา การสะท้อนกลับยังใช้งานได้ง่ายมากสำหรับกรณีการใช้งานของฉัน (ค้นหาผู้พัฒนาระบบทั้งหมดของอินเทอร์เฟซ)


1
หากคุณดูที่ปัญหา JIRA มีความคิดเห็นที่นั่นว่าพวกเขาย้ายออกไปจากการสะท้อนเนื่องจากปัญหาความมั่นคง
Wim Deblauwe

1

หากคุณกำลังมองหาทางเลือกไปยังสะท้อนผมอยากจะแนะนำให้แพนด้าสาธารณูปโภค - AnnotationsScanner มันเป็น Guava-free (Guava มี ~ 3MB, Panda Utilities มี ~ 200kb) สแกนเนอร์ขึ้นอยู่กับรหัสที่มาของห้องสมุดภาพสะท้อน

นอกจากนี้ยังทุ่มเทสำหรับการค้นหาในอนาคต หากคุณต้องการสแกนแหล่งรวมหลาย ๆ ครั้งหรือให้ API ซึ่งช่วยให้ใครบางคนกำลังสแกน classpath ปัจจุบันAnnotationsScannerProcessแคชเก็บข้อมูลทั้งหมดClassFilesดังนั้นจึงรวดเร็วจริง ๆ

ตัวอย่างง่ายๆของAnnotationsScannerการใช้งาน:

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

ฤดูใบไม้ผลิมีบางสิ่งที่เรียกว่าAnnotatedTypeScannerคลาส
คลาสนี้ใช้ภายใน

ClassPathScanningCandidateComponentProvider

คลาสนี้มีรหัสสำหรับการสแกนทรัพยากรclasspathจริง ทำได้โดยใช้ข้อมูลเมตาของคลาสที่มีให้ ณ รันไทม์

หนึ่งสามารถขยายคลาสนี้หรือใช้คลาสเดียวกันสำหรับการสแกน ด้านล่างนี้เป็นคำจำกัดความของตัวสร้าง

/**
     * 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;
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.