programing

리플렉션을 사용하여 패키지의 모든 클래스를 찾을 수 있습니까?

kingscode 2022. 7. 10. 19:05
반응형

리플렉션을 사용하여 패키지의 모든 클래스를 찾을 수 있습니까?

특정 패키지에 포함된 모든 클래스 또는 인터페이스를 검색할 수 있습니까?(예를 들어, 빠르게 보면 아니라고 생각됩니다.)

클래스 로더의 동적 특성으로 인해 이 작업은 불가능합니다.클래스 로더는 VM에 제공할 수 있는 클래스를 알릴 필요가 없으며, 대신 클래스에 대한 요청만 전달되므로 클래스를 반환하거나 예외를 발생시켜야 합니다.

그러나 사용자가 직접 클래스 로더를 작성하거나 클래스 경로와 해당 jar를 검사하면 이 정보를 찾을 수 있습니다.이것은 파일 시스템 조작에 의한 것이며, 리플렉션에 의한 것은 아닙니다.이를 지원하는 라이브러리가 있을 수도 있습니다.

원격으로 생성되거나 전달되는 클래스가 있는 경우 해당 클래스를 검색할 수 없습니다.

일반적인 방법은 액세스해야 하는 클래스를 파일에 등록하거나 다른 클래스에서 참조하는 것입니다.아니면 그냥 명명법에 관한 규칙을 사용할 수도 있습니다.

부록:Reflections Library를 사용하면 현재 클래스 경로에서 클래스를 검색할 수 있습니다.패키지의 모든 클래스를 가져오는 데 사용할 수 있습니다.

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

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

오픈 소스 리플렉션라이브러리를 참조해 주세요.이것으로 원하는 것을 쉽게 달성할 수 있습니다.

먼저 반사 인덱스를 설정합니다(기본적으로 모든 클래스 검색이 비활성화되어 있으므로 약간 복잡합니다).

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

Google Guava 14에는 최상위 클래스를 검색하기 위한 세 가지 방법이 포함된 새로운 클래스가 포함되어 있습니다.

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

자세한 내용은 javadocs를 참조하십시오.

방법1 사용하여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 이 메서드는 원래 인터넷 아카이브에 의해 아카이브된 http://snippets.dzone.com/posts/show/4831,에서 가져온 것입니다.이러한 방법은 현재 링크되어 있습니다.이 스니펫은 https://dzone.com/articles/get-all-classes-within-package에서도 구할 수 있습니다.

이 예는 Spring 4 용이지만 이전 버전에서도 클래스 경로 스캐너를 찾을 수 있습니다.

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

구글 구아바

주의: 버전 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
  }
}

안녕하세요. 위의 솔루션과 다른 사이트에서 항상 문제가 있었습니다.
저는 개발자로서 API용 애드온을 프로그래밍하고 있습니다.API는 외부 라이브러리 또는 서드파티 도구를 사용하지 못하도록 합니다.설정은 jar 또는 zip 파일의 코드와 일부 디렉터리에 직접 있는 클래스 파일의 혼합으로도 구성됩니다.그래서 내 코드는 모든 셋업에서 작동할 수 있어야 했다.많은 연구 끝에 가능한 모든 설정의 95% 이상에서 작동하는 방법을 생각해냈습니다.

다음 코드는 기본적으로 항상 작동하는 과잉 살상 방법입니다.

코드:

이 코드는 지정된 패키지에 포함된 모든 클래스에 대해 검색합니다.은 현재의 in in in in it it it 、 재 it 、 。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;
}

이러한 3가지 메서드를 사용하면 특정 패키지의 모든 클래스를 검색할 수 있습니다.
뭇매를 맞다

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

설명:

은 먼저 the the the the 、 the the 、 the the the the the the the the 。ClassLoader 해당 하는 모든 이러한 패키지를 URL 그 후 s.가 생성됩니다.URLConnection이이이 URL 。디렉토리)를 사용할 수 .FileURLConnection파일 내의 jar'zip')JarURLConnection의 접속 , 의 접속 타입에 따라서는, 2개의 다른 메서드가 호출됩니다.

