ฉันจะอ่านคลาสทั้งหมดจากแพ็คเกจ Java ใน classpath ได้อย่างไร


97

ฉันต้องการอ่านคลาสที่อยู่ในแพ็คเกจ Java ชั้นเรียนเหล่านั้นอยู่ใน classpath ฉันต้องทำงานนี้จากโปรแกรม Java โดยตรง คุณรู้วิธีง่ายๆในการทำหรือไม่?

List<Class> classes = readClassesFrom("my.package")

7
พูดง่ายๆไม่คุณไม่สามารถทำได้ง่ายๆ มีเทคนิคที่ยาวมากบางอย่างที่ใช้ได้ผลในบางสถานการณ์ แต่ฉันขอแนะนำให้ใช้การออกแบบที่แตกต่างออกไป
skaffman


วิธีแก้ปัญหาอาจพบได้ในโครงการWeld
Ondra Žižka

อ้างอิงลิงค์นี้สำหรับคำตอบ: stackoverflow.com/questions/176527/…
Karthik E

คำตอบ:


50

หากคุณมีSpringใน classpath ของคุณสิ่งต่อไปนี้จะทำ

ค้นหาคลาสทั้งหมดในแพ็คเกจที่มีคำอธิบายประกอบ XmlRootElement:

private List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException
{
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

    List<Class> candidates = new ArrayList<Class>();
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                               resolveBasePackage(basePackage) + "/" + "**/*.class";
    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
    for (Resource resource : resources) {
        if (resource.isReadable()) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            if (isCandidate(metadataReader)) {
                candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
            }
        }
    }
    return candidates;
}

private String resolveBasePackage(String basePackage) {
    return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
}

private boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException
{
    try {
        Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
        if (c.getAnnotation(XmlRootElement.class) != null) {
            return true;
        }
    }
    catch(Throwable e){
    }
    return false;
}

ขอบคุณสำหรับข้อมูลโค้ด :) หากคุณต้องการหลีกเลี่ยงการทำซ้ำของคลาสในรายชื่อผู้สมัครให้เปลี่ยนประเภทคอลเลกชันจากรายการเป็นชุด และใช้ hasEnclosingClass () และ getEnclosingClassName () ของ classMetaData ใช้เมธอด getEnclosingClass เกี่ยวกับคลาสพื้นฐาน
traeper

29

คุณสามารถใช้โครงการ Reflections ที่อธิบายไว้ที่นี่

ค่อนข้างสมบูรณ์และใช้งานง่าย

คำอธิบายสั้น ๆ จากเว็บไซต์ด้านบน:

การสะท้อนจะสแกนพา ธ คลาสของคุณจัดทำดัชนีเมทาดาทาช่วยให้คุณสืบค้นบนรันไทม์และอาจบันทึกและรวบรวมข้อมูลนั้นสำหรับโมดูลต่างๆภายในโปรเจ็กต์ของคุณ

ตัวอย่าง:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);

ทำงานใน Equinox หรือไม่ และถ้าเป็นเช่นนั้นสามารถทำได้โดยไม่ต้องเปิดใช้งานปลั๊กอินหรือไม่?
แท็ก

ใช้งานได้กับ Equinox บน Reflections เวอร์ชันเก่าให้เพิ่มบันเดิล jar vfsType ดูที่นี่
zapp

28

ฉันใช้อันนี้มันใช้ได้กับไฟล์หรือที่เก็บถาวรของ jar

