คุณสามารถค้นหาคลาสทั้งหมดในแพ็คเกจโดยใช้การสะท้อนได้หรือไม่?


528

เป็นไปได้หรือไม่ที่จะหาคลาสหรืออินเตอร์เฟสทั้งหมดในแพ็คเกจที่กำหนด? (ดูอย่างรวดเร็วเช่นPackageมันจะดูเหมือนไม่.)


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

2
โปรดทราบว่าโพสต์นี้
barfuin

1
ดูคำตอบที่เกี่ยวข้อง: stackoverflow.com/a/30149061/4102160
Cfx

1
โปรดทราบว่าโพสต์นี้
sp00m

1
ดูคำตอบของฉันด้านล่างเกี่ยวกับ ClassGraph ปัจจุบันเป็นวิธีที่มีประสิทธิภาพที่สุดสำหรับการสแกนเส้นทางพา ธ คลาสและโมดูล
ลุคฮัทชิสัน

คำตอบ:


374

เนื่องจากลักษณะของคลาสโหลดเดอร์แบบไดนามิกจึงเป็นไปไม่ได้ ตัวโหลดคลาสไม่จำเป็นต้องแจ้งให้ VM ทราบว่าคลาสใดที่มันสามารถให้ได้แทนพวกมันเป็นเพียงแค่ส่งคำร้องขอคลาสและต้องส่งคืนคลาสหรือส่งข้อยกเว้น

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

หากมีคลาสที่สร้างขึ้นหรือส่งจากระยะไกลคุณจะไม่สามารถค้นพบคลาสเหล่านั้นได้

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

ภาคผนวก: ไลบรารี Reflectionsจะอนุญาตให้คุณค้นหาคลาสใน classpath ปัจจุบัน สามารถใช้รับคลาสทั้งหมดในแพ็คเกจ:

 Reflections reflections = new Reflections("my.project.prefix");

 Set<Class<? extends Object>> allClasses = 
     reflections.getSubTypesOf(Object.class);

12
การที่ฉันไม่สามารถสืบค้นชื่อคลาสได้บั๊กฉันเป็นเวลานาน แน่นอนว่ามันยากและประสิทธิภาพอาจแตกต่างกันไปและสำหรับ classloaders บางรายรายการนั้นไม่ได้กำหนดหรือไม่ได้ จำกัด แต่ก็มีวิธีที่จะแก้ไขปัญหานี้ได้
นายเงางามและใหม่安宇

16
โปรดทราบว่าการแก้ปัญหานี้จะไม่ทำงานตามค่าเริ่มต้น getSubTypesOf ไม่ส่งคืนชนิดย่อยของวัตถุ ดูโซลูชันของ Aleksander Blomskøldสำหรับวิธีกำหนดค่า SubTypeScanner
Alex Spurling

17
การสะท้อนต้องใช้ฝรั่ง ฝรั่งมีขนาดใหญ่ เวอร์ชัน 14.0.1 คือ 2.1MB
ไมค์โจนส์

3
ไม่ได้ผลสำหรับฉัน Mac OSX - การพึ่งพาการสะท้อนรุ่น 0.9.9-RC1 (maven) - JDK 1.7 พิจารณาคำตอบที่ยอมรับ @ AleksanderBlomskøldคำตอบคือคำตอบที่หนึ่ง !!!!!
Konstantinos Margaritis

68
หากสิ่งนี้ส่งคืนรายการว่างให้เริ่มต้นวัตถุ Reflections ดังนี้: Reflections reflections = new Reflections ("your.package.here", SubTypesScanner ใหม่ (false))
João Rocha da Silva

186

คุณน่าจะลองดูที่ไลบรารี่โอเพ่นซอร์ส ด้วยคุณสามารถบรรลุสิ่งที่คุณต้องการ

ขั้นแรกให้ตั้งค่าดัชนีการสะท้อนกลับ (มันค่อนข้างยุ่งเนื่องจากการค้นหาคลาสทั้งหมดจะถูกปิดใช้งานตามค่าเริ่มต้น):

List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());

Reflections reflections = new Reflections(new ConfigurationBuilder()
    .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
    .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
    .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("org.your.package"))));

จากนั้นคุณสามารถค้นหาวัตถุทั้งหมดในแพ็คเกจที่กำหนด:

Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);

6
โอ้นี่เราไป: code.google.com/p/reflections/issues/detail?id=122 วัตถุถูกยกเว้นโดยค่าเริ่มต้น แต่คุณสามารถ rejigger ได้ ขอบคุณที่ชี้นำฉันไปที่ห้องสมุดนี้มันเยี่ยมมาก!
mtrc

1
ฉันพบปัญหาเกี่ยวกับ Mac ของฉันด้วยรหัสนี้ (เกี่ยวข้องกับห้องสมุดท้องถิ่น) แต่การใช้.addUrls(ClasspathHelper.forJavaClassPath())แทนที่จะแก้ไขปัญหาข้างต้นให้ฉัน รหัสน้อยเช่นกัน!
David Pärsson

3
ถ้าใครสงสัยว่าวิธีที่ง่ายที่สุดในการรับแพ็คเกจเริ่มต้นคือการใช้คำนำหน้าจะเป็นสตริงว่างเปล่า -> ""
JBA

2
"สะท้อน" ห้องสมุดมีใบอนุญาตหากิน: github.com/ronmamo/reflections/blob/master/COPYING.txt เคล็ดลับคือใบอนุญาตให้ใช้งานได้ฟรีเท่านั้นใบอนุญาต ดังนั้นการใช้ห้องสมุดจริง ๆ (ไม่ใช่ใบอนุญาต) ทุกคนจะต้องติดต่อผู้เขียนและเจรจาเงื่อนไขการใช้งาน
Serge Rogatch

3
เสิร์จฉันคิดว่าคุณเข้าใจผิด WTFPL: wtfpl.netฉันคิดว่า WTFPL หมายความว่าคุณมีอิสระที่จะทำสิ่งที่คุณต้องการไม่ใช่แค่กับใบอนุญาต แต่ด้วยรหัสเช่นกัน
Richo

122

Google Guava 14 มีคลาสใหม่ClassPathพร้อมสามวิธีในการสแกนหาคลาสระดับบนสุด:

  • getTopLevelClasses()
  • getTopLevelClasses(String packageName)
  • getTopLevelClassesRecursive(String packageName)

ดูClassPathjavadocsสำหรับข้อมูลเพิ่มเติม


