클래스 경로에서 Java 패키지의 모든 클래스를 어떻게 읽습니까?


94

Java 패키지에 포함 된 클래스를 읽어야합니다. 이러한 클래스는 클래스 경로에 있습니다. 이 작업을 Java 프로그램에서 직접 수행해야합니다. 간단한 방법을 알고 계십니까?

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

7
간단히 말해서, 당신은 그렇게 쉽게 할 수 없습니다. 일부 상황에서 작동하는 매우 긴 트릭이 있지만 다른 디자인을 강력히 제안합니다.
skaffman


해결책은 Weld 프로젝트 에서 찾을 수 있습니다 .
Ondra Žižka 2011

답변은 다음 링크를 참조하십시오. stackoverflow.com/questions/176527/…
Karthik E

답변:


48

당신이있는 경우 봄을 당신이 다음 클래스 경로에 다음이 그것을 할 것입니다.

XmlRootElement로 주석이 달린 패키지에서 모든 클래스를 찾습니다.

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

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

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

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

코드 조각 주셔서 감사합니다. :) 후보 목록에서 클래스 중복을 방지하려면 컬렉션 유형을 목록에서 집합으로 변경합니다. 그리고 classMetaData의 hasEnclosingClass () 및 getEnclosingClassName ()을 사용하십시오. 기본 클래스에 대한 사용 getEnclosingClass 방법
traeper

29

여기에 설명 된 Reflections Project를 사용할 수 있습니다.

매우 완전하고 사용하기 쉽습니다.

위 웹 사이트의 간략한 설명 :

Reflections는 클래스 경로를 스캔하고 메타 데이터의 색인을 생성하며 런타임시 쿼리 할 수 ​​있으며 프로젝트 내의 많은 모듈에 대해 해당 정보를 저장하고 수집 할 수 있습니다.

예:

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

Equinox에서 작동합니까? 그렇다면 플러그인을 활성화하지 않고도 그렇게 할 수 있습니까?
태그

춘분에서 작동합니다. 이전 버전의 Reflections에서는 번들 jar vfsType을 추가합니다. 참조 여기
ZAPP

28

나는 이것을 사용하며 파일 또는 jar 아카이브와 함께 작동합니다.

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

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

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

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

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

1
File.Separator에 대한 참조를 꺼내고 '/'만 사용하면 작동합니다.
Will Glass

1
경로에 공백이있을 때 jar 파일에 대해 작동하려면 한 번 더 변경해야합니다. jar 파일 경로를 디코딩해야합니다. 변경 : jarFileName = packageURL.getFile (); 받는 사람 : jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass

나는 jar ..에 패키지 이름 만 얻습니다. 클래스 이름을 얻지
못합니다

new File (uri)은 공백 문제를 해결합니다.
Trejkaz 2013 년

entryName.lastIndexOf ( '.')는 -1이됩니다
marstone

11

Spring은 .NET에서 뛰어난 클래스 경로 검색 기능을 구현했습니다 PathMatchingResourcePatternResolver. classpath*: 접두사 를 사용하면 주어진 계층 구조의 클래스를 포함한 모든 리소스를 찾을 수 있으며 원하는 경우 필터링 할 수도 있습니다. 그럼 당신의 자식을 사용할 수 있습니다 AbstractTypeHierarchyTraversingFilter, AnnotationTypeFilter그리고 AssignableTypeFilter클래스 수준 주석에 하나 이러한 리소스를 필터링하거나 인터페이스를 그들은 구현합니다.


6

자바 1.6.0_24 :

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

이 솔루션은 EJB 환경 에서 테스트되었습니다 .


6

ScannotationReflections 는 클래스 경로 스캔 방식을 사용합니다.

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

또 다른 접근 방식은 Java Pluggable Annotation Processing API 를 사용 하여 컴파일 타임에 모든 주석 처리 된 클래스를 수집하고 런타임 사용을위한 색인 파일을 빌드하는 주석 프로세서를 작성하는 것입니다. 이 메커니즘은 ClassIndex 라이브러리 에서 구현됩니다 .

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

3

이 기능은 내가 아는 한 Java 리플렉션 API에서 여전히 의심스럽게 누락되었습니다. 다음과 같이 패키지 객체를 얻을 수 있습니다.

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

그러나 아마 눈치 채 셨겠지만, 그 패키지의 클래스를 나열 할 수는 없습니다. 현재로서는 좀 더 파일 시스템 지향적 인 접근 방식을 취해야합니다.

게시물 에서 몇 가지 샘플 구현을 찾았 습니다.

클래스가 JAR 파일에 묻혀있을 때 이러한 메서드가 작동 할 것이라고 100 % 확신하지는 않지만 그중 하나가이를 수행하기를 바랍니다.

나는 @skaffman에 동의합니다 ... 이에 대해 다른 방법이 있다면 대신 그렇게하는 것이 좋습니다.


4
의심스럽지 않고 그런 식으로 작동하지 않습니다. 클래스는 패키지에 "속하지"않고 참조가 있습니다. 연관성은 다른 방향을 가리 키지 않습니다.
skaffman

