2009-11-27 5 views
11

Допустим, у меня есть пакет java commands, который содержит классы, которые все наследуют от ICommand. Могу ли я получить все эти классы? Я блокирую что-то между линиями:Получение всех классов из пакета

Package p = Package.getPackage("commands"); 
Class<ICommand>[] c = p.getAllPackagedClasses(); //not real 

Возможно ли это?

+0

отредактируйте это, чтобы быть разборчивым. Что такое «javapackage».Что такое «желание команд». – bmargulies

+0

Тема темы прекрасна: http://google.com/search?q=Getting+all+Classes+from+a+Package =) – BalusC

+0

@balusc, но/correct/answer сложно. –

ответ

14

Вот простой пример, предполагая, что классы не JAR-упаковано:

// Prepare. 
String packageName = "com.example.commands"; 
List<Class<ICommand>> commands = new ArrayList<Class<ICommand>>(); 
URL root = Thread.currentThread().getContextClassLoader().getResource(packageName.replace(".", "/")); 

// Filter .class files. 
File[] files = new File(root.getFile()).listFiles(new FilenameFilter() { 
    public boolean accept(File dir, String name) { 
     return name.endsWith(".class"); 
    } 
}); 

// Find classes implementing ICommand. 
for (File file : files) { 
    String className = file.getName().replaceAll(".class$", ""); 
    Class<?> cls = Class.forName(packageName + "." + className); 
    if (ICommand.class.isAssignableFrom(cls)) { 
     commands.add((Class<ICommand>) cls); 
    } 
} 
+3

+1, хотя небольшое улучшение: 'root.getFile()' должен быть 'URLDecoder.decode (root.getFile()," UTF-8 ")' в случае любых пробелов в пути 'classloader'. 'getResource()' будет преобразовывать это в '% 20', вместе с другими символами, я полагаю. – Aquillo

1

Начать с public Classloader.getResources (String name). Спросите classloader для класса, соответствующего каждому имени в интересующем вас пакете. Повторите для всех загрузчиков классов.

1

Да, но это не самая простая вещь. С этим связано множество проблем. Не все классы легко найти. Некоторые классы могут быть в: Jar, как файл класса, по сети и т.д.

Посмотрите at this thread.

Для того, чтобы убедиться, что они были типа ICommand тогда вам придется использовать отражение для проверки наследующий класс.

3

Это полезный метод использования Spring.

Подробная информация о шаблоне можно найти here

public static List<Class> listMatchingClasses(String matchPattern) throws IOException { 
    List<Class> classes = new LinkedList<Class>(); 
    PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver(); 
    Resource[] resources = scanner.getResources(matchPattern); 

    for (Resource resource : resources) { 
     Class<?> clazz = getClassFromResource(resource); 
     classes.add(clazz); 
    } 

    return classes; 
} 



public static Class getClassFromResource(Resource resource) { 
    try { 
     String resourceUri = resource.getURI().toString(); 
     resourceUri = resourceUri.replace(esourceUri.indexOf(".class"), "").replace("/", "."); 
     // try printing the resourceUri before calling forName, to see if it is OK. 
     return Class.forName(resourceUri); 
    } catch (Exception ex) { 
     ex.printStackTrace(); 
    } 
    return null; 
} 
1

Это было бы очень полезным инструментом, нам нужно, и JDK должна оказать некоторую поддержку.

Но, вероятно, это лучше сделать во время сборки. Вы знаете, где все ваши файлы классов, и вы можете проверить их статически и построить график. Во время выполнения вы можете запросить этот граф, чтобы получить все подтипы. Это требует больше работы, но я считаю, что это действительно относится к процессу сборки.

+0

Что-то вроде API JSR-199? См. Http://stackoverflow.com/questions/1810614/getting-all-classes-from-a-package/1811120#1811120 –

6

Ниже, реализация с использованием JSR-199 API т.е. классов от javax.tools.*:

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

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
     null, null, null); 

Location location = StandardLocation.CLASS_PATH; 
String packageName = "commands"; 
Set<JavaFileObject.Kind> kinds = new HashSet<JavaFileObject.Kind>(); 
kinds.add(JavaFileObject.Kind.CLASS); 
boolean recurse = false; 

Iterable<JavaFileObject> list = fileManager.list(location, packageName, 
     kinds, recurse); 

for (JavaFileObject javaFileObject : list) { 
    commands.add(javaFileObject.getClass()); 
} 
+0

Интересно, но 'ToolProvider.getSystemJavaCompiler()' возвращает 'null' как в Eclipse, так и в CLI? Что-то более необходимо? – BalusC

+1

После копания это, по-видимому, требует JDK вместо JRE (который у меня, однако, есть в CLI, я позже буду копать, почему это не работает). Это означало бы, что вам нужно установить JDK в среду final/prod, чтобы заставить его работать. Это может быть шоустоппер. – BalusC

+2

Действительно, вам нужен JDK для получения объекта '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ', ' На серверной стороне должно быть хорошо (большинство серверов приложений используют JDK в конце концов, например, для компиляции JSP). Но, конечно, могут быть исключения. На самом деле, я просто хотел показать, что в JDK есть определенная поддержка, даже если это не идеальный вариант использования для API компилятора (здесь это плохое использование). Этот API может делать больше, гораздо больше, например, компиляция на лету исходного кода Java, который генерируется динамически в памяти (что уже более интересно). –

0

Используя Johannes Link's ClasspathSuite, я был в состоянии сделать это следующим образом:

import org.junit.extensions.cpsuite.ClassTester; 
import org.junit.extensions.cpsuite.ClasspathClassesFinder; 

public static List<Class<?>> getClasses(final Package pkg, final boolean includeChildPackages) { 
    return new ClasspathClassesFinder(new ClassTester() { 
     @Override public boolean searchInJars() { return true; } 
     @Override public boolean acceptInnerClass() { return false; } 
     @Override public boolean acceptClassName(String name) { 
      return name.startsWith(pkg.getName()) && (includeChildPackages || name.indexOf(".", pkg.getName().length()) != -1); 
     } 
     @Override public boolean acceptClass(Class<?> c) { return true; } 
    }, System.getProperty("java.class.path")).find(); 
} 

ClasspathClassesFinder ищет класс файлов и банок в системном пути.

В вашем конкретном случае, вы могли бы изменить acceptClass так:

@Override public boolean acceptClass(Class<?> c) { 
    return ICommand.class.isAssignableFrom(c); 
} 

Одно замечание: будьте осторожны, что вы возвращаетесь в acceptClassName, как следующая вещь ClasspathClassesFinder делает это загрузить класс и вызвать acceptClass , Если acceptClassName всегда возвращает true, вы в конечном итоге загружаете каждый класс в путь класса и это может вызвать OutOfMemoryError.

0

Вы можете использовать OpenPojo и сделать это:

final List<PojoClass> pojoClasses = PojoClassFactory.getPojoClassesRecursively("my.package.path", null); 

Затем вы можете перейти по списку и выполнять любые функции вы хотите.