สิ่งนี้ใช้ได้กับฉันในกรณีที่การสะท้อนไม่สามารถทำได้ (ไม่มีบรรพบุรุษโดยตรงทั่วไปไม่มีคำอธิบายประกอบทั่วไป)
Riccardo Cossu

1
ในฐานะที่ผมกล่าวถึงในความคิดเห็นด้านล่าง , ClassPathมีการติดแท็กด้วย@Betaจึงไม่อาจเป็นความคิดที่ดีสำหรับบางคน ...
คริสเตียน

1
การบอกว่านี่เป็นงานที่การสะท้อนกลับไม่ได้แปลกไปสักหน่อยวิธีการแก้ปัญหาอย่างไม่ต้องสงสัยถูกนำมาใช้โดยใช้ฟังก์ชั่นการสะท้อน (และตัวโหลดคลาส)
Maarten Bodewes

5
ฉันคิดว่าเขาหมายถึงห้องสมุดรีเฟลคชั่นที่กล่าวถึงในคำตอบอื่น ๆ
Christoph Leiter

ทำงานภายใต้ Java 11 หากใช้ guava เวอร์ชัน 28.1-jre
gorjanz

111

คุณสามารถใช้วิธีการนี้1ClassLoaderที่ใช้

/**
 * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
 *
 * @param packageName The base package
 * @return The classes
 * @throws ClassNotFoundException
 * @throws IOException
 */
private static Class[] getClasses(String packageName)
        throws ClassNotFoundException, IOException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    assert classLoader != null;
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while (resources.hasMoreElements()) {
        URL resource = resources.nextElement();
        dirs.add(new File(resource.getFile()));
    }
    ArrayList<Class> classes = new ArrayList<Class>();
    for (File directory : dirs) {
        classes.addAll(findClasses(directory, packageName));
    }
    return classes.toArray(new Class[classes.size()]);
}

/**
 * Recursive method used to find all classes in a given directory and subdirs.
 *
 * @param directory   The base directory
 * @param packageName The package name for classes found inside the base directory
 * @return The classes
 * @throws ClassNotFoundException
 */
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists()) {
        return classes;
    }
    File[] files = directory.listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            assert !file.getName().contains(".");
            classes.addAll(findClasses(file, packageName + "." + file.getName()));
        } else if (file.getName().endsWith(".class")) {
            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
        }
    }
    return classes;
}

__________
1วิธีนี้เริ่มต้นจากhttp://snippets.dzone.com/posts/show/4831ซึ่งเก็บถาวรโดย Internet Archive ซึ่งเชื่อมโยงกับตอนนี้ ข้อมูลยังมีบริการที่https://dzone.com/articles/get-all-classes-within-package


6
ฉันมีปัญหากับสิ่งนี้หากเส้นทางของฉันรวมช่องว่าง คลาส URL กำลังหลบหนีช่องว่างไป%20แต่ตัวnew File()สร้างถือว่าเป็นตัวอักษรเปอร์เซ็นต์ที่ลงชื่อสองศูนย์ ฉันแก้ไขมันโดยการเปลี่ยนdirs.add(...)บรรทัดเป็น: dirs.add(new File(resource.toURI())); นี่ก็หมายความว่าฉันต้องเพิ่มURISyntaxExceptionไปยังส่วนการโยนของgetClasses
Kip

19
คุณเพิ่งคัดลอกจาก dzone.com/articles/get-all-classes-within-package ! โปรดอ้างอิงแหล่งที่มาในครั้งต่อไป
RS

19
+1 เนื่องจากโซลูชันนี้ไม่จำเป็นต้องใช้ไลบรารีภายนอก ... ไม่เคยลองใช้รหัสของคุณกับห้องสมุดเพื่อให้ได้สิ่งเล็ก ๆ น้อย ๆ เช่นนี้ คุณรู้หรือไม่ว่าคุณกำลังเพิ่มพื้นผิวการโจมตีที่เป็นไปได้สำหรับผู้โจมตี? พฤศจิกายน 2015 ปัญหา Apache Commons ค้นพบที่นำไปสู่การดำเนินการคำสั่งระยะไกลเพียงแค่มี Apache Commons ใน classpath ของแอปที่ปรับใช้บน Jboss / Weblogic [ foxglovesecurity.com/2015/11/06/…
sc0p

@Qix บันทึกไว้อย่างถูกต้องว่ารหัสนี้ไม่สนับสนุน jar เพื่อที่จะให้การสนับสนุนขวดและไดเรกทอรี รหัสมีการเปลี่ยนแปลงตามที่ระบุไว้ด้านล่าง:
user1523177

1
วิธีแก้ปัญหาที่ดี แต่น่าจะดีกว่าถ้า 'Class.forName (String className)' จะถูกแทนที่ด้วย 'Class.forName (String className, บูลีนเริ่มต้น, ตัวโหลด ClassLoader)' โดยที่ 'initialize = false;' เพื่อไม่ให้สร้างอินสแตนซ์ของคลาส
Andrei_N

99

ฤดูใบไม้ผลิ

ตัวอย่างนี้ใช้สำหรับ Spring 4 แต่คุณสามารถค้นหา classpath scanner ในเวอร์ชันก่อนหน้าได้เช่นกัน

// create scanner and disable default filters (that is the 'false' argument)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// add include filters which matches all the classes (or use your own)
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));

// get matching classes defined in the package
final Set<BeanDefinition> classes = provider.findCandidateComponents("my.package.name");

// this is how you can load the class type from BeanDefinition instance
for (BeanDefinition bean: classes) {
    Class<?> clazz = Class.forName(bean.getBeanClassName());
    // ... do your magic with the class ...
}

Google Guava

หมายเหตุ:ในเวอร์ชัน 14 API ยังคงถูกทำเครื่องหมายเป็น@Betaดังนั้นโปรดระวังในรหัสการผลิต

final ClassLoader loader = Thread.currentThread().getContextClassLoader();

for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
  if (info.getName().startsWith("my.package.")) {
    final Class<?> clazz = info.load();
    // do something with your clazz
  }
}

5
คำตอบที่ยอดเยี่ยม มีการแก้ปัญหามากเกินไปที่นี่ซึ่งเป็น verbose ไม่ผ่านการทดสอบและไม่ทำงาน! อันนี้ยอดเยี่ยม: มันกระชับและทดสอบ (มันมาจาก Guava) ดีมาก! มันมีประโยชน์มันสมควรได้รับ upvotes มากขึ้น
JeanValjean