public static ArrayList<String>getClassNamesFromPackage(String packageName) throws IOException{
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL packageURL;
    ArrayList<String> names = new ArrayList<String>();;

    packageName = packageName.replace(".", "/");
    packageURL = classLoader.getResource(packageName);

    if(packageURL.getProtocol().equals("jar")){
        String jarFileName;
        JarFile jf ;
        Enumeration<JarEntry> jarEntries;
        String entryName;

        // build jar file name, then loop through zipped entries
        jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8");
        jarFileName = jarFileName.substring(5,jarFileName.indexOf("!"));
        System.out.println(">"+jarFileName);
        jf = new JarFile(jarFileName);
        jarEntries = jf.entries();
        while(jarEntries.hasMoreElements()){
            entryName = jarEntries.nextElement().getName();
            if(entryName.startsWith(packageName) && entryName.length()>packageName.length()+5){
                entryName = entryName.substring(packageName.length(),entryName.lastIndexOf('.'));
                names.add(entryName);
            }
        }

    // loop through files in classpath
    }else{
    URI uri = new URI(packageURL.toString());
    File folder = new File(uri.getPath());
        // won't work with path which contains blank (%20)
        // File folder = new File(packageURL.getFile()); 
        File[] contenuti = folder.listFiles();
        String entryName;
        for(File actual: contenuti){
            entryName = actual.getName();
            entryName = entryName.substring(0, entryName.lastIndexOf('.'));
            names.add(entryName);
        }
    }
    return names;
}

1
ใช้งานได้ถ้าคุณนำการอ้างอิงไปยัง File ตัวคั่นและใช้เพียง '/'
Will Glass

1
จำเป็นต้องมีการเปลี่ยนแปลงอีกอย่างหนึ่งเพื่อให้สามารถทำงานกับไฟล์ jar ได้เมื่อพา ธ มีช่องว่าง คุณต้องถอดรหัสพา ธ ไฟล์ jar เปลี่ยน: jarFileName = packageURL.getFile (); ถึง: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass

ฉันได้รับเฉพาะชื่อแพ็คเกจใน jar .. ไม่ได้รับชื่อคลาส
AutoMEta

ไฟล์ใหม่ (uri) จะแก้ไขปัญหาของคุณเกี่ยวกับช่องว่าง
Trejkaz

entryName.lastIndexOf (".") จะเป็น -1
marstone

11

Spring ได้ใช้ฟังก์ชันการค้นหา classpath ที่ยอดเยี่ยมในไฟล์PathMatchingResourcePatternResolver. หากคุณใช้classpath*คำนำหน้า: คุณสามารถค้นหาทรัพยากรทั้งหมดรวมถึงคลาสในลำดับชั้นที่กำหนดและยังกรองทรัพยากรได้หากต้องการ แล้วคุณสามารถใช้เด็กของAbstractTypeHierarchyTraversingFilter, AnnotationTypeFilterและAssignableTypeFilterการกรองทรัพยากรเหล่านั้นทั้งในคำอธิบายประกอบระดับชั้นเรียนหรือในอินเตอร์เฟซที่พวกเขาใช้


6

Java 1.6.0_24:

public static File[] getPackageContent(String packageName) throws IOException{
    ArrayList<File> list = new ArrayList<File>();
    Enumeration<URL> urls = Thread.currentThread().getContextClassLoader()
                            .getResources(packageName);
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        File dir = new File(url.getFile());
        for (File f : dir.listFiles()) {
            list.add(f);
        }
    }
    return list.toArray(new File[]{});
}

โซลูชันนี้ได้รับการทดสอบภายในสภาพแวดล้อมEJB


6

การสแกนและการสะท้อนใช้วิธีการสแกนพา ธ คลาส:

Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);

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

Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");

4

กลไกที่มีประสิทธิภาพมากที่สุดสำหรับการแสดงรายการคลาสทั้งหมดในแพ็คเกจที่กำหนดคือClassGraphเนื่องจากจัดการกับอาร์เรย์ของกลไกข้อกำหนดคลาสพา ธ ที่กว้างที่สุดรวมถึงระบบโมดูล JPMS ใหม่ (ฉันเป็นคนเขียน)

List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}

ฉันเพิ่งเจอเรื่องนี้ในอีกสองปีต่อมา นี่เป็นเครื่องมือที่ยอดเยี่ยม! มันทำงานได้เร็วกว่าการสะท้อนและฉันชอบที่มันไม่เรียกใช้ตัวเริ่มต้นแบบคงที่ สิ่งที่ฉันต้องการในการแก้ปัญหาที่เรามี
akagixxer

3