, 이 '어느 정도', '어느 '일 경우 어떻게 되는지 알아보겠습니다.FileURLConnection.
먼저 전달된 파일이 존재하며 디렉토리인지 확인합니다.이 경우 클래스 파일인지 여부를 확인합니다.Class되어 이 에 들어갑니다.ArrayList클래스 파일이 아닌 디렉토리인 경우, 그 파일에 반복해 같은 작업을 실시합니다.하다

경우,URLConnection는 입니다.JarURLConnection개인 조력자이 메서드는 zip/jar 아카이브의 모든 엔트리에 대해 반복합니다.이며, 경우.Class된다.ArrayList.

주 메서드됩니다.ArrayList된 패키지되어 있습니다.이 클래스는 "Current "Manager"로 되어 있습니다.ClassLoader고고있있있있다다

가 어느 ,ClassNotFoundException정확한 원인에 대한 자세한 정보가 담긴 정보가 던져질 것입니다.

특정 패키지의 모든 클래스를 나열하는 가장 강력한 메커니즘은 ClassGraph입니다.이는 새로운 JPMS 모듈 시스템을 포함하여 가능한 한 광범위한 클래스 경로 지정 메커니즘을 처리하기 때문입니다.(저는 저자입니다.)

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

추가 라이브러리 사용 안 함:

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

일반적으로 클래스 로더는 클래스 경로의 모든 클래스를 검색할 수 없습니다.그러나 일반적으로 사용되는 클래스 로더는 UrlClassLoader뿐입니다.이 로더에서 디렉토리 및 jar 파일 목록을 가져와(getURL 참조) 하나씩 열어 사용 가능한 클래스를 나열할 수 있습니다.클래스 패스스캔이라고 불리는 이 어프로치는, 스캔 노테이션과 리플렉션에 실장되어 있습니다.

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 컴파일러가 클래스 패스에 있는 프로세서를 자동으로 검출하기 때문에 스캔이 완전히 자동화되므로 추가 설정이 필요하지 않습니다.

이건 어때?

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 도우미가 없는 경우:

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 파일의 경우

  • 외부 의존 없이

저는 이 문제를 해결하기 위한 간단한 기트허브 프로젝트를 준비했습니다.

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

파일 기반 클래스 경로와 jar 파일 모두에서 작동합니다.

프로젝트를 체크 아웃 한 후 'make'를 실행하면 다음과 같이 출력됩니다.

 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. 

다른 답변도 참조해 주세요.

내가 하는 방법은 이렇다.모든 하위 폴더(하위 패키지)를 검색하고 익명 클래스를 로드하지 않습니다.

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

네, 몇 개의 API를 사용하여 할 수 있는 방법은 다음과 같습니다.여기서는 제가 hibernate core를 사용하고 있는 이 문제에 직면하여 특정 주석이 있는 클래스를 찾아야 했습니다.

선택하려는 클래스를 표시하는 데 사용할 사용자 지정 주석으로 만드십시오.

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

}

all Found Classes 호출주석 첨부WithEntityToBeScanned() 메서드를 사용하여 클래스 세트를 가져옵니다.

아래에 리브가 필요합니다.

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

스프링랜드에 계신 분들은PathMatchingResourcePatternResolver;

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

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

클래스 경로의 모든 클래스 로더 엔트리를 조회해야 합니다.

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

이제 패키지와 함께 모든 클래스 이름을 가지고 있으면 리플렉션과 함께 로드하여 클래스인지 인터페이스인지 분석할 수 있습니다.

Reflections 라이브러리를 사용하려고 했지만 사용상의 문제가 있어 패키지에 있는 클래스를 얻기 위해 포함해야 할 jar가 너무 많았습니다.

이 중복된 질문에서 찾은 솔루션을 게시합니다.패키지로 모든 클래스 이름을 가져오려면 어떻게 해야 합니까?

답변은 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으로 find 메서드를 호출합니다.필요에 따라 클래스의 인스턴스 생성을 추가했습니다.

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

방금 util class를 작성했는데, 테스트 방법이 포함되어 있어 확인하실 수 있습니다.

Iterate Package Util.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();
    }
}

Alexander Blomsköld의 솔루션은 매개 변수화된 테스트에서 나에게 작동하지 않았다.@RunWith(Parameterized.class)메이븐 씨테스트 이름이 올바르게 지정되었으며, 검색되었지만 실행되지 않은 위치도 지정되었습니다.