น่าเสียดายที่ClassPathคลาสใน Guava นั้นถูกทำเครื่องหมายด้วย@Beta: "APIs ที่มีคำอธิบายประกอบ @Beta ที่ระดับหรือวิธีการอาจมีการเปลี่ยนแปลงพวกเขาสามารถแก้ไขได้ในทางใดทางหนึ่งหรือแม้กระทั่งลบออกในรุ่นใหญ่ ๆ หากรหัสของคุณ เป็นไลบรารี่เอง (นั่นคือมันถูกใช้กับ CLASSPATH ของผู้ใช้ที่อยู่นอกการควบคุมของคุณเอง) คุณไม่ควรใช้เบต้า API เว้นแต่คุณจะทำการบรรจุใหม่ ... " code.google.com/p/guava-l ไลบรารี/
Christian

@ คริสเตียนจุดที่ดีฉันไม่ได้สังเกตเห็น! ขอบคุณ. ฉันจะเพิ่มคำตอบอื่นโดยใช้เครื่องสแกนคลาสพา ธ ของ Spring ซึ่งไม่ใช่รุ่นเบต้าแน่นอน
voho

ในการค้นหาคลาสแบบสแตติกที่ซ้อนอยู่โดยใช้วิธีการแก้ปัญหาฝรั่งgetAllClasses()สามารถใช้วิธีการ
v.ladynev

1
The Spring solution จะทำงานได้ก็ต่อเมื่อทำงานจาก jar ที่ปฏิบัติการได้
ลุค

38

สวัสดี. ฉันมักจะมีปัญหาบางอย่างกับการแก้ปัญหาข้างต้น (และในเว็บไซต์อื่น ๆ )
ฉันในฐานะนักพัฒนากำลังเขียนโปรแกรมเสริมสำหรับ API API ป้องกันการใช้ไลบรารีภายนอกหรือเครื่องมือของบุคคลที่สาม การตั้งค่ายังประกอบด้วยการผสมผสานของรหัสในไฟล์ jar หรือ zip และไฟล์คลาสที่อยู่โดยตรงในบางไดเรกทอรี ดังนั้นรหัสของฉันจะต้องสามารถจัดการทุกการตั้งค่า หลังจากการวิจัยจำนวนมากฉันได้พบวิธีการที่จะทำงานอย่างน้อย 95% ของการตั้งค่าที่เป็นไปได้ทั้งหมด

รหัสต่อไปนี้นั้นเป็นวิธีการ overkill ที่สามารถใช้ได้เสมอ

รหัส:

รหัสนี้สแกนแพคเกจที่กำหนดสำหรับคลาสทั้งหมดที่รวมอยู่ในนั้น ClassLoaderมันจะทำงานสำหรับการเรียนทั้งหมดในปัจจุบัน

/**
 * Private helper method
 * 
 * @param directory
 *            The directory to start with
 * @param pckgname
 *            The package name to search for. Will be needed for getting the
 *            Class object.
 * @param classes
 *            if a file isn't loaded but still is in the directory
 * @throws ClassNotFoundException
 */
private static void checkDirectory(File directory, String pckgname,
        ArrayList<Class<?>> classes) throws ClassNotFoundException {
    File tmpDirectory;

    if (directory.exists() && directory.isDirectory()) {
        final String[] files = directory.list();

        for (final String file : files) {
            if (file.endsWith(".class")) {
                try {
                    classes.add(Class.forName(pckgname + '.'
                            + file.substring(0, file.length() - 6)));
                } catch (final NoClassDefFoundError e) {
                    // do nothing. this class hasn't been found by the
                    // loader, and we don't care.
                }
            } else if ((tmpDirectory = new File(directory, file))
                    .isDirectory()) {
                checkDirectory(tmpDirectory, pckgname + "." + file, classes);
            }
        }
    }
}

/**
 * Private helper method.
 * 
 * @param connection
 *            the connection to the jar
 * @param pckgname
 *            the package name to search for
 * @param classes
 *            the current ArrayList of all classes. This method will simply
 *            add new classes.
 * @throws ClassNotFoundException
 *             if a file isn't loaded but still is in the jar file
 * @throws IOException
 *             if it can't correctly read from the jar file.
 */
private static void checkJarFile(JarURLConnection connection,
        String pckgname, ArrayList<Class<?>> classes)
        throws ClassNotFoundException, IOException {
    final JarFile jarFile = connection.getJarFile();
    final Enumeration<JarEntry> entries = jarFile.entries();
    String name;

    for (JarEntry jarEntry = null; entries.hasMoreElements()
            && ((jarEntry = entries.nextElement()) != null);) {
        name = jarEntry.getName();

        if (name.contains(".class")) {
            name = name.substring(0, name.length() - 6).replace('/', '.');

            if (name.contains(pckgname)) {
                classes.add(Class.forName(name));
            }
        }
    }
}

/**
 * Attempts to list all the classes in the specified package as determined
 * by the context class loader
 * 
 * @param pckgname
 *            the package name to search
 * @return a list of classes that exist within that package
 * @throws ClassNotFoundException
 *             if something went wrong
 */
public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
        throws ClassNotFoundException {
    final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();

    try {
        final ClassLoader cld = Thread.currentThread()
                .getContextClassLoader();

        if (cld == null)
            throw new ClassNotFoundException("Can't get class loader.");

        final Enumeration<URL> resources = cld.getResources(pckgname
                .replace('.', '/'));
        URLConnection connection;

        for (URL url = null; resources.hasMoreElements()
                && ((url = resources.nextElement()) != null);) {
            try {
                connection = url.openConnection();

                if (connection instanceof JarURLConnection) {
                    checkJarFile((JarURLConnection) connection, pckgname,
                            classes);
                } else if (connection instanceof FileURLConnection) {
                    try {
                        checkDirectory(
                                new File(URLDecoder.decode(url.getPath(),
                                        "UTF-8")), pckgname, classes);
                    } catch (final UnsupportedEncodingException ex) {
                        throw new ClassNotFoundException(
                                pckgname
                                        + " does not appear to be a valid package (Unsupported encoding)",
                                ex);
                    }
                } else
                    throw new ClassNotFoundException(pckgname + " ("
                            + url.getPath()
                            + ") does not appear to be a valid package");
            } catch (final IOException ioex) {
                throw new ClassNotFoundException(
                        "IOException was thrown when trying to get all resources for "
                                + pckgname, ioex);
            }
        }
    } catch (final NullPointerException ex) {
        throw new ClassNotFoundException(
                pckgname
                        + " does not appear to be a valid package (Null pointer exception)",
                ex);
    } catch (final IOException ioex) {
        throw new ClassNotFoundException(
                "IOException was thrown when trying to get all resources for "
                        + pckgname, ioex);
    }

    return classes;
}

