Хотя вышеупомянутые ответы довольно корректны, я думаю, что намного более важно отметить, что слова "шаблон разработки" абсолютно неизвестны 90% всех людей, которые создают программное обеспечение. Они только начинают писать код.
проблема не выбирает лучший подход дизайна, она убеждает других, что дизайн имеет значение.
Вот базовый пример, предполагая, что классы не упакованы в 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);
}
}
Вот служебный метод, использующий Spring.
Подробности о шаблоне можно найти здесь
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;
}
Да, но это не самая простая задача. С этим есть много проблем. Не все классы легко найти. Некоторые классы могут находиться в: Jar, как файл класса, по сети и т. Д.
Взгляните на этот поток.
Чтобы убедиться, что они принадлежат к типу ICommand, вам придется использовать отражение чтобы проверить наследующий класс.
Ниже, реализация с использованием 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());
}
Начните с общедоступного Classloader.getResources (имя строки). Спросите у загрузчика классов класс, соответствующий каждому имени в интересующем вас пакете. Повторите для всех релевантных загрузчиков классов.
Это был бы очень полезный инструмент, который нам нужен, и JDK должен предоставить некоторую поддержку.
Но, наверное, лучше сделать это во время сборки. Вы знаете, где находятся все ваши файлы классов, и можете проверять их статически и строить график. Во время выполнения вы можете запросить этот график, чтобы получить все подтипы. Это требует дополнительной работы, но я считаю, что это действительно относится к процессу сборки.
Используя 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 ищет файлы классов и jar-файлы в системном пути к классам.
В вашем конкретном случае, вы можете изменить acceptClass следующим образом:
@Override public boolean acceptClass(Class<?> c) {
return ICommand.class.isAssignableFrom(c);
}
Следует отметить одно: будьте осторожны с тем, что вы возвращаете в acceptClassName, поскольку следующее, что делает ClasspathClassesFinder, - это загружает класс и вызывает acceptClass. Если acceptClassName всегда возвращает true, вы в конечном итоге загрузите каждый класс в пути к классам, и это может вызвать OutOfMemoryError.