Bạn có thể tìm thấy tất cả các lớp trong một gói bằng cách sử dụng sự phản chiếu?


528

Có thể tìm thấy tất cả các lớp hoặc giao diện trong một gói nhất định? (Nhanh chóng nhìn vào Package, ví dụ , có vẻ như không.)


2
FYI giải pháp Amit liên kết đến hoạt động, mặc dù nó có lỗi nếu đường dẫn lớp có ký tự khoảng trắng trong đó (và có lẽ đối với các ký tự không chữ và số khác nữa). nếu bạn đang sử dụng nó trong bất kỳ loại mã sản xuất nào, hãy xem nhận xét của tôi về câu trả lời của anh ấy để biết cách giải quyết.
Kip

2
Cũng lưu ý bài này .
barfuin

1
Xem câu trả lời liên quan: stackoverflow.com/a/30149061/4102160
Cfx

1
Cũng lưu ý bài này .
sp00m

1
Xem câu trả lời của tôi dưới đây về ClassGraph, đây hiện là phương pháp mạnh mẽ nhất để quét đường dẫn lớp và đường dẫn mô-đun.
Luke Hutchison

Câu trả lời:


374

Do tính chất năng động của trình nạp lớp, điều này là không thể. Trình nạp lớp không bắt buộc phải báo cho VM biết lớp nào nó có thể cung cấp, thay vào đó chúng chỉ là các yêu cầu được trao cho các lớp và phải trả về một lớp hoặc ném ngoại lệ.

Tuy nhiên, nếu bạn viết trình nạp lớp của riêng mình hoặc kiểm tra các đường dẫn lớp và nó là bình, bạn có thể tìm thấy thông tin này. Điều này sẽ thông qua các hoạt động hệ thống tập tin, và không phản ánh. Thậm chí có thể có các thư viện có thể giúp bạn làm điều này.

Nếu có các lớp được tạo hoặc phân phối từ xa, bạn sẽ không thể khám phá các lớp đó.

Thay vào đó, phương thức bình thường là ở đâu đó đăng ký các lớp bạn cần truy cập trong một tệp hoặc tham chiếu chúng trong một lớp khác. Hoặc chỉ sử dụng quy ước khi đặt tên.

Phụ lục: Thư viện Reflection sẽ cho phép bạn tra cứu các lớp trong đường dẫn hiện tại. Nó có thể được sử dụng để có được tất cả các lớp trong một gói:

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

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

12
Việc không thể truy vấn tên lớp đã khiến tôi chán nản trong một thời gian dài. Chắc chắn, nó khó và hiệu suất có thể thay đổi lớn, và đối với một số trình nạp lớp nhất định, danh sách này không được xác định hoặc không bị ràng buộc, nhưng có nhiều cách có thể được thực hiện xung quanh.
Ông Shiny và New

16
Lưu ý rằng giải pháp này sẽ không hoạt động như mặc định getSubTypesOf không trả về các kiểu con của Object. Xem giải pháp của Aleksander Blomskøld để biết cách định cấu hình SubTypeScanner.
Alex Spurling

17
Phản xạ đòi hỏi ổi. Quả ổi to. Phiên bản 14.0.1 là 2.1MB.
mike jones

3
Không làm việc cho tôi. Mac OSX - Phiên bản phụ thuộc phản xạ 0.9.9-RC1 (maven) - JDK 1.7. Xem xét lại câu trả lời được chấp nhận. @ AleksanderBlomskøld câu trả lời là một trong những để đi. !!!!!
Viêm ruột Konstantinos

68
Nếu điều này trả về một danh sách trống, hãy khởi tạo đối tượng Reflection như thế này: Reflection Reflections = new Reflections ("your.package.here", SubTypesScanner mới (false));
João Rocha da Silva

186

Có lẽ bạn nên xem thư viện Reflection mã nguồn mở . Với nó, bạn có thể dễ dàng đạt được những gì bạn muốn.