วิธีการทั้งสามนี้ช่วยให้คุณสามารถค้นหาชั้นเรียนทั้งหมดในแพ็คเกจที่กำหนด
คุณใช้มันแบบนี้:

getClassesForPackage("package.your.classes.are.in");

คำอธิบาย:

ClassLoaderวิธีแรกที่ได้รับในปัจจุบัน จากนั้นจะดึงทรัพยากรทั้งหมดที่มีแพ็คเกจดังกล่าวและทำซ้ำของURLs เหล่านี้ จากนั้นจะสร้างURLConnectionและกำหนดประเภทของ URl ที่เรามี มันสามารถเป็นไดเรคทอรี ( FileURLConnection) หรือไดเร็กตอรี่ที่อยู่ในไฟล์ jar หรือ zip ( JarURLConnection) ขึ้นอยู่กับประเภทของการเชื่อมต่อที่เรามีสองวิธีที่แตกต่างกันจะถูกเรียกว่า

FileURLConnectionครั้งแรกที่ช่วยให้เห็นสิ่งที่เกิดขึ้นถ้ามันเป็น
มันตรวจสอบก่อนว่าไฟล์ที่ส่งผ่านมีอยู่และเป็นไดเรกทอรีหรือไม่ หากเป็นกรณีนี้จะตรวจสอบว่าเป็นไฟล์คลาสหรือไม่ หากดังนั้นวัตถุจะถูกสร้างขึ้นและนำมาใส่ในClass ArrayListถ้ามันไม่ใช่ไฟล์คลาส แต่เป็นไดเรกทอรีเราก็ทำซ้ำมันและทำสิ่งเดียวกัน เคส / ไฟล์อื่น ๆ ทั้งหมดจะถูกละเว้น

ถ้าURLConnectionเป็นJarURLConnectionตัวช่วยส่วนตัวอื่น ๆ จะถูกเรียก วิธีนี้วนซ้ำทุกรายการในไฟล์ zip / jar หากหนึ่งในรายการเป็นไฟล์เรียนและอยู่ภายในของแพคเกจที่วัตถุจะถูกสร้างขึ้นและเก็บไว้ในClassArrayList

หลังจากที่แยกวิเคราะห์ทรัพยากรทั้งหมดแล้ว (เมธอด main) จะส่งคืนการArrayListบรรจุคลาสทั้งหมดในแพ็กเกจที่กำหนดซึ่งปัจจุบันClassLoaderรู้เกี่ยวกับ

หากกระบวนการล้มเหลว ณ จุดใด ๆClassNotFoundExceptionจะมีการโยนข้อมูลต่อไปโดยละเอียดเกี่ยวกับสาเหตุที่แน่นอน


4
ตัวอย่างนี้ดูเหมือนว่าจะต้องนำเข้าsun.net.www.protocol.file.FileURLConnectionซึ่งสร้างคำเตือนในเวลารวบรวม ("คำเตือน: sun.net.www.protocol.file.FileURLConnection เป็น API ที่เป็นกรรมสิทธิ์ของ Sun และอาจถูกลบออกในรุ่นอนาคต") มีทางเลือกอื่นในการใช้คลาสนั้นหรือไม่สามารถเตือนได้โดยใช้คำอธิบายประกอบ
Christian

วิธีนี้ใช้ไม่ได้กับคลาส bootstrap เช่นเดียวกับใน java.lang, java.util, ... สามารถพบได้โดยรับ System.getProperty ("sun.boot.class.path") แยกด้วย: หรือ; (ขึ้นอยู่กับระบบปฏิบัติการ) และจากนั้นเรียกใช้รุ่นที่แก้ไขเล็กน้อยข้างต้นไดเรกทอรีตรวจสอบและ checkJarFile
coderforlife

1
คุณสามารถแก้ไขคำเตือน / ข้อผิดพลาดได้โดยใช้ connection.getClass (). getCanonicalName (). เท่ากับ ("sun.net.www.protocol.file.FileURLConnection") หากคุณต้องการคุณสามารถสร้าง URLConnection ซึ่งคุณคิดว่าควรใช้ sun.net.www.protocol.file.FileURLConnection และเปรียบเทียบชื่อของคลาสการเชื่อมต่อกับชื่อคลาสที่คุณสร้างขึ้น หากทั้งคู่เหมือนกันคุณสามารถใช้เป็นตัวอย่างของ sun.net.www.protocol.file.FileURLConnection แทนที่จะล้มเหลวในกรณีที่ชื่อคลาสเปลี่ยนไป
William Deans

1
@Christian คุณสามารถหลีกเลี่ยงการเชื่อมต่อ FileURLC ทำสิ่งนี้: if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }มันเป็นสิ่งที่ฉันทำในรหัสของฉันเพื่อค้นหาคลาสที่มีคำอธิบายประกอบ JPA
Zardoz89

15

โดยไม่ต้องใช้ไลบรารีเพิ่มเติม:

package test;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) throws Exception{
        List<Class> classes = getClasses(Test.class.getClassLoader(),"test");
        for(Class c:classes){
            System.out.println("Class: "+c);
        }
    }

    public static List<Class> getClasses(ClassLoader cl,String pack) throws Exception{

        String dottedPackage = pack.replaceAll("[/]", ".");
        List<Class> classes = new ArrayList<Class>();
        URL upackage = cl.getResource(pack);

        DataInputStream dis = new DataInputStream((InputStream) upackage.getContent());
        String line = null;
        while ((line = dis.readLine()) != null) {
            if(line.endsWith(".class")) {
               classes.add(Class.forName(dottedPackage+"."+line.substring(0,line.lastIndexOf('.'))));
            }
        }
        return classes;
    }
}