-------------------------------------------------------
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에서 로컬로 실행하면 테스트는 정상적으로 동작.알렉산더 블롬스쾰드

Alexander Blomsköld의 답변에 대한 David Pérsson의 코멘트에서 영감을 얻은 다음 칩을 사용하여 작업을 수행했습니다.

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

나는 그렇게 간단한 것에 대해 짧은 작업을 할 수 있는 사람을 찾을 수 없었다.여기 있습니다. 잠시 동안 빈둥빈둥 놀다가 제가 직접 만들었어요.

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

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

이 둘 중 를 사용합니다.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);
}

동적 클래스 로더를 사용하지 않는 경우 클래스 경로를 검색하고 각 엔트리에 대해 디렉토리 또는 JAR 파일을 검색할 수 있습니다.

언급할 가치가 있다

는, 「」를 사용할 수 .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));
}

이렇게 하면 나중에 원하는 대로 사용할 수 있는 클래스 목록이 생성됩니다.

하지만, 이 따로 , 도서관이 없어도 된다.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;
}
}

@Stale의 답변을 바탕으로 서드파티 라이브러리에 의존하지 않기 위해 다음과 같은 첫 번째 패키지의 물리적 위치를 검사함으로써 파일 시스템 접근 방식을 구현합니다.

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

플레인 자바: Find All Classes Using Plain Java Reflection Test.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);
  }
}

추신: 오류 처리 등을 위한 보일러 플레이트를 간소화하기 위해 여기서 사용하고 있습니다.vavr ★★★★★★★★★★★★★★★★★」lombok

다른 구현은 내 GitHub dagulok/java-reflection-find-notated-classes-or-methods repo에서 찾을 수 있습니다.

의 경우org.reflections0.10:

org.reflections.scanners.SubTypesScanner 

그리고.

org.reflections.Reflections.getAllTypes() 

는 권장되지 않습니다.사용자:

public Set<String> getEntityNamesInPackage(String packagePath) {
    Reflections reflections = new Reflections(new ConfigurationBuilder()
                    .filterInputsBy(new FilterBuilder().includePackage(packagePath))
                    .setUrls(ClasspathHelper.forPackage(packagePath))
                    .setScanners(SubTypes.filterResultsBy(s -> true)));

    return reflections.getAll(SubTypes).stream()
            .filter(s -> s.startsWith(packagePath))
            .collect(Collectors.toSet());
}

단지 관련 클래스의 그룹을 로드하는 것을 검토하고 있는 경우, Spring이 도움이 됩니다.

스프링은 코드 한 줄에 특정 인터페이스를 구현하는 모든 클래스의 목록 또는 맵을 인스턴스화할 수 있습니다.목록 또는 맵에는 해당 인터페이스를 구현하는 모든 클래스의 인스턴스가 포함됩니다.

즉, 파일 시스템에서 클래스 목록을 로드하는 대신 패키지에 관계없이 로드하려는 모든 클래스에 동일한 인터페이스를 구현하고 Spring을 사용하여 모든 클래스의 인스턴스를 제공합니다.이렇게 하면 패키지의 종류에 관계없이 원하는 모든 클래스를 로드(인스턴스화)할 수 있습니다.

한편, 모든 것을 패키지에 포함할 필요가 있는 경우는, 그 패키지의 모든 클래스에서 특정의 인터페이스를 실장하는 것만으로 끝납니다.

인터페이스 자체는 메서드를 선언할 필요가 없습니다.완전히 비어 있을 수 있습니다.

특정 인터페이스를 구현하는 클래스 목록을 삽입하려면 다음 코드 행을 사용합니다.

  @Autowired
  private List<ISomeInterface> implementationList;

스프링을 사용하여 클래스 맵을 삽입할 수도 있습니다.자세한 내용은 문서를 참조하십시오.

마지막으로 파일 시스템 트리 전체를 검색하는 것보다 조금 더 우아한 솔루션을 하나 더 제안합니다.

@ClassCatalog와 같이 적용되는 클래스의 카탈로그를 작성하는 사용자 지정 주석을 만듭니다.

패키지의 모든 클래스가 로드되지 않을 수 있기 때문에 항상 클래스의 패키지를 알고 있기 때문에 가능하지 않습니다.

언급URL : https://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package-using-reflection

반응형