Đầu tiên, thiết lập chỉ mục phản xạ (hơi lộn xộn vì việc tìm kiếm tất cả các lớp bị tắt theo mặc định):

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

Sau đó, bạn có thể truy vấn tất cả các đối tượng trong một gói nhất định:

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

6
À, ở đây chúng tôi đi: code.google.com/p/reflections/issues/detail?id=122 . Đối tượng được loại trừ theo mặc định, nhưng bạn có thể kích hoạt lại nó. Cảm ơn đã chỉ cho tôi đến thư viện này, thật tuyệt!
mtrc

1
Tôi đã gặp vấn đề trên máy Mac của mình với mã này (liên quan đến thư viện gốc), nhưng sử dụng .addUrls(ClasspathHelper.forJavaClassPath())thay vì giải quyết chúng ở trên đã giải quyết chúng cho tôi. Ít mã là tốt!
David Pärsson

3
nếu bất cứ ai tự hỏi một cách đơn giản nhất để có được gói mặc định là có tiền tố là một chuỗi rỗng -> "".
JBA

2
Thư viện "Reflections" có một giấy phép phức tạp: github.com/ronmamo/reflections/blob/master/COPYING.txt . Thủ thuật là giấy phép cho phép sử dụng miễn phí chỉ bản thân giấy phép. Vì vậy, để thực sự sử dụng thư viện (không phải giấy phép), mọi người phải liên hệ với tác giả và đàm phán các điều khoản sử dụng.
Serge Rogatch 17/2/2015

3
Serge, tôi nghĩ bạn hiểu nhầm WTFPL: wtfpl.net Tôi nghĩ WTFPL có nghĩa là bạn có thể tự do làm bất cứ điều gì bạn muốn, không chỉ với giấy phép mà còn với mã số
Richo

122

Google Guava 14 bao gồm một lớp mới ClassPathvới ba phương pháp để quét các lớp cấp cao nhất:

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

Xem ClassPathjavadocs để biết thêm.


điều này hiệu quả với tôi khi Reflection không thể làm (không có tổ tiên trực tiếp chung, không có chú thích chung)
Riccardo Cossu

1
Như tôi đã đề cập trong một bình luận bên dưới , ClassPathđược gắn thẻ @Beta, vì vậy có thể không phải là ý tưởng hay cho một số ...
Christian

1
Nói rằng điều này hoạt động ở nơi mà sự phản chiếu không làm được một chút kỳ lạ, chắc chắn giải pháp được thực hiện bằng cách sử dụng chức năng phản chiếu (và trình nạp lớp).
Maarten Bodewes

5
Tôi nghĩ rằng ông có nghĩa là thư viện Reflections được đề cập trong câu trả lời khác.
Christoph Leiter

Hoạt động theo Java 11, nếu sử dụng phiên bản ổi 28.1-jre.
gorjanz

112

Bạn có thể sử dụng phương pháp 1 này sử dụng ClassLoader.