เมื่อฉันเรียกใช้สิ่งนี้ใน JAR upackageก็คือnull... :(
Christian

สำหรับแพ็คเกจ "com.mycompany.beans" ให้แทนที่ "ทดสอบ" ด้วย "com / mycompany / beans"
James Jithin

4
ฉันได้รับ null เมื่อใช้รหัสนี้ ดูเหมือนว่าจะทำงานเฉพาะในกรณีที่ขวดของคุณเป็นที่ปฏิบัติการ
Alao

หากคุณได้รับชื่อแพ็คเกจจากString pack = getPackage().getName();นั้นคุณต้องเพิ่มpack = pack.replaceAll("[.]", "/");
user2682877

13

ในตัวโหลดคลาสทั่วไปไม่อนุญาตให้สแกนผ่านคลาสทั้งหมดบน classpath แต่โดยปกติแล้ว class loader ที่ใช้เพียงอย่างเดียวคือ UrlClassLoader ซึ่งเราสามารถดึงรายชื่อของไดเรกทอรีและไฟล์ jar (ดูที่getURLs ) และเปิดมันทีละตัวเพื่อแสดงรายการคลาสที่มีอยู่ วิธีการนี้เรียกว่าการสแกนคลาสพา ธ ถูกนำไปใช้ในการสแกนและการสะท้อนกลับ

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

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

// package-info.java
@IndexSubclasses
package my.package;

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

โปรดสังเกตว่าไม่จำเป็นต้องมีการตั้งค่าเพิ่มเติมเนื่องจากการสแกนเป็นไปโดยอัตโนมัติโดยสมบูรณ์ด้วยจาวาคอมไพเลอร์ Java โดยอัตโนมัติค้นพบโปรเซสเซอร์ที่พบใน classpath


ค้นพบคลาสที่บรรจุใน Jar หรือไม่? ดูเหมือนจะไม่ได้ผลสำหรับฉัน
asgs

เครื่องมือใดที่คุณพยายามใช้
Sławek

ฉันใช้งาน Reflections lib แต่ฉันทำให้มันทำงานหลังจากทำตามวิธีแก้ปัญหาที่ @Aleksander Blomskøldใช้สำหรับ lib รุ่นนี้
asgs

สวัสดีฉันใช้ eclipse และไม่สามารถใช้งานได้ ClassIndex.getPackageClasses ("my.package") ส่งคืนแผนที่เปล่า
Juan

11

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

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

5

นี่คือวิธีที่ฉันทำ ฉันสแกนโฟลเดอร์ย่อยทั้งหมด (แพ็คเกจย่อย) และฉันไม่พยายามโหลดคลาสที่ไม่ระบุชื่อ:

   /**
   * Attempts to list all the classes in the specified package as determined
   * by the context class loader, recursively, avoiding anonymous classes
   * 
   * @param pckgname
   *            the package name to search
   * @return a list of classes that exist within that package
   * @throws ClassNotFoundException
   *             if something went wrong
   */
  private static List<Class> getClassesForPackage(String pckgname) throws ClassNotFoundException {
      // This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
      ArrayList<File> directories = new ArrayList<File>();
      String packageToPath = pckgname.replace('.', '/');
      try {
          ClassLoader cld = Thread.currentThread().getContextClassLoader();
          if (cld == null) {
              throw new ClassNotFoundException("Can't get class loader.");
          }

          // Ask for all resources for the packageToPath
          Enumeration<URL> resources = cld.getResources(packageToPath);
          while (resources.hasMoreElements()) {
              directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8")));
          }
      } catch (NullPointerException x) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)");
      } catch (UnsupportedEncodingException encex) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)");
      } catch (IOException ioex) {
          throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname);
      }

      ArrayList<Class> classes = new ArrayList<Class>();
      // For every directoryFile identified capture all the .class files
      while (!directories.isEmpty()){
          File directoryFile  = directories.remove(0);             
          if (directoryFile.exists()) {
              // Get the list of the files contained in the package
              File[] files = directoryFile.listFiles();

              for (File file : files) {
                  // we are only interested in .class files
                  if ((file.getName().endsWith(".class")) && (!file.getName().contains("$"))) {
                      // removes the .class extension
                      int index = directoryFile.getPath().indexOf(packageToPath);
                      String packagePrefix = directoryFile.getPath().substring(index).replace('/', '.');;                          
                    try {                  
                      String className = packagePrefix + '.' + file.getName().substring(0, file.getName().length() - 6);                            
                      classes.add(Class.forName(className));                                
                    } catch (NoClassDefFoundError e)
                    {
                      // do nothing. this class hasn't been found by the loader, and we don't care.
                    }
                  } else if (file.isDirectory()){ // If we got to a subdirectory
                      directories.add(new File(file.getPath()));                          
                  }
              }
          } else {
              throw new ClassNotFoundException(pckgname + " (" + directoryFile.getPath() + ") does not appear to be a valid package");
          }
      }
      return classes;
  }  

4

ฉันรวบรวมโปรเจ็กต์ github ง่ายๆที่แก้ปัญหานี้:

https://github.com/ddopson/java-class-enumerator

มันควรจะทำงานสำหรับ classpaths ที่ใช้ทั้งไฟล์และสำหรับไฟล์ jar

หากคุณเรียกใช้ 'ทำ' หลังจากตรวจสอบโครงการแล้วมันจะพิมพ์ออกมา:

 Cleaning...
rm -rf build/
 Building...
javac -d build/classes src/pro/ddopson/ClassEnumerator.java src/test/ClassIShouldFindOne.java src/test/ClassIShouldFindTwo.java src/test/subpkg/ClassIShouldFindThree.java src/test/TestClassEnumeration.java
 Making JAR Files...
jar cf build/ClassEnumerator_test.jar -C build/classes/ . 
jar cf build/ClassEnumerator.jar -C build/classes/ pro
 Running Filesystem Classpath Test...
java -classpath build/classes test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'file:/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: FileName 'ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: FileName 'ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: FileName 'subpkg'  =>  class 'null'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test/subpkg'
ClassDiscovery: FileName 'ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: FileName 'TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Running JAR Classpath Test...
java -classpath build/ClassEnumerator_test.jar  test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'jar:file:/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar!/test'
ClassDiscovery: Reading JAR file: '/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar'
ClassDiscovery: JarEntry 'META-INF/'  =>  class 'null'
ClassDiscovery: JarEntry 'META-INF/MANIFEST.MF'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/ClassEnumerator.class'  =>  class 'null'
ClassDiscovery: JarEntry 'test/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: JarEntry 'test/ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: JarEntry 'test/subpkg/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/subpkg/ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: JarEntry 'test/TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Tests Passed. 

ดูคำตอบอื่นของฉัน


4

ใช่คุณสามารถใช้ API ได้เล็กน้อยนี่คือวิธีที่ฉันชอบทำประสบปัญหานี้ซึ่งฉันใช้แกนไฮเบอร์เนต & ต้องค้นหาคลาสที่มีหมายเหตุประกอบอยู่ด้วย

