주어진 패키지에서 모든 클래스 또는 인터페이스를 찾을 수 있습니까? (예를 들어 빠르게 살펴보면 Package
아니오처럼 보일 것입니다.)
주어진 패키지에서 모든 클래스 또는 인터페이스를 찾을 수 있습니까? (예를 들어 빠르게 살펴보면 Package
아니오처럼 보일 것입니다.)
답변:
클래스 로더의 동적 특성으로 인해 불가능합니다. 클래스 로더는 VM에 제공 할 수있는 클래스를 알려줄 필요가 없으며 대신 클래스에 대한 요청을 처리 한 것이므로 클래스를 반환하거나 예외를 발생시켜야합니다.
그러나 클래스 로더를 직접 작성하거나 클래스 경로와 항아리를 검사하면이 정보를 찾을 수 있습니다. 이것은 반영이 아닌 파일 시스템 작업을 통해 이루어집니다. 이를 수행하는 데 도움이되는 라이브러리가있을 수도 있습니다.
생성되거나 원격으로 제공되는 클래스가 있으면 해당 클래스를 발견 할 수 없습니다.
일반적인 방법은 액세스 할 클래스를 파일에 등록하거나 다른 클래스에서 참조하는 것입니다. 또는 명명과 관련하여 규칙을 사용하십시오.
부록 : 리플렉션 라이브러리 를 사용하면 현재 클래스 경로에서 클래스를 찾을 수 있습니다. 패키지의 모든 클래스를 가져 오는 데 사용할 수 있습니다.
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);
.addUrls(ClasspathHelper.forJavaClassPath())
위의 대신 사용 하면 문제가 해결되었습니다. 적은 코드도!
Google Guava 14에는 ClassPath
최상위 클래스를 스캔하는 세 가지 방법이 있는 새로운 클래스 가 포함되어 있습니다.
getTopLevelClasses()
getTopLevelClasses(String packageName)
getTopLevelClassesRecursive(String packageName)
자세한 정보 는 ClassPath
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 에서 사용할 수 있습니다 .
%20
했지만 new File()
생성자는이 문자를 리터럴 백분율 부호 2 0으로 처리했습니다. dirs.add(...)
이 줄을 다음과 같이 변경하여 수정했습니다 . dirs.add(new File(resource.toURI()));
이것은 또한URISyntaxException
getClasses
이 예제는 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
}
}
ClassPath
구아바 의 클래스에는 다음과 @Beta
같이 표시됩니다 . "클래스 또는 메소드 레벨에서 @Beta 주석으로 표시된 API는 변경 될 수 있습니다. 어떤 방식 으로든 수정하거나 모든 주요 릴리스에서 제거 할 수 있습니다. 라이브러리 자체입니다 (예 : 자신의 통제 범위를 벗어난 사용자의 CLASSPATH에 사용됨). 다시 패키지하지 않는 한 베타 API를 사용해서는 안됩니다 ... " code.google.com/p/guava-libraries/#Important_Warnings
getAllClasses()
메소드를 사용할 수 있습니다.
안녕하세요. 위의 솔루션과 다른 사이트에서 항상 문제가있었습니다.
개발자로서 API 용 애드온을 프로그래밍하고 있습니다. API는 외부 라이브러리 또는 타사 도구를 사용하지 못하게합니다. 설정은 jar 또는 zip 파일의 코드와 일부 디렉토리에 직접 위치한 클래스 파일로 구성됩니다. 그래서 내 코드는 모든 설정에서 arround를 사용할 수 있어야했습니다. 많은 연구 끝에 나는 가능한 모든 설정의 적어도 95 %에서 작동하는 방법을 생각해 냈습니다.
다음 코드는 기본적으로 항상 작동하는 과도한 방법입니다.
이 코드는 포함 된 모든 클래스에 대해 주어진 패키지를 스캔합니다. 현재의 모든 클래스에서만 작동합니다 ClassLoader
.
/**
* Private helper method
*
* @param directory
* The directory to start with
* @param pckgname
* The package name to search for. Will be needed for getting the
* Class object.
* @param classes
* if a file isn't loaded but still is in the directory
* @throws ClassNotFoundException
*/
private static void checkDirectory(File directory, String pckgname,
ArrayList<Class<?>> classes) throws ClassNotFoundException {
File tmpDirectory;
if (directory.exists() && directory.isDirectory()) {
final String[] files = directory.list();
for (final String file : files) {
if (file.endsWith(".class")) {
try {
classes.add(Class.forName(pckgname + '.'
+ file.substring(0, file.length() - 6)));
} catch (final NoClassDefFoundError e) {
// do nothing. this class hasn't been found by the
// loader, and we don't care.
}
} else if ((tmpDirectory = new File(directory, file))
.isDirectory()) {
checkDirectory(tmpDirectory, pckgname + "." + file, classes);
}
}
}
}
/**
* Private helper method.
*
* @param connection
* the connection to the jar
* @param pckgname
* the package name to search for
* @param classes
* the current ArrayList of all classes. This method will simply
* add new classes.
* @throws ClassNotFoundException
* if a file isn't loaded but still is in the jar file
* @throws IOException
* if it can't correctly read from the jar file.
*/
private static void checkJarFile(JarURLConnection connection,
String pckgname, ArrayList<Class<?>> classes)
throws ClassNotFoundException, IOException {
final JarFile jarFile = connection.getJarFile();
final Enumeration<JarEntry> entries = jarFile.entries();
String name;
for (JarEntry jarEntry = null; entries.hasMoreElements()
&& ((jarEntry = entries.nextElement()) != null);) {
name = jarEntry.getName();
if (name.contains(".class")) {
name = name.substring(0, name.length() - 6).replace('/', '.');
if (name.contains(pckgname)) {
classes.add(Class.forName(name));
}
}
}
}
/**
* Attempts to list all the classes in the specified package as determined
* by the context class loader
*
* @param pckgname
* the package name to search
* @return a list of classes that exist within that package
* @throws ClassNotFoundException
* if something went wrong
*/
public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
throws ClassNotFoundException {
final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
try {
final ClassLoader cld = Thread.currentThread()
.getContextClassLoader();
if (cld == null)
throw new ClassNotFoundException("Can't get class loader.");
final Enumeration<URL> resources = cld.getResources(pckgname
.replace('.', '/'));
URLConnection connection;
for (URL url = null; resources.hasMoreElements()
&& ((url = resources.nextElement()) != null);) {
try {
connection = url.openConnection();
if (connection instanceof JarURLConnection) {
checkJarFile((JarURLConnection) connection, pckgname,
classes);
} else if (connection instanceof FileURLConnection) {
try {
checkDirectory(
new File(URLDecoder.decode(url.getPath(),
"UTF-8")), pckgname, classes);
} catch (final UnsupportedEncodingException ex) {
throw new ClassNotFoundException(
pckgname
+ " does not appear to be a valid package (Unsupported encoding)",
ex);
}
} else
throw new ClassNotFoundException(pckgname + " ("
+ url.getPath()
+ ") does not appear to be a valid package");
} catch (final IOException ioex) {
throw new ClassNotFoundException(
"IOException was thrown when trying to get all resources for "
+ pckgname, ioex);
}
}
} catch (final NullPointerException ex) {
throw new ClassNotFoundException(
pckgname
+ " does not appear to be a valid package (Null pointer exception)",
ex);
} catch (final IOException ioex) {
throw new ClassNotFoundException(
"IOException was thrown when trying to get all resources for "
+ pckgname, ioex);
}
return classes;
}
이 세 가지 방법은 지정된 패키지에서 모든 클래스를 찾을 수있는 기능을 제공합니다.
당신은 이것을 다음과 같이 사용합니다 :
getClassesForPackage("package.your.classes.are.in");
이 메소드는 먼저 current를 가져옵니다 ClassLoader
. 그런 다음 해당 패키지가 포함 된 모든 리소스를 가져오고이 패키지를 반복 URL
합니다. 그런 다음 a를 만들고 URLConnection
어떤 유형의 URl이 있는지 결정합니다. 디렉토리 ( FileURLConnection
) 또는 jar 또는 zip 파일 ( JarURLConnection
) 내의 디렉토리 일 수 있습니다 . 연결 유형에 따라 두 가지 다른 메소드가 호출됩니다.
먼저이면 어떻게되는지 봅시다 FileURLConnection
.
먼저 전달 된 File이 존재하고 디렉토리인지 확인합니다. 이 경우 클래스 파일인지 확인합니다. 그렇다면 Class
객체가 생성되어에 삽입됩니다 ArrayList
. 클래스 파일이 아니라 디렉토리 인 경우 간단히 반복하여 동일한 작업을 수행합니다. 다른 모든 사례 / 파일은 무시됩니다.
(가) 경우 URLConnection
A는 JarURLConnection
다른 개인 도우미 메서드가 호출됩니다. 이 방법은 zip / jar 아카이브의 모든 항목을 반복합니다. 하나의 항목이 클래스 파일이고 패키지 내부에 있으면 Class
객체가 생성되어에 저장됩니다 ArrayList
.
모든 리소스를 파싱 한 후 (메인 메서드) ArrayList
현재 지정된 패키지의 모든 클래스를 포함하는 클래스를 반환합니다 ClassLoader
.
프로세스가 어느 시점에서 실패 ClassNotFoundException
하면 정확한 원인에 대한 자세한 정보가 포함 된 a 가 발생합니다.
sun.net.www.protocol.file.FileURLConnection
컴파일시 경고를 생성합니다 ( "경고 : sun.net.www.protocol.file.FileURLConnection은 Sun 독점 API이며 향후 릴리스에서 제거 될 수 있습니다"). 해당 클래스를 사용하는 대안이 있습니까, 아니면 주석을 사용하여 경고를 억제 할 수 있습니까?
if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }
JPA 어노테이션이있는 클래스를 검색하기 위해 코드에서 수행 한 작업
추가 라이브러리를 사용하지 않고 :
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;
}
}
upackage
입니다 null
... :(
String pack = getPackage().getName();
다음을 추가해야합니다.pack = pack.replaceAll("[.]", "/");
일반적으로 클래스 로더는 클래스 경로의 모든 클래스를 스캔 할 수 없습니다. 그러나 일반적으로 유일하게 사용되는 클래스 로더는 디렉토리 및 jar 파일 목록을 검색하고 ( getURLs 참조 ) 사용 가능한 클래스를 나열하기 위해 하나씩 열 수있는 UrlClassLoader입니다 . 클래스 경로 스캔이라고하는이 방법은 Scannotation and Reflections 에서 구현됩니다 .
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 컴파일러가 클래스 경로에서 발견 된 프로세서를 자동으로 감지하므로 스캔이 완전히 자동화되므로 추가 설정이 필요하지 않습니다.
주어진 패키지에 모든 클래스를 나열하는 가장 강력한 메커니즘은 현재 ClassGraph 입니다. 새로운 JPMS 모듈 시스템을 포함하여 가장 광범위한 클래스 경로 지정 메커니즘을 처리하기 때문 입니다. (저는 저자입니다.)
List<String> classNames = new ArrayList<>();
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
.enableClassInfo().scan()) {
classNames.addAll(scanResult.getAllClasses().getNames());
}
내가하는 방법은 다음과 같습니다. 모든 하위 폴더 (하위 패키지)를 스캔하고 익명 클래스를로드하려고 시도하지 않습니다.
/**
* 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;
}
이 문제를 해결하는 간단한 github 프로젝트를 만들었습니다.
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.
내 다른 답변 도 참조하십시오
예, API를 거의 사용하지 않는 것이 좋습니다. 여기서 내가 좋아하는 방법은 최대 절전 모드를 사용하고 특정 주석으로 주석이 달린 클래스를 찾아야하는이 문제에 직면했습니다.
이 클래스를 사용하여 선택하려는 클래스를 표시하십시오.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EntityToBeScanned {
}
그런 다음 수업에 다음과 같이 표시하십시오.
@EntityToBeScanned
public MyClass{
}
다음과 같은 메소드가있는이 유틸리티 클래스를 만드십시오.
public class ClassScanner {
public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
Reflections reflections = new Reflections(".*");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
return annotated;
}
}
발견 된 클래스 세트 를 얻으려면 allFoundClassesAnnotatedWithEntityToBeScanned () 메소드를 호출하십시오 .
아래에 주어진 라이브러리가 필요합니다.
<!-- 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>
클래스 경로에서 모든 클래스 로더 항목을 찾아야합니다.
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());
// ....
}
entry가 디렉토리이면 오른쪽 하위 디렉토리를 찾으십시오.
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());
}
}
이제 패키지와 함께 모든 클래스 이름을 가지면 리플렉션으로로드하고 클래스 또는 인터페이스인지 분석 할 수 있습니다.
리플렉션 라이브러리를 사용하려고 시도했지만 사용하는 데 문제가 있었으며 패키지의 클래스를 얻기 위해 포함해야 할 항아리가 너무 많았습니다.
이 중복 질문에서 찾은 해결책을 게시 할 것입니다 : 패키지에서 모든 클래스 이름을 얻는 방법?
대답은 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 클래스를 작성했습니다. 테스트 메소드가 포함되어 있습니다. 확인 할 수 있습니다 ~
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();
}
}
@RunWith(Parameterized.class)
Maven을 사용할 때 Aleksander Blomskøld의 솔루션 이 매개 변수 테스트에 적합하지 않았습니다 . 테스트 이름이 올 바르고 발견되었지만 실행되지 않은 위치는 다음과 같습니다.
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running some.properly.named.test.run.with.maven.SomeTest
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 sec
필자의 경우 @Parameters
패키지에 각 클래스의 인스턴스를 만듭니다. 테스트는 IDE에서 로컬로 실행될 때 잘 작동했습니다. 그러나 Maven을 실행할 때 Aleksander Blomskøld의 솔루션으로 찾은 클래스는 없습니다.
Aleksander 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);
이건 어때?
public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
final String pkgPath = pkgName.replace('.', '/');
final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
Path root;
if (pkg.toString().startsWith("jar:")) {
try {
root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
} catch (final FileSystemNotFoundException e) {
root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
}
} else {
root = Paths.get(pkg);
}
final String extension = ".class";
try (final Stream<Path> allPaths = Files.walk(root)) {
allPaths.filter(Files::isRegularFile).forEach(file -> {
try {
final String path = file.toString().replace('/', '.');
final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
allClasses.add(Class.forName(name));
} catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
}
});
}
return allClasses;
}
그런 다음 함수를 오버로드 할 수 있습니다.
public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
return getClassesForPackage(pkg.getName());
}
테스트해야 할 경우 :
public static void main(final String[] argv) throws IOException, URISyntaxException {
for (final Class<?> cls : getClassesForPackage("my.package")) {
System.out.println(cls);
}
for (final Class<?> cls : getClassesForPackage(MyClass.class.getPackage())) {
System.out.println(cls);
}
}
IDE에 가져 오기 도우미가없는 경우 :
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
효과가있다:
IDE에서
JAR 파일
외부 의존성이없는
거의 모든 대답 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은 필수는 아닙니다 . 스트림 대신 for 루프를 사용할 수 있습니다. 그리고 당신은 이것을 이렇게 테스트 할 수 있습니다
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);
}
언급 할 가치가있는
일부 패키지 아래의 모든 클래스 목록을 원하는 경우 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;
}
}
@Staale의 답변 과 타사 라이브러리에 의존하지 않으려는 시도를 바탕으로 다음 과 같이 첫 번째 패키지 실제 위치를 검사하여 파일 시스템 접근 방식을 구현합니다.
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
...
Class<?>[] foundClasses = new Class<?>[0];
final ArrayList<Class<?>> foundClassesDyn = new ArrayList<Class<?>>();
new java.io.File(
klass.getResource(
"/" + curPackage.replace( "." , "/")
).getFile()
).listFiles(
new java.io.FileFilter() {
public boolean accept(java.io.File file) {
final String classExtension = ".class";
if ( file.isFile()
&& file.getName().endsWith(classExtension)
// avoid inner classes
&& ! file.getName().contains("$") )
{
try {
String className = file.getName();
className = className.substring(0, className.length() - classExtension.length());
foundClassesDyn.add( Class.forName( curPackage + "." + className ) );
} catch (ClassNotFoundException e) {
e.printStackTrace(System.out);
}
}
return false;
}
}
);
foundClasses = foundClassesDyn.toArray(foundClasses);
관련 클래스 그룹을로드하려는 경우 Spring이 도움이 될 수 있습니다.
Spring은 주어진 인터페이스를 구현하는 모든 클래스의 목록 또는 맵을 한 줄의 코드로 인스턴스화 할 수 있습니다. 목록 또는 맵에는 해당 인터페이스를 구현하는 모든 클래스의 인스턴스가 포함됩니다.
즉, 파일 시스템에서 클래스 목록을로드하는 대신 패키지와 상관없이로드하려는 모든 클래스에서 동일한 인터페이스를 구현하고 스프링을 사용하여 모든 인스턴스를 제공합니다. 이렇게하면 패키지에 관계없이 원하는 모든 클래스를로드 (및 인스턴스화) 할 수 있습니다.
반면에 패키지에 모두 포함시키는 것이 원하는 경우 해당 패키지의 모든 클래스가 지정된 인터페이스를 구현하도록합니다.
일반 자바 : FindAllClassesUsingPlainJavaReflectionTest.java
@Slf4j
class FindAllClassesUsingPlainJavaReflectionTest {
private static final Function<Throwable, RuntimeException> asRuntimeException = throwable -> {
log.error(throwable.getLocalizedMessage());
return new RuntimeException(throwable);
};
private static final Function<String, Collection<Class<?>>> findAllPackageClasses = basePackageName -> {
Locale locale = Locale.getDefault();
Charset charset = StandardCharsets.UTF_8;
val fileManager = ToolProvider.getSystemJavaCompiler()
.getStandardFileManager(/* diagnosticListener */ null, locale, charset);
StandardLocation location = StandardLocation.CLASS_PATH;
JavaFileObject.Kind kind = JavaFileObject.Kind.CLASS;
Set<JavaFileObject.Kind> kinds = Collections.singleton(kind);
val javaFileObjects = Try.of(() -> fileManager.list(location, basePackageName, kinds, /* recurse */ true))
.getOrElseThrow(asRuntimeException);
String pathToPackageAndClass = basePackageName.replace(".", File.separator);
Function<String, String> mapToClassName = s -> {
String prefix = Arrays.stream(s.split(pathToPackageAndClass))
.findFirst()
.orElse("");
return s.replaceFirst(prefix, "")
.replaceAll(File.separator, ".");
};
return StreamSupport.stream(javaFileObjects.spliterator(), /* parallel */ true)
.filter(javaFileObject -> javaFileObject.getKind().equals(kind))
.map(FileObject::getName)
.map(fileObjectName -> fileObjectName.replace(".class", ""))
.map(mapToClassName)
.map(className -> Try.of(() -> Class.forName(className))
.getOrElseThrow(asRuntimeException))
.collect(Collectors.toList());
};
@Test
@DisplayName("should get classes recursively in given package")
void test() {
Collection<Class<?>> classes = findAllPackageClasses.apply(getClass().getPackage().getName());
assertThat(classes).hasSizeGreaterThan(4);
classes.stream().map(String::valueOf).forEach(log::info);
}
}
추신 : 오류 처리 등을 위해 상용구를 단순화하기 위해 여기 vavr
와 lombok
라이브러리를 사용하고 있습니다.
다른 구현은 내 GitHub daggerok / java-reflection-find-annotated-classes-or-methods repo에서 찾을 수 있습니다.
나는 너무 간단한 무언가를 위해 간절히 일하는 것을 찾을 수 없었다. 그래서 여기에, 잠시 동안 조여서 나 스스로 만들었습니다.
Reflections reflections =
new Reflections(new ConfigurationBuilder()
.filterInputsBy(new FilterBuilder().includePackage(packagePath))
.setUrls(ClasspathHelper.forPackage(packagePath))
.setScanners(new SubTypesScanner(false)));
Set<String> typeList = reflections.getAllTypes();