簡単にできそうで意外と面倒な package 配下のクラス一覧を取得する方法について。
Guava の ClassPath 利用
ClassPath を使えば簡単に取得できる。
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Set<Class<?>> allClasses = ClassPath.from(loader)
.getTopLevelClasses("my.package").stream()
.map(info -> info.load())
.collect(Collectors.toSet());
再帰的に取りたい場合は getTopLevelClassesRecursive()
を使う。
Reflections 利用
Reflections ライブラリだと以下のようになる。
Reflections reflections = new Reflections("my.package"); Set<Class<?>> allClasses = reflections.getSubTypesOf(Object.class);
Spring
ClassPathScanningCandidateComponentProvider
を利用する。
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*"))); Set<Class<?>> allClasses = provider.findCandidateComponents("my.package").stream() .map(bean -> uncheckCall(() -> Class.forName(bean.getBeanClassName())));
uncheckCall は例外用のユーティリティ。
public static <T> T uncheckCall(Callable<T> callable) { try { return callable.call(); } catch (Exception e) { throw new RuntimeException(e); } }
ライブラリ利用なし
フラットなファイルシステムから取る場合と jar から取る場合で分岐が必要で泥臭くなる。
public static Set<Class<?>> listClasses(String packageName) { final String resourceName = packageName.replace('.', '/'); final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); final URL root = classLoader.getResource(resourceName); if ("file".equals(root.getProtocol())) { File[] files = new File(root.getFile()).listFiles((dir, name) -> name.endsWith(".class")); return Arrays.asList(files).stream() .map(file -> file.getName()) .map(name -> name.replaceAll(".class$", "")) .map(name -> packageName + "." + name) .map(fullName -> uncheckCall(() -> Class.forName(fullName))) .collect(Collectors.toSet()); } if ("jar".equals(root.getProtocol())) { try (JarFile jarFile = ((JarURLConnection) root.openConnection()).getJarFile()) { return Collections.list(jarFile.entries()).stream() .map(jarEntry -> jarEntry.getName()) .filter(name -> name.startsWith(resourceName)) .filter(name -> name.endsWith(".class")) .map(name -> name.replace('/', '.').replaceAll(".class$", "")) .map(fullName -> uncheckCall(() -> classLoader.loadClass(fullName))) .collect(Collectors.toSet()); } catch (IOException e) { throw new RuntimeException(e); } } return new HashSet<>(); }
ToolProvider で取得
tools.jar が必要なので JDK 入れないとダメだけど ToolProvider 使うこともできる。
public static Set<Class<?>> listClasses(String packageName) throws Exception { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); JavaFileManager jfm = compiler.getStandardFileManager(new DiagnosticCollector<>(), null, null); Set<JavaFileObject.Kind> kind = new HashSet<JavaFileObject.Kind>(){{ add(JavaFileObject.Kind.CLASS); }}; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Iterable<JavaFileObject> iterable = jfm.list(StandardLocation.CLASS_PATH, packageName, kind, false); return StreamSupport.stream(iterable.spliterator(), false) .map(javaFileObject -> javaFileObject.toUri().toString()) .map(uri -> uri.replace("/", ".")) .map(dottedUri -> dottedUri.substring(dottedUri.indexOf(packageName)).replaceAll(".class$", "")) .map(fqcn -> uncheckCall(() -> classLoader.loadClass(fqcn))) .collect(Collectors.toSet()); }