ทำให้คำอธิบายประกอบแบบกำหนดเองเหล่านี้ใช้ซึ่งคุณจะทำเครื่องหมายว่าคุณต้องการเลือกคลาสใด

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EntityToBeScanned {

}

จากนั้นทำเครื่องหมายชั้นเรียนของคุณด้วยเช่น

@EntityToBeScanned 
public MyClass{

}

ทำให้คลาสยูทิลิตี้นี้ซึ่งมีวิธีการดังต่อไปนี้

public class ClassScanner {

    public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
        Reflections reflections = new Reflections(".*");
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
        return annotated;
    }

}

เรียกใช้เมธอด allFoundClassesAnnotatedWithEntityToBeScanned ()เพื่อรับชุดของคลาสที่พบ

คุณจะต้อง libs ที่ระบุด้านล่าง

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>21.0</version>
    </dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.22.0-CR1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.10</version>
</dependency>

3

คุณต้องค้นหารายการตัวโหลดคลาสทั้งหมดในคลาสพา ธ :

    String pkg = "org/apache/commons/lang";
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    URL[] urls = ((URLClassLoader) cl).getURLs();
    for (URL url : urls) {
        System.out.println(url.getFile());
        File jar = new File(url.getFile());
        // ....
    }   

หากรายการคือไดเรกทอรีให้ค้นหาในไดเรกทอรีย่อยที่ถูกต้อง:

if (jar.isDirectory()) {
    File subdir = new File(jar, pkg);
    if (!subdir.exists())
        continue;
    File[] files = subdir.listFiles();
    for (File file : files) {
        if (!file.isFile())
            continue;
        if (file.getName().endsWith(".class"))
            System.out.println("Found class: "
                    + file.getName().substring(0,
                            file.getName().length() - 6));
    }
}   

หากรายการนั้นเป็นไฟล์และเป็น jar ให้ตรวจสอบรายการ ZIP ของมัน:

else {
    // try to open as ZIP
    try {
        ZipFile zip = new ZipFile(jar);
        for (Enumeration<? extends ZipEntry> entries = zip
                .entries(); entries.hasMoreElements();) {
            ZipEntry entry = entries.nextElement();
            String name = entry.getName();
            if (!name.startsWith(pkg))
                continue;
            name = name.substring(pkg.length() + 1);
            if (name.indexOf('/') < 0 && name.endsWith(".class"))
                System.out.println("Found class: "
                        + name.substring(0, name.length() - 6));
        }
    } catch (ZipException e) {
        System.out.println("Not a ZIP: " + e.getMessage());
    } catch (IOException e) {
        System.err.println(e.getMessage());
    }
}

ตอนนี้เมื่อคุณมีชื่อคลาสทั้งหมดภายในแพ็คเกจคุณสามารถลองโหลดด้วยการสะท้อนและวิเคราะห์ว่าเป็นคลาสหรืออินเทอร์เฟซเป็นต้น


คุณจะใส่อะไรในแพ็คเกจในไฟล์ Jar?
Kyle Bridenstine

ตัวอย่างนี้จะไม่ผ่านแพ็คเกจย่อย อาจเป็นเรื่องที่น่าสนใจสำหรับบางคน ... @ mr-tea เพียงระบุแพ็คเกจที่คุณต้องการ ฉันใส่สิ่งนี้ลงในโครงการระบุแพคเกจการทดสอบภายในโครงการนั้นรวบรวมและจัดทำและเรียกว่าตัวอย่างจากวิธีการหลักของ JAR ทำงานเหมือนจับใจ :)
Christian

3

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

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

คำตอบที่ถูกเขียนโดย sp00m ; ฉันได้เพิ่มการแก้ไขบางอย่างเพื่อให้ทำงานได้:

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

public final class ClassFinder {

    private final static char DOT = '.';
    private final static char SLASH = '/';
    private final static String CLASS_SUFFIX = ".class";
    private final static String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the given '%s' package exists?";

    public final static List<Class<?>> find(final String scannedPackage) {
        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        final String scannedPath = scannedPackage.replace(DOT, SLASH);
        final Enumeration<URL> resources;
        try {
            resources = classLoader.getResources(scannedPath);
        } catch (IOException e) {
            throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage), e);
        }
        final List<Class<?>> classes = new LinkedList<Class<?>>();
        while (resources.hasMoreElements()) {
            final File file = new File(resources.nextElement().getFile());
            classes.addAll(find(file, scannedPackage));
        }
        return classes;
    }

    private final static List<Class<?>> find(final File file, final String scannedPackage) {
        final List<Class<?>> classes = new LinkedList<Class<?>>();
        if (file.isDirectory()) {
            for (File nestedFile : file.listFiles()) {
                classes.addAll(find(nestedFile, scannedPackage));
            }
        //File names with the $1, $2 holds the anonymous inner classes, we are not interested on them. 
        } else if (file.getName().endsWith(CLASS_SUFFIX) && !file.getName().contains("$")) {

            final int beginIndex = 0;
            final int endIndex = file.getName().length() - CLASS_SUFFIX.length();
            final String className = file.getName().substring(beginIndex, endIndex);
            try {
                final String resource = scannedPackage + DOT + className;
                classes.add(Class.forName(resource));
            } catch (ClassNotFoundException ignore) {
            }
        }
        return classes;
    }

}

หากต้องการใช้มันก็แค่เรียกวิธีการค้นหาตามที่ sp00n พูดถึงในตัวอย่างนี้: ฉันได้เพิ่มการสร้างอินสแตนซ์ของคลาสถ้าจำเป็น

List<Class<?>> classes = ClassFinder.find("com.package");

ExcelReporting excelReporting;
for (Class<?> aClass : classes) {
    Constructor constructor = aClass.getConstructor();
    //Create an object of the class type
    constructor.newInstance();
    //...
}

3

ฉันเพิ่งเขียนคลาส util รวมถึงวิธีทดสอบคุณสามารถตรวจสอบได้ ~

IteratePackageUtil.java:

package eric.j2se.reflect;

import java.util.Set;

import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

/**
 * an util to iterate class in a package,
 * 
 * @author eric
 * @date Dec 10, 2013 12:36:46 AM
 */
