안녕하세요~!!

날씨가 부쩍추워지고 이제 12월에 들어섰네요ㅎㅎ모두 올해 작심하고 이루려 했던 목표들은 모두 이루셨으면 좋겠어요!!
Java로 개발을 하다보면
특정 인터페이스를 상속받고 있는 클래스들만 어디 한곳에 리스트로 추출하고 싶을때가 있더라구요
그래서 이번에 리플렉트를 응용해서 알아보겠습니다.
Interface를 상속받고있는 클래스들을 List로 추출하기
주로 사용된 기술
- 리플렉션 : 리플렉트 패키지를 사용하여 클래스 정보를 동적으로 가져오고 조작
- 클래스로더 : 현재 쓰레드의 클래스 로더를 얻어서 클래스 정보를 읽어온다.
- 파일 시스템 : 주어진 패키지 이름으로 파일 시스템 경로로 변환해 경로에있는 파일 수집
- 예외처리 : 파일 처리 및 클래스 로딩시 예외처리
총 5개의 기술이 사용되었습니다.
학습성과
리플렉션과 클래스 로더를 사용하여 런타임 중에 클래스를 검색하고 조작하는 방법을 배울 수 있었습니다.
이를 통해 동적인 클래스 로딩과 검색에 대한 이해를 높일 수 있었으며, 이러한 접근 방법을 통해 유연한 프로그래밍을 할 수 게되었습니다.
상황
- FindImplementedClasses 자바 파일에서 Sample 패키지 안에 SampleInterface 인터페이스가 상속받은 클래스를 찾는다.
- Sample패키지 내에 3개의 클래스 중 단 2개의 클래스만 SampleInterface 인터페이스를 상속받고있다.
- 오차없이 SampleInterface 인터페이스를 상속받고있는 클래스를 리스트에 담고 출력한다.
실행 화면
소스가 간단하기에 영상이 아닌 사진으로 대체합니다.
전체소스
package Java.Reflect;
import java.util.ArrayList;
import Java.Reflect.Sample.SampleInterface;
public class FindImplementedClasses {
public static void main(String[] args) {
String targetPackage = "Java.Reflect.Sample";
Class<?> interfaze = SampleInterface.class;
ArrayList<Class<?>> foundClass = findClassWithSelectQuery(targetPackage, interfaze);
if (foundClass != null && foundClass.size() != 0) {
for (Class<?> cls : foundClass) {
System.out.println(cls.getName());
}
}
}
public static ArrayList<Class<?>> findClassWithSelectQuery(String packageName, Class<?> interfaze) {
ArrayList<Class<?>> clsArr = new ArrayList<Class<?>>();
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = packageName.replace('.', '/');
java.net.URL resource = classLoader.getResource(path);
String directory = resource.getFile();
java.io.File dir = new java.io.File(directory);
if (dir.exists()) {
java.io.File[] files = dir.listFiles();
for (java.io.File file : files) {
if (file.getName().endsWith(".class")) {
String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
try {
Class<?> clazz = Class.forName(className);
if (interfaze.isAssignableFrom(clazz) && !clazz.isInterface()) {
clsArr.add(clazz);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return clsArr;
}
}
소스설명
대상 패키지와 찾을 Interface변수
String targetPackage = "Java.Reflect.Sample";
Class<?> interfaze = SampleInterface.class;
대상 패키지와 찾을 인터페이스 클래스를 변수로 선언하여 매개변수로 보낼 수 있도록 설정한다.(생략가능)
인터페이스 찾는 함수
public static ArrayList<Class<?>> findClassWithSelectQuery(String packageName, Class<?> interfaze) {
ArrayList<Class<?>> clsArr = new ArrayList<Class<?>>();
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = packageName.replace('.', '/');
java.net.URL resource = classLoader.getResource(path);
String directory = resource.getFile();
java.io.File dir = new java.io.File(directory);
if (dir.exists()) {
java.io.File[] files = dir.listFiles();
for (java.io.File file : files) {
if (file.getName().endsWith(".class")) {
String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
try {
Class<?> clazz = Class.forName(className);
if (interfaze.isAssignableFrom(clazz) && !clazz.isInterface()) {
clsArr.add(clazz);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return clsArr;
}
이 소스중에서
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = packageName.replace('.', '/');
java.net.URL resource = classLoader.getResource(path);
String directory = resource.getFile();
java.io.File dir = new java.io.File(directory);
이 부분은 현재 쓰레드의 컨텍스트 클래스 로더를 가져온다. 이 로더는 클래스 피일 및 기타 리소스를 로드하는데 사용된다.
그리고 패키지 이름을 받아서 해당 패키지의 경로로 변환합니다.
클래스 로더를 사용해 해당 패키지 경로에 대한 URL 리소스를 가져온다. 이 URL은 클래스 로더가 클래스 파일을 찾는 경로에 대한 정보를 포함한다.
그리고 URL 객체로부터 파일 시스템에서의 경로를 얻어온다. 이 경로는 리소스가 위치한 파일 시스템의 경로를 문자열로 표현한 것이다.
파일 시스템 상의 결로를 기반으로 자바의 파일 객체를 생성한다.
if (dir.exists()) {
java.io.File[] files = dir.listFiles();
for (java.io.File file : files) {
if (file.getName().endsWith(".class")) {
String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
try {
Class<?> clazz = Class.forName(className);
if (interfaze.isAssignableFrom(clazz) && !clazz.isInterface()) {
clsArr.add(clazz);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
해당 디렉토리에 있는 파일들의 목록을 가져오고 파일이 .class로 끝나는 클래스 파일인지 확인한다.이 조건을 통과하면 해당 파일은 클래스 파일로 간주한다.
그리고 Class.forName() 메서드를 사용해서 해당 클래스를 동적으로 로드한다음, 이를 통해 클래스 파일을 JVM에 로드하고 그 클래스를 나타내는 Class 객체를 반환한다.
이때 중요한것은 아래의 조건이다.
if (interfaze.isAssignableFrom(clazz) && !clazz.isInterface())
얻은 클래스 객체가 주어진 인터페이스를 구현하고 있는지 확인한다. 만약 해당 인터페이스를 구현하고 있고, 인터페이스 자체가 아니라면 List객체에 추가하지 않는다.
이제 이렇게 리턴되는 List를 출력시키는 메소드만 지나면 결과가나올것이다.
마치며
간단하게 특정 인터페이스클래스를 상속받고있는 클래스만 추출하는 프로그램을 만들어봤습니다.
프로그램을 만들면서 파일 시스템과 예외처리에 대해서는 알고있었고, 리플렉트에 대해서도 아주 얕게 알고있었지만
직접 패키지에서 클래스를 찾아 해당 클래스를 동적으로 로드하고
그것이 인터페이스에 상속받고 그 인터페이스를 확인 할 수 있는 기능을 확인하면서
스프링의 구조와 동작방식에 대해서 좀더 깊게 연구가 되었습니다.
댓글