ฟังก์ชันดังกล่าวยังคงหายไปอย่างน่าสงสัยจาก API การสะท้อนของ Java เท่าที่ฉันทราบ คุณสามารถรับวัตถุแพคเกจได้โดยทำสิ่งนี้:

Package packageObj = Package.getPackage("my.package");

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

ฉันพบการใช้งานตัวอย่างบางส่วนในสิ่งนี้โพสต์

ฉันไม่แน่ใจ 100% ว่าวิธีการเหล่านี้จะใช้ได้ผลเมื่อชั้นเรียนของคุณถูกฝังอยู่ในไฟล์ JAR แต่ฉันหวังว่าหนึ่งในนั้นจะทำเพื่อคุณ

ฉันเห็นด้วยกับ @skaffman ... ถ้าคุณมีวิธีอื่นในการดำเนินการนี้ฉันขอแนะนำให้ทำเช่นนั้นแทน


4
มันไม่น่าสงสัยมันก็ไม่ได้ผลเช่นนั้น ชั้นเรียนไม่ได้ "อยู่ในแพ็คเกจ" แต่มีการอ้างอิงถึง สมาคมไม่ได้ชี้ไปในทิศทางอื่น
skaffman

1
@skaffman จุดที่น่าสนใจมาก ไม่เคยคิดเกี่ยวกับเรื่องนี้ ตราบใดที่เราหลงไปตามแนวความคิดนั้นเหตุใดการเชื่อมโยงจึงไม่เป็นแบบสองทิศทาง (นี่คือความอยากรู้อยากเห็นของตัวเองมากกว่าตอนนี้)
Brent Writes Code

2

eXtcosมีแนวโน้มที่ดี ลองนึกภาพว่าคุณต้องการค้นหาคลาสทั้งหมดที่:

  1. ขยายจากคลาส "Component" และจัดเก็บไว้
  2. มีคำอธิบายประกอบ "MyComponent" และ
  3. อยู่ในแพ็คเกจ "ทั่วไป"

ด้วย eXtcos มันง่ายพอ ๆ กับ

ClasspathScanner scanner = new ClasspathScanner();
final Set<Class> classStore = new ArraySet<Class>();

Set<Class> classes = scanner.getClasses(new ClassQuery() {
    protected void query() {
        select().
        from(“common”).
        andStore(thoseExtending(Component.class).into(classStore)).
        returning(allAnnotatedWith(MyComponent.class));
    }
});

2
  1. บิลเบิร์คได้เขียน (ดีบทความเกี่ยวกับการสแกนระดับ] และแล้วเขาก็เขียนScannotation

  2. Hibernateเขียนไว้แล้ว:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDIอาจแก้ปัญหานี้ได้ แต่ไม่รู้ - ยังไม่ได้ตรวจสอบอย่างละเอียด

.

@Inject Instance< MyClass> x;
...
x.iterator() 

นอกจากนี้สำหรับคำอธิบายประกอบ:

abstract class MyAnnotationQualifier
extends AnnotationLiteral<Entity> implements Entity {}

ลิงก์ไปยังบทความตาย
user2418306

1

ฉันได้นำไปใช้และใช้ได้ในกรณีส่วนใหญ่ เพราะมันเป็นนานผมวางไว้ในแฟ้มได้ที่นี่

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

วิธีนี้สามารถใช้ได้เฉพาะเมื่อ:

  1. คุณมีคลาสที่อยู่ในแพ็คเกจเดียวกับที่คุณต้องการค้นพบคลาสนี้เรียกว่า SeedClass ตัวอย่างเช่นถ้าคุณต้องการที่จะแสดงรายการทุกชั้นใน 'java.io' java.io.Fileชั้นเมล็ดอาจจะ

  2. คลาสของคุณอยู่ในไดเร็กทอรีหรือในไฟล์ JAR ซึ่งมีข้อมูลซอร์สไฟล์ (ไม่ใช่ไฟล์ซอร์สโค้ด แต่เป็นเพียงไฟล์ซอร์ส) เท่าที่ฉันได้ลองใช้งานได้เกือบ 100% ยกเว้นคลาส JVM (คลาสเหล่านั้นมาพร้อมกับ JVM)

  3. โปรแกรมของคุณต้องได้รับอนุญาตให้เข้าถึง ProtectionDomain ของคลาสเหล่านั้น หากโปรแกรมของคุณโหลดภายในเครื่องก็ไม่น่ามีปัญหา