public class IteratePackageUtil {
    /**
     * <p>
     * Get set of all class in a specified package recursively. this only support lib
     * </p>
     * <p>
     * class of sub package will be included, inner class will be included,
     * </p>
     * <p>
     * could load class that use the same classloader of current class, can't load system packages,
     * </p>
     * 
     * @param pkg
     *            path of a package
     * @return
     */
    public static Set<Class<? extends Object>> getClazzSet(String pkg) {
        // prepare reflection, include direct subclass of Object.class
        Reflections reflections = new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false), new ResourcesScanner())
                .setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.classLoaders(new ClassLoader[0])))
                .filterInputsBy(new FilterBuilder().includePackage(pkg)));

        return reflections.getSubTypesOf(Object.class);
    }

    public static void test() {
        String pkg = "org.apache.tomcat.util";

        Set<Class<? extends Object>> clazzSet = getClazzSet(pkg);
        for (Class<? extends Object> clazz : clazzSet) {
            System.out.println(clazz.getName());
        }
    }

    public static void main(String[] args) {
        test();
    }
}

3

โซลูชันของ Aleksander Blomskøldไม่ได้ผลสำหรับฉันสำหรับการทดสอบแบบกำหนดพารามิเตอร์@RunWith(Parameterized.class)เมื่อใช้ Maven การทดสอบถูกตั้งชื่ออย่างถูกต้องและยังพบ แต่ไม่ดำเนินการ:

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running some.properly.named.test.run.with.maven.SomeTest
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 sec

ปัญหาที่คล้ายกันได้รับรายงานที่นี่

ในกรณีของฉัน@Parametersคือการสร้างอินสแตนซ์ของแต่ละคลาสในแพ็คเกจ การทดสอบทำงานได้ดีเมื่อทำงานในเครื่องใน IDE อย่างไรก็ตามเมื่อรัน Maven ไม่มีคลาสที่พบกับโซลูชันของ Aleksander Blomskøld

ฉันทำให้มันทำงานกับ snipped ต่อไปนี้ซึ่งเป็นแรงบันดาลใจจากความคิดเห็นของ David Pärssonในคำตอบของ Aleksander Blomskøld:

Reflections reflections = new Reflections(new ConfigurationBuilder()
            .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
            .addUrls(ClasspathHelper.forJavaClassPath()) 
            .filterInputsBy(new FilterBuilder()
            .include(FilterBuilder.prefix(basePackage))));

Set<Class<?>> subTypesOf = reflections.getSubTypesOf(Object.class);

3

เกี่ยวกับสิ่งนี้:

public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
    final String pkgPath = pkgName.replace('.', '/');
    final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
    final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();

    Path root;
    if (pkg.toString().startsWith("jar:")) {
        try {
            root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
        } catch (final FileSystemNotFoundException e) {
            root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
        }
    } else {
        root = Paths.get(pkg);
    }

    final String extension = ".class";
    try (final Stream<Path> allPaths = Files.walk(root)) {
        allPaths.filter(Files::isRegularFile).forEach(file -> {
            try {
                final String path = file.toString().replace('/', '.');
                final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
                allClasses.add(Class.forName(name));
            } catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
            }
        });
    }
    return allClasses;
}

จากนั้นคุณสามารถโอเวอร์โหลดฟังก์ชัน:

public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
    return getClassesForPackage(pkg.getName());
}

หากคุณต้องการทดสอบ:

public static void main(final String[] argv) throws IOException, URISyntaxException {
    for (final Class<?> cls : getClassesForPackage("my.package")) {
        System.out.println(cls);
    }
    for (final Class<?> cls : getClassesForPackage(MyClass.class.getPackage())) {
        System.out.println(cls);
    }
}

หาก IDE ของคุณไม่มีตัวช่วยนำเข้า:

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

มันได้ผล:

  • จาก IDE ของคุณ

  • สำหรับไฟล์ JAR

  • โดยไม่ต้องพึ่งพาภายนอก


2

คำตอบเกือบทั้งหมดใช้Reflectionsหรืออ่านไฟล์คลาสจากระบบไฟล์ หากคุณพยายามอ่านคลาสจากระบบไฟล์คุณอาจได้รับข้อผิดพลาดเมื่อคุณจัดทำแพ็กเกจแอปพลิเคชันของคุณเป็น JAR หรืออื่น ๆ นอกจากนี้คุณอาจไม่ต้องการใช้ห้องสมุดแยกต่างหากสำหรับวัตถุประสงค์นั้น

นี่เป็นอีกวิธีหนึ่งซึ่งเป็นจาวาบริสุทธิ์และไม่ขึ้นอยู่กับระบบไฟล์

import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public class PackageUtil {

    public static Collection<Class> getClasses(final String pack) throws Exception {
        final StandardJavaFileManager fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
        return StreamSupport.stream(fileManager.list(StandardLocation.CLASS_PATH, pack, Collections.singleton(JavaFileObject.Kind.CLASS), false).spliterator(), false)
                .map(javaFileObject -> {
                    try {
                        final String[] split = javaFileObject.getName()
                                .replace(".class", "")
                                .replace(")", "")
                                .split(Pattern.quote(File.separator));

                        final String fullClassName = pack + "." + split[split.length - 1];
                        return Class.forName(fullClassName);
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }

                })
                .collect(Collectors.toCollection(ArrayList::new));
    }
}

Java 8 ไม่ได้เป็นต้อง คุณสามารถใช้ลูปแทนสตรีมได้ และคุณสามารถทดสอบได้เช่นนี้

public static void main(String[] args) throws Exception {
    final String pack = "java.nio.file"; // Or any other package
    PackageUtil.getClasses(pack).stream().forEach(System.out::println);
}

1
มันไม่ได้มีประโยชน์มากเพราะ: มันจำเป็นต้องมี JDKให้ใช้ToolProvider.getSystemJavaCompiler()รหัสนี้ไม่สแกนแพ็กเกจที่ซ้อนกัน
v.ladynev

ฉันไม่สามารถทำให้มันทำงานกับแพ็คเกจของโถภายนอกได้
Enrico Giurin

1

หากคุณไม่ได้ใช้ตัวโหลดคลาสแบบไดนามิกคุณสามารถค้นหา classpath และสำหรับแต่ละรายการค้นหาไดเรกทอรีหรือไฟล์ JAR


1

มูลค่าการกล่าวขวัญ

ถ้าคุณต้องการมีรายการของคลาสทั้งหมดภายใต้บางแพคเกจคุณสามารถใช้Reflectionวิธีต่อไปนี้:

List<Class> myTypes = new ArrayList<>();

Reflections reflections = new Reflections("com.package");
for (String s : reflections.getStore().get(SubTypesScanner.class).values()) {
    myTypes.add(Class.forName(s));
}