/**
 * 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 Phương pháp này đã được thực hiện ban đầu từ http://snippets.dzone.com/posts/show/4831 , được lưu trữ bởi Internet Archive, như liên quan đến bây giờ. Đoạn mã cũng có sẵn tại https://dzone.com/articles/get-all-groupes-within-package .


6
Tôi đã có một vấn đề với điều này nếu đường dẫn của tôi bao gồm không gian. Lớp URL đã thoát các khoảng trắng đến %20, nhưng hàm new File()tạo đã coi đó là phần trăm theo nghĩa đen ký hai số không. Tôi đã sửa nó bằng cách thay đổi dirs.add(...)dòng này: dirs.add(new File(resource.toURI())); Điều này cũng có nghĩa là tôi phải thêm URISyntaxExceptionvào mệnh đề ném củagetClasses
Kip

19
Bạn vừa sao chép từ dzone.com/articles/get-all-groupes-within-package ! vui lòng tham khảo nguồn lần sau
RS

19
+1 vì giải pháp này KHÔNG yêu cầu các thư viện bên ngoài ... KHÔNG BAO GIỜ, thực sự KHÔNG BAO GIỜ ghép mã của bạn một cách ngẫu nhiên với các thư viện chỉ để đạt được một điều nhỏ như thế này. Bạn có biết rằng bạn đang thêm bề mặt tấn công tiềm năng cho những kẻ tấn công? Tháng 11 năm 2015 một vấn đề Apache Commons phát hiện ra rằng dẫn đến từ xa Thực hiện lệnh chỉ bằng cách có Apache Commons trong classpath của ứng dụng triển khai trên Jboss / WebLogic [ foxglovesecurity.com/2015/11/06/...
sc0p

@Qix lưu ý chính xác rằng mã này không hỗ trợ jar. Để hỗ trợ lọ và thư mục . Mã đã được thay đổi như được lưu ý dưới đây:
user1523177 8/2/2016

1
Giải pháp tốt, nhưng có vẻ tốt hơn nếu 'Class.forName (String className)' sẽ được thay thế bằng 'Class.forName (String className, boolean khởi tạo, trình tải ClassLoader)' trong đó 'initize = false;' để không tạo ra các thể hiện của lớp.
Andrei_N

99

Mùa xuân

Ví dụ này dành cho Spring 4, nhưng bạn cũng có thể tìm thấy trình quét classpath trong các phiên bản trước.

// 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 ...
}

Quả ổi

Lưu ý: Trong phiên bản 14, API vẫn được đánh dấu là @Beta , vì vậy hãy cẩn thận trong mã sản xuất.

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
Câu trả lời tuyệt vời. Có quá nhiều giải pháp ở đây là dài dòng, không thử nghiệm, không hoạt động! Điều này thật tuyệt vời: nó súc tích và được thử nghiệm (nó là từ Guava). Rất tốt! Nó là hữu ích, nó xứng đáng upvote nhiều hơn.
JeanValjean

Thật không may, ClassPathlớp trong Guava cũng được đánh dấu bằng @Beta: "API được đánh dấu bằng chú thích @Beta ở cấp độ lớp hoặc phương thức có thể thay đổi. Chúng có thể được sửa đổi theo bất kỳ cách nào, hoặc thậm chí bị xóa, trong bất kỳ bản phát hành chính nào. Nếu mã của bạn là một thư viện (nghĩa là nó được sử dụng trên CLASSPATH của người dùng ngoài tầm kiểm soát của bạn), bạn không nên sử dụng API beta, trừ khi bạn đóng gói lại chúng ... " code.google.com/p/guava-lologists/#Important_Warnings
Christian

@Christian Điểm tốt, tôi chưa nhận thấy! Cảm ơn bạn. Tôi sẽ thêm một câu trả lời khác bằng trình quét class class Spring, đây không phải là bản beta.
voho

Để tìm các lớp tĩnh lồng nhau bằng cách sử dụng dung dịch ổi, getAllClasses()phương pháp có thể được sử dụng.
v.ladynev

1
Giải pháp Spring là hoạt động duy nhất nếu chạy từ một jar thực thi.
Lu-ca

38

Xin chào. Tôi luôn có một số vấn đề với các giải pháp trên (và trên các trang web khác).
Tôi, với tư cách là một nhà phát triển, đang lập trình một addon cho API. API ngăn chặn việc sử dụng bất kỳ thư viện bên ngoài hoặc công cụ của bên thứ 3. Thiết lập cũng bao gồm một hỗn hợp mã trong các tệp jar hoặc zip và các tệp lớp nằm trực tiếp trong một số thư mục. Vì vậy, mã của tôi phải có khả năng làm việc trên mọi thiết lập. Sau rất nhiều nghiên cứu, tôi đã tìm ra một phương pháp sẽ hoạt động trong ít nhất 95% tất cả các thiết lập có thể.

Các mã sau đây về cơ bản là phương pháp quá mức sẽ luôn hoạt động.

Mật mã:

Mã này quét một gói nhất định cho tất cả các lớp được bao gồm trong nó. Nó sẽ chỉ hoạt động cho tất cả các lớp trong hiện tại 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;
}

Ba phương thức này cung cấp cho bạn khả năng tìm thấy tất cả các lớp trong một gói nhất định.
Bạn sử dụng nó như thế này:

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

Giải thích:

Phương pháp đầu tiên có được hiện tại ClassLoader. Sau đó, nó tìm nạp tất cả các tài nguyên có chứa gói nói và lặp lại của các URLs này . Sau đó, nó tạo ra một URLConnectionvà xác định loại URl chúng ta có. Nó có thể là một thư mục ( FileURLConnection) hoặc một thư mục bên trong tệp jar hoặc tệp zip ( JarURLConnection). Tùy thuộc vào loại kết nối, chúng tôi có hai phương thức khác nhau sẽ được gọi.

Đầu tiên hãy xem điều gì xảy ra nếu nó là a FileURLConnection.
Đầu tiên, nó kiểm tra nếu File đã qua tồn tại và là một thư mục. Nếu đó là trường hợp nó kiểm tra nếu nó là một tập tin lớp. Nếu vậy một Classđối tượng sẽ được tạo và đưa vàoArrayList . Nếu nó không phải là một tập tin lớp mà là một thư mục, chúng ta chỉ cần lặp lại vào nó và làm điều tương tự. Tất cả các trường hợp / tập tin khác sẽ bị bỏ qua.

Nếu URLConnectionlà một JarURLConnectionphương thức trợ giúp riêng tư khác sẽ được gọi. Phương pháp này lặp lại trên tất cả các mục trong kho lưu trữ zip / jar. Nếu một mục nhập là một tệp lớp và nằm trong gói, một Classđối tượng sẽ được tạo và lưu trữ trong ArrayList.

Sau khi tất cả các tài nguyên đã được phân tích cú pháp, nó (phương thức chính) trả về hàm ArrayListchứa tất cả các lớp trong gói đã cho, đó là hiện tạiClassLoader biết về.

Nếu quá trình không thành công tại bất kỳ thời điểm nào, a ClassNotFoundExceptionsẽ bị ném thông tin chi tiết về nguyên nhân chính xác.


4
Ví dụ này dường như yêu cầu nhập sun.net.www.protocol.file.FileURLConnection, tạo cảnh báo vào thời gian biên dịch ("cảnh báo: sun.net.www.protatio.file.FileURLCconnectection là API độc quyền của Sun và có thể bị xóa trong bản phát hành trong tương lai"). Có cách nào khác để sử dụng lớp đó không, hoặc cảnh báo có thể được loại bỏ bằng cách sử dụng các chú thích không?
Christian

Phương thức này không hoạt động đối với các lớp bootstrap, như các lớp trong java.lang, java.util, ... Chúng có thể được tìm thấy bằng cách lấy System.getProperty ("sun.boot. Class.path"), chia tách với: hoặc; (tùy thuộc vào HĐH), và sau đó chạy các phiên bản sửa đổi một chút của checkDirectory và checkJarFile ở trên.
coderforlife

1
Bạn có thể khắc phục cảnh báo / lỗi bằng cách sử dụng Connection.getClass (). GetCanonicalName (). Bằng ("sun.net.www.protatio.file.FileURLCconnectection"). Nếu bạn thực sự muốn, bạn có thể tạo URLConnection mà bạn nghĩ NÊN sử dụng sun.net.www.protatio.file.FileURLCconnectection và cũng so sánh tên của lớp kết nối với tên của lớp bạn đã tạo. Nếu cả hai đều giống nhau, bạn có thể coi nó như một ví dụ của sun.net.www.protatio.file.FileURLCconnectection thay vì thất bại trong trường hợp tên lớp thay đổi.
William Deans

1
@Christian Bạn có thể tránh FileURLCconnectection làm điều gì đó như thế này: if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }Đó là những gì tôi đã làm trên mã của mình để tìm kiếm các lớp chú thích JPA
Zardoz89

15

Không sử dụng bất kỳ thư viện bổ sung:

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;
    }
}

Khi tôi chạy cái này trong JAR, upackagenull... :(
Christian

Đối với gói "com.mycompany.beans", thay thế "thử nghiệm" bằng "com / mycompany / bean"
James Jithin 17/03/2016

4
Tôi nhận được một null khi sử dụng mã này. Có vẻ như chỉ hoạt động nếu bình của bạn là một thực thi
Alao

nếu bạn có được tên gói từ String pack = getPackage().getName();đó, thì bạn phải thêmpack = pack.replaceAll("[.]", "/");
user2682877

13

Trong các trình nạp lớp chung không cho phép quét qua tất cả các lớp trên đường dẫn lớp. Nhưng thường thì trình tải lớp duy nhất được sử dụng là UrlClassLoader từ đó chúng ta có thể truy xuất danh sách các thư mục và tệp jar (xem getURL ) và mở từng cái một để liệt kê các lớp có sẵn. Cách tiếp cận này, được gọi là quét đường dẫn lớp, được thực hiện trong ScannotationReflection .

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

Một cách tiếp cận khác là sử dụng API xử lý chú thích có thể cắm của Java để viết bộ xử lý chú thích sẽ thu thập tất cả các lớp chú thích vào thời gian biên dịch và xây dựng tệp chỉ mục để sử dụng thời gian chạy. Cơ chế này được triển khai trong thư viện Class Index :

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

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

Lưu ý rằng không cần thiết lập bổ sung vì quá trình quét hoàn toàn tự động nhờ trình biên dịch Java tự động phát hiện bất kỳ bộ xử lý nào được tìm thấy trên đường dẫn lớp.


Điều này có phát hiện ra các lớp được đóng gói trong Jar không? Nó dường như không làm việc cho tôi.
vào

bạn đang cố gắng sử dụng công cụ nào?
Sławek

Tôi đang sử dụng lib Reflection. Nhưng tôi đã làm cho nó hoạt động sau khi làm theo cách giải quyết được đề cập bởi @Aleksander Blomskøld cho các phiên bản gần đây của lib này.
vào

Xin chào, tôi đang sử dụng nhật thực và không thể làm cho nó hoạt động, ClassIndex.getPackageC Cầu ("my.package") trả lại một bản đồ trống
Juan

11

Cơ chế mạnh mẽ nhất để liệt kê tất cả các lớp trong một gói nhất định hiện tại là ClassGraph , vì nó xử lý mảng cơ chế đặc tả đường dẫn rộng nhất có thể , bao gồm cả hệ thống mô-đun JPMS mới. (Tôi là tác giả.)

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

5

Đây là cách tôi làm điều đó. Tôi quét tất cả các thư mục con (gói phụ) và tôi không thử tải các lớp ẩn danh:

   /**
   * 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

Tôi kết hợp một dự án github đơn giản để giải quyết vấn đề này:

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

Nó nên hoạt động đối với các đường dẫn lớp dựa trên tệp VÀ cho các tệp jar.

Nếu bạn chạy 'make' sau khi kiểm tra dự án, nó sẽ in ra:

 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. 

Xem thêm câu trả lời khác của tôi


4

Vâng, sử dụng một vài API bạn có thể, đây là cách tôi thích thực hiện, đã đối mặt với vấn đề này mà tôi đang sử dụng lõi ngủ đông & phải tìm các lớp có chú thích với một chú thích nhất định.

Đặt các chú thích tùy chỉnh này bằng cách sử dụng mà bạn sẽ đánh dấu các lớp bạn muốn được chọn.

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 {

}

Sau đó đánh dấu lớp của bạn với nó như thế nào

@EntityToBeScanned 
public MyClass{

}

Tạo lớp tiện ích này có phương thức sau

public class ClassScanner {

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

}

Gọi phương thức allFoundClassAnnotatedWithEntityToBeScned () để tìm Tập hợp các lớp được tìm thấy.

Bạn sẽ cần libs được đưa ra dưới đây

<!-- 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

Bạn cần tra cứu mọi mục nhập trình nạp lớp trong đường dẫn lớp:

    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());
        // ....
    }   

Nếu mục nhập là thư mục, chỉ cần tra cứu trong thư mục con bên phải:

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

Nếu mục nhập là tệp và đó là tệp jar, hãy kiểm tra các mục ZIP của tệp đó:

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

Bây giờ khi bạn có tất cả các tên lớp đóng gói, bạn có thể thử tải chúng với sự phản chiếu và phân tích nếu chúng là các lớp hoặc giao diện, v.v.


Bạn sẽ nhập gì cho một gói trong tệp Jar?
Kyle Bridenstine

Ví dụ này sẽ không đi qua các gói phụ. Có lẽ đó là điều đáng quan tâm đối với một số ... @ mr-tea Chỉ cần chỉ định gói bạn đang tìm kiếm. Tôi đặt cái này trong một dự án, chỉ định gói thử nghiệm trong dự án đó, biên dịch và đóng gói nó và gọi mẫu ví dụ là phương thức chính của JAR. Làm việc như người ở. :)
Christian

3

Tôi đã cố gắng sử dụng thư viện Reflection, nhưng đã gặp một số vấn đề khi sử dụng nó và có quá nhiều lọ tôi nên đưa vào chỉ để lấy các lớp trên một gói.

Tôi sẽ đăng một giải pháp tôi đã tìm thấy trong câu hỏi trùng lặp này: Làm thế nào để có được tất cả các tên lớp trong một gói?

Câu trả lời được viết bởi sp00m ; Tôi đã thêm một số chỉnh sửa để làm cho nó hoạt động:

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;
    }

}

Để sử dụng nó, chỉ cần gọi phương thức find là sp00n được đề cập trong ví dụ này: Tôi đã thêm việc tạo các thể hiện của các lớp nếu cần.

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

Tôi vừa viết một lớp tiện dụng, nó bao gồm các phương thức kiểm tra, bạn có thể kiểm tra ~

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

Giải pháp của Aleksander Blomskøld không hiệu quả đối với tôi đối với các thử nghiệm được tham số hóa @RunWith(Parameterized.class)khi sử dụng Maven. Các thử nghiệm được đặt tên chính xác và cũng được tìm thấy nhưng không được thực hiện:

-------------------------------------------------------
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

Một vấn đề tương tự đã được báo cáo ở đây .

Trong trường hợp của tôi @Parameterslà tạo các thể hiện của mỗi lớp trong một gói. Các thử nghiệm hoạt động tốt khi chạy cục bộ trong IDE. Tuy nhiên, khi chạy Maven, không có lớp nào được tìm thấy với giải pháp của Aleksander Blomskøld.

Tôi đã làm cho nó hoạt động với đoạn trích sau được lấy cảm hứng từ nhận xét của David Pärsson về câu trả lời của 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

Cái này thì sao:

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;
}

Sau đó, bạn có thể quá tải chức năng:

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

Nếu bạn cần kiểm tra nó:

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

Nếu IDE của bạn không có trình trợ giúp nhập:

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;

Nó hoạt động:

  • từ IDE của bạn

  • cho một tệp JAR

  • không phụ thuộc bên ngoài


2

Hầu như tất cả các câu trả lời đều sử dụng Reflectionshoặc đọc các tệp lớp từ hệ thống tệp. Nếu bạn cố đọc các lớp từ hệ thống tệp, bạn có thể gặp lỗi khi đóng gói ứng dụng của mình dưới dạng JAR hoặc khác. Ngoài ra, bạn có thể không muốn sử dụng một thư viện riêng cho mục đích đó.

Đây là một cách tiếp cận khác là java thuần túy và không phụ thuộc vào hệ thống tệp.

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 không bắt buộc . Bạn có thể sử dụng cho các vòng lặp thay vì các luồng. Và bạn có thể kiểm tra nó như thế này

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
Nó không hữu ích lắm vì: cần phải có JDK để sử dụng ToolProvider.getSystemJavaCompiler(), mã này không quét các gói lồng nhau.
v.ladynev

Tôi không thể làm cho nó làm việc với một gói của một jar bên ngoài
Enrico Giurin

1

Với điều kiện bạn không sử dụng bất kỳ trình nạp lớp động nào, bạn có thể tìm kiếm đường dẫn lớp và cho mỗi mục nhập tìm kiếm thư mục hoặc tệp JAR.


1

Đáng nói

Nếu bạn muốn có một danh sách tất cả các lớp theo gói nào đó, bạn có thể sử dụng Reflectioncách sau:

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

Điều này sẽ tạo một danh sách các lớp mà sau này bạn có thể sử dụng chúng theo ý muốn.


1

Điều đó là rất có thể, nhưng không có các thư viện bổ sung như Reflectionsnó rất khó ...
Thật khó vì bạn không có đầy đủ công cụ để lấy tên lớp.
Và, tôi lấy mã của ClassFinderlớp mình:

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

Dựa trên câu trả lời của @ Staale và trong nỗ lực không phụ thuộc vào thư viện của bên thứ ba, tôi sẽ thực hiện phương pháp Hệ thống tệp bằng cách kiểm tra vị trí thực tế của gói đầu tiên với:

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

Nếu bạn chỉ đang tìm cách tải một nhóm các lớp liên quan, thì Spring có thể giúp bạn.

Spring có thể khởi tạo một danh sách hoặc bản đồ của tất cả các lớp thực hiện một giao diện đã cho trong một dòng mã. Danh sách hoặc bản đồ sẽ chứa các thể hiện của tất cả các lớp thực hiện giao diện đó.

Điều đó đang được nói, như là một cách thay thế để tải danh sách các lớp ra khỏi hệ thống tệp, thay vào đó chỉ thực hiện cùng một giao diện trong tất cả các lớp bạn muốn tải, bất kể gói và sử dụng Spring để cung cấp cho bạn các phiên bản của tất cả chúng. Bằng cách đó, bạn có thể tải (và khởi tạo) tất cả các lớp bạn muốn bất kể chúng nằm trong gói nào.

Mặt khác, nếu có tất cả chúng trong một gói là những gì bạn muốn, thì đơn giản là có tất cả các lớp trong gói đó thực hiện một giao diện nhất định.


0

java đơn giản: Find ALLClassUsingPlainJavaReflectionTest.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: để đơn giản hóa các mẫu nồi hơi để xử lý lỗi, v.v., tôi đang sử dụng ở đây vavrlombok các thư viện

các triển khai khác có thể được tìm thấy trong repo GitHub daggerok / java-Reflection-find-annotated-class-or-method của tôi


0

Tôi không thể tìm thấy một snipped làm việc ngắn cho một cái gì đó rất đơn giản. Vì vậy, đây là, tôi đã tự làm nó sau khi vặn vẹo một lúc:

    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

Nếu bạn ở vùng đất mùa xuân, bạn có thể sử dụng PathMatchingResourcePatternResolver;

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

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

-4

Không thể, vì tất cả các lớp trong gói có thể không được tải, trong khi bạn luôn biết gói của một lớp.

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.