1. 특정 패키지에 있는 클래스 가져오기

이전에 학습했던 System Class Loader를 이용하면 특정 패키지에 있는 모든 클래스를 가지고 올 수 있다.

private Set<Class<?>> findAllClassesUsingClassLoader(String packageName) {
    final InputStream stream = ClassLoader.getSystemClassLoader()
        .getResourceAsStream(packageName.replaceAll("[.]", "/"));
    final BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(stream)));
    return reader.lines()
        .filter(line -> line.endsWith(".class"))
        .map(line -> getClass(line, packageName))
        .collect(Collectors.toSet());
}

private Class<?> getClass(String className, String packageName) {
    final String classPath = packageName + "."
        + className.substring(0, className.lastIndexOf('.'));
    try {
        return Class.forName(classPath);
    } catch (ClassNotFoundException e) {
        throw new IllegalArgumentException(String.format("Cannot find class. (%s)", classPath));
    }
}

2. 가져온 클래스들의 빈 생성자 찾기

private Set<Controller> findAllControllers(final String controllerPackage) {
    final Set<Class<?>> classes = findAllClassesUsingClassLoader(controllerPackage);
    return classes.stream()
        .filter(clazz -> clazz.getDeclaredConstructors().length != 0)
        .filter(this::hasNoArgumentConstructor)
        .map(this::findNoArgumentConstructor)
        .map(constructor -> {
            try {
                return (Controller) constructor.newInstance();
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                throw new IllegalArgumentException(String.format("Cannot invoke constructor. (%s)", constructor.getName()));
            }
        })
        .collect(Collectors.toSet());
}

private boolean hasNoArgumentConstructor(final Class<?> clazz) {
    return Arrays.stream(clazz.getDeclaredConstructors())
        .anyMatch(constructor -> constructor.getParameterTypes().length == 0);
}

private Constructor<?> findNoArgumentConstructor(final Class<?> clazz) {
    return Arrays.stream(clazz.getDeclaredConstructors())
        .filter(constructor -> constructor.getParameterTypes().length == 0)
        .findAny()
        .orElseThrow(() -> new IllegalArgumentException("Cannot find no-argument constructor."));
}

getDeclaredConstructors() 는 해당 클래스가 가지고 있는 모든 생성자를 반환한다. newInstance() 메소드를 통해서 생성자를 통해 인스턴스화 할 수 있다.

참고 자료

https://www.baeldung.com/java-find-all-classes-in-package

https://stackoverflow.com/questions/25054460/java-execute-a-class-method-with-a-specify-annotation

https://stackoverflow.com/questions/27810634/how-can-i-check-a-class-has-no-arguments-constructor

https://www.tutorialspoint.com/java/lang/class_getdeclaredconstructors.htm

https://www.tutorialspoint.com/javareflect/javareflect_constructor_newinstance.htm