สิ่งนี้จะสร้างรายการของคลาสที่คุณสามารถใช้ในภายหลังตามที่คุณต้องการ


1

มันเป็นไปได้มาก แต่ถ้าไม่มีห้องสมุดเพิ่มเติมReflectionsมันยาก ...
มันยากเพราะคุณยังไม่ได้รับชื่อเต็มชั้น
และฉันใช้รหัสของClassFinderชั้นเรียนของฉัน:

package play.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by LINKOR on 26.05.2017 in 15:12.
 * Date: 2017.05.26
 */
public class FileClassFinder {
private JarFile file;
private boolean trouble;
public FileClassFinder(String filePath) {
    try {
        file = new JarFile(filePath);
    } catch (IOException e) {
        trouble = true;
    }
}

public List<String> findClasses(String pkg) {
    ArrayList<String> classes = new ArrayList<>();
    Enumeration<JarEntry> entries = file.entries();
    while (entries.hasMoreElements()) {
        JarEntry cls = entries.nextElement();
        if (!cls.isDirectory()) {
            String fileName = cls.getName();
            String className = fileName.replaceAll("/",         ".").replaceAll(File.pathSeparator, ".").substring(0, fileName.lastIndexOf('.'));
            if (className.startsWith(pkg)) classes.add(className.substring(pkg.length() + 1));
        }
    }
    return classes;
}
}

0

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

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
...
Class<?>[] foundClasses = new Class<?>[0];
final ArrayList<Class<?>> foundClassesDyn = new ArrayList<Class<?>>();

new java.io.File(
    klass.getResource(
        "/" + curPackage.replace( "." , "/")
    ).getFile()
).listFiles(
    new java.io.FileFilter() {
        public boolean accept(java.io.File file) {
            final String classExtension = ".class";

            if ( file.isFile()
                && file.getName().endsWith(classExtension)
                // avoid inner classes
                && ! file.getName().contains("$") )
            {
                try {
                    String className = file.getName();
                    className = className.substring(0, className.length() - classExtension.length());
                    foundClassesDyn.add( Class.forName( curPackage + "." + className ) );
                } catch (ClassNotFoundException e) {
                    e.printStackTrace(System.out);
                }
            }

            return false;
        }
    }
);

foundClasses = foundClassesDyn.toArray(foundClasses);

0

หากคุณเพียงแค่ต้องการโหลดกลุ่มของคลาสที่เกี่ยวข้องสปริงสามารถช่วยคุณได้

Spring สามารถสร้างรายการหรือแผนที่ของคลาสทั้งหมดที่ใช้อินเตอร์เฟสที่กำหนดในโค้ดบรรทัดเดียว รายการหรือแผนที่จะมีอินสแตนซ์ของคลาสทั้งหมดที่ใช้อินเทอร์เฟซนั้น

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

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


0

Java ธรรมดา: FindAllClassesUsingPlainJavaReflectionTest.java

@Slf4j
class FindAllClassesUsingPlainJavaReflectionTest {

  private static final Function<Throwable, RuntimeException> asRuntimeException = throwable -> {
    log.error(throwable.getLocalizedMessage());
    return new RuntimeException(throwable);
  };

  private static final Function<String, Collection<Class<?>>> findAllPackageClasses = basePackageName -> {

    Locale locale = Locale.getDefault();
    Charset charset = StandardCharsets.UTF_8;
    val fileManager = ToolProvider.getSystemJavaCompiler()
                                  .getStandardFileManager(/* diagnosticListener */ null, locale, charset);

    StandardLocation location = StandardLocation.CLASS_PATH;
    JavaFileObject.Kind kind = JavaFileObject.Kind.CLASS;
    Set<JavaFileObject.Kind> kinds = Collections.singleton(kind);
    val javaFileObjects = Try.of(() -> fileManager.list(location, basePackageName, kinds, /* recurse */ true))
                             .getOrElseThrow(asRuntimeException);

    String pathToPackageAndClass = basePackageName.replace(".", File.separator);
    Function<String, String> mapToClassName = s -> {
      String prefix = Arrays.stream(s.split(pathToPackageAndClass))
                            .findFirst()
                            .orElse("");
      return s.replaceFirst(prefix, "")
              .replaceAll(File.separator, ".");
    };

    return StreamSupport.stream(javaFileObjects.spliterator(), /* parallel */ true)
                        .filter(javaFileObject -> javaFileObject.getKind().equals(kind))
                        .map(FileObject::getName)
                        .map(fileObjectName -> fileObjectName.replace(".class", ""))
                        .map(mapToClassName)
                        .map(className -> Try.of(() -> Class.forName(className))
                                             .getOrElseThrow(asRuntimeException))
                        .collect(Collectors.toList());
  };

  @Test
  @DisplayName("should get classes recursively in given package")
  void test() {
    Collection<Class<?>> classes = findAllPackageClasses.apply(getClass().getPackage().getName());
    assertThat(classes).hasSizeGreaterThan(4);
    classes.stream().map(String::valueOf).forEach(log::info);
  }
}

PS: เพื่อลดความซับซ้อนของแม่แบบสำหรับการจัดการข้อผิดพลาด ฯลฯ ฉันใช้ที่นี่vavrและlombokห้องสมุด

การใช้งานอื่น ๆ สามารถพบได้ในGitHub ของฉัน daggerok / java-reflection-find-annotated-class-or-methods repo


0

ฉันไม่สามารถหางานสั้น ๆ มาทำอะไรง่ายๆได้ ดังนั้นที่นี่คือฉันทำมันเองหลังจากที่สกรูไปรอบ ๆ ในขณะที่:

    Reflections reflections =
        new Reflections(new ConfigurationBuilder()
                .filterInputsBy(new FilterBuilder().includePackage(packagePath))
                .setUrls(ClasspathHelper.forPackage(packagePath))
                .setScanners(new SubTypesScanner(false)));

    Set<String> typeList = reflections.getAllTypes(); 

0

ถ้าคุณอยู่ในฤดูใบไม้ผลิที่ดินคุณสามารถใช้PathMatchingResourcePatternResolver;

  PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");

    Arrays.asList(resources).forEach(r->{
        ...
    });

-4

เป็นไปไม่ได้เนื่องจากคลาสทั้งหมดในแพ็คเกจอาจไม่สามารถโหลดได้ในขณะที่คุณรู้จักแพ็คเกจของคลาสเสมอ

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