1
@skaffman 매우 흥미로운 점입니다. 그런 식으로 생각하지 않았습니다. 그래서 우리가 그 생각의 흔적을 헤매고있는 ​​한, 왜 연관성은 양방향이 아닌가?
Brent는

3

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

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

나는 몇 년 후에 이것을 발견했습니다. 이것은 훌륭한 도구입니다! 리플렉션보다 훨씬 빠르게 작동하며 정적 이니셜 라이저를 호출하지 않는 것이 좋습니다. 우리가 가진 문제를 해결하는 데 필요한 것입니다.
akagixxer

2

eXtcos 는 유망 보입니다. 다음과 같은 모든 클래스를 찾고 싶다고 상상해보십시오.

  1. "Component"클래스에서 확장하여 저장
  2. "MyComponent"로 주석이 추가되고
  3. "공통"패키지에 있습니다.

eXtcos를 사용하면 다음과 같이 간단합니다.

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

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

2
  1. Bill Burke는 ( 클래스 스캔에 대한 멋진 기사) 를 썼고 Scannotation 을 썼습니다 .

  2. Hibernate 는 이미 다음과 같이 작성했습니다.

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI 가이 문제를 해결할 수 있지만 모르겠습니다. 아직 완전히 조사하지 않았습니다.

.

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

또한 주석의 경우 :

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

기사 링크가 죽었습니다.
user2418306

1

나는 그것을 구현했으며 대부분의 경우 작동합니다. 길기 때문에 여기 파일에 넣었습니다 .

아이디어는 대부분의 경우 사용할 수있는 클래스 소스 파일의 위치를 ​​찾는 것입니다 (알려진 예외는 JVM 클래스 파일입니다-제가 테스트 한 한). 코드가 디렉토리에있는 경우 모든 파일과 스팟 클래스 파일 만 스캔합니다. 코드가 JAR 파일에있는 경우 모든 항목을 스캔하십시오.

이 방법은 다음과 같은 경우에만 사용할 수 있습니다.

  1. 발견하려는 동일한 패키지에 클래스가 있습니다.이 클래스를 SeedClass라고합니다. 예를 들어 'java.io'의 모든 클래스를 나열하려는 경우 시드 클래스는 java.io.File.

  2. 클래스는 디렉토리 또는 JAR 파일에 있으며 소스 파일 정보 (소스 코드 파일이 아니라 소스 파일)가 있습니다. 내가 시도한 한 JVM 클래스를 제외하고 거의 100 % 작동합니다 (이 클래스는 JVM과 함께 제공됨).

  3. 프로그램에는 해당 클래스의 ProtectionDomain에 액세스 할 수있는 권한이 있어야합니다. 프로그램이 로컬로로드되면 문제가 없습니다.

정기적 인 사용을 위해서만 프로그램을 테스트 했으므로 여전히 문제가있을 수 있습니다.

이게 도움이 되길 바란다.


1
그것은 매우 흥미로운 것 같습니다! 나는 그것을 사용하려고 노력할 것이다. 도움이되었다고 생각되면 오픈 소스 프로젝트에서 코드를 사용할 수 있습니까?

1
저도 오픈 소스로 준비하고 있습니다. 그러니 : D
NawaMan

@NawaMan : 드디어 오픈 소스 했습니까? 그렇다면 마지막 버전은 어디에서 찾을 수 있습니까? 감사!
Joanis

1

다음은 위 / 아래의 다른 답변에 대한 약간의 수정 인 또 다른 옵션입니다.

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

0

애플릿이 일반적인 장소 였을 때 클래스 경로에 URL이있을 수 있습니다. 클래스 로더가 클래스를 필요로 할 때 http 리소스를 포함하여 클래스 경로의 모든 위치를 검색합니다. 클래스 경로에 URL 및 디렉토리와 같은 것을 가질 수 있기 때문에 클래스의 최종 목록을 얻는 쉬운 방법은 없습니다.

그러나 꽤 가까워 질 수 있습니다. 현재 일부 Spring 라이브러리에서이 작업을 수행하고 있습니다. 클래스 경로에있는 모든 항아리를 가져 와서 파일처럼 열 수 있습니다. 그런 다음이 파일 목록을 가져 와서 클래스를 포함하는 데이터 구조를 만들 수 있습니다.


0

종속성 maven을 사용하십시오.

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

다음 코드를 사용하십시오.

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

-2

Brent-연결이 한 가지 방법 인 이유는 CLASSPATH의 모든 구성 요소에있는 모든 클래스가 모든 패키지 (java / javax 제외)에서 자신을 선언 할 수 있다는 사실과 관련이 있습니다. 따라서 아무도 모르거나 알 수 없기 때문에 주어진 "패키지"에있는 모든 클래스의 매핑이 없습니다. 내일 jar 파일을 업데이트하고 클래스를 제거하거나 추가 할 수 있습니다. 그것은 세계의 모든 나라에서 John / Jon / Johan이라는 이름의 모든 사람들의 목록을 얻으려는 것과 같습니다. 우리 중 누구도 전지전능하지 않으므로 아무도 정답을 얻지 못할 것입니다.


좋은 철학적 대답이지만 CDI 빈 스캔은 어떻게 작동합니까? 아니면 Hibernate는 @Entities를 어떻게 스캔합니까?
Ondra Žižka 2011
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.