ฉันได้ทดสอบโปรแกรมสำหรับการใช้งานปกติเท่านั้นดังนั้นจึงอาจยังมีปัญหา

ฉันหวังว่านี่จะช่วยได้.


1
ดูเหมือนจะเป็นสิ่งที่น่าสนใจมาก! ฉันจะพยายามใช้มัน หากพบว่ามีประโยชน์สำหรับฉันฉันสามารถใช้รหัสของคุณในโครงการโอเพนซอร์สได้หรือไม่

1
ฉันกำลังเตรียมที่จะโอเพ่นซอร์สด้วย ลุยเลย: D
NawaMan

@NawaMan: ในที่สุดคุณเปิดแหล่งที่มาหรือไม่? ถ้าเป็นเช่นนั้นเราจะหาเวอร์ชันล่าสุดได้ที่ไหน ขอบคุณ!
Joanis

1

นี่คืออีกทางเลือกหนึ่งการปรับเปลี่ยนเล็กน้อยสำหรับคำตอบอื่นในด้านบน / ด้านล่าง:

Reflections reflections = new Reflections("com.example.project.package", 
    new SubTypesScanner(false));
Set<Class<? extends Object>> allClasses = 
    reflections.getSubTypesOf(Object.class);

0

ย้อนกลับไปเมื่อแอพเพล็ตเป็นสถานที่ทั่วไปอาจมี URL บน classpath เมื่อ classloader ต้องการคลาสมันจะค้นหาตำแหน่งทั้งหมดบน classpath รวมถึงทรัพยากร http เนื่องจากคุณสามารถมีสิ่งต่างๆเช่น URL และไดเร็กทอรีบน classpath จึงไม่มีวิธีง่ายๆในการรับรายชื่อคลาสที่ชัดเจน

อย่างไรก็ตามคุณสามารถเข้าใกล้ได้ดี ห้องสมุด Spring บางแห่งกำลังดำเนินการอยู่ในขณะนี้ คุณสามารถรับ jar ทั้งหมดบน classpath และเปิดขึ้นมาเหมือนไฟล์ จากนั้นคุณสามารถใช้รายการไฟล์นี้และสร้างโครงสร้างข้อมูลที่มีคลาสของคุณ


0

ใช้การพึ่งพา maven:

groupId: net.sf.extcos
artifactId: extcos
version: 0.4b

จากนั้นใช้รหัสนี้:

ComponentScanner scanner = new ComponentScanner();
        Set classes = scanner.getClasses(new ComponentQuery() {
            @Override
            protected void query() {
                select().from("com.leyton").returning(allExtending(DynamicForm.class));
            }
        });

-2

Brent - เหตุผลที่การเชื่อมโยงเป็นวิธีหนึ่งที่เกี่ยวข้องกับความจริงที่ว่าคลาสใด ๆ ในส่วนประกอบใด ๆ ของ CLASSPATH ของคุณสามารถประกาศตัวเองในแพ็คเกจใดก็ได้ (ยกเว้น java / javax) ดังนั้นจึงไม่มีการแมปคลาสทั้งหมดใน "แพ็กเกจ" ที่กำหนดเพราะไม่มีใครรู้และไม่สามารถรู้ได้ คุณสามารถอัปเดตไฟล์ jar ในวันพรุ่งนี้และลบหรือเพิ่มคลาสได้ มันเหมือนกับการพยายามหารายชื่อคนที่ชื่อ John / Jon / Johan ในทุกประเทศทั่วโลก - ไม่มีพวกเราที่ฉลาดรอบรู้ดังนั้นพวกเราจะไม่มีคำตอบที่ถูกต้อง


คำตอบเชิงปรัชญาที่ดี แต่การสแกน CDI bean ทำงานอย่างไร? หรือ Hibernate scan @Entities อย่างไร
Ondra Žižka
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.