자바에서 특정 경로에 있는 컴파일된 .class 파일을 동적으로 로드해야 하는 경우, 기본 제공되는 클래스 로더 대신 사용자 정의 ClassLoader를 작성할 수 있다. 이를 위해 ClassLoader 클래스를 상속하고 findClass 메서드를 오버라이드하면 된다.
먼저, 별도의 패키지에 존재하는 테스트용 클래스를 하나 준비한다. 아래는 cn.lijie 패키지 내에 위치한 BugNew 클래스 예시이다.
package cn.lijie;
public class BugNew {
public void display() {
System.out.println("Custom class loaded successfully!");
}
}
이 클래스는 컴파일 후 다음과 같은 경로에 저장된다:
/D:/infosec/netauth/accountsync/BugNew.class
패키지 구조에 맞춰 디렉터리 계층을 유지해야 하며, 이 경우 cn/lijie/BugNew.class 형태로 배치되어야 한다.
다음은 사용자 정의 클래스 로더 구현체이다. 지정된 URI 경로에서 바이트 데이터를 읽어와 defineClass를 통해 클래스 객체를 생성한다.
package com.infosec.manager.controller.login;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class CustomClassLoader extends ClassLoader {
private String classPath = "file:///D:/infosec/netauth/accountsync/BugNew.class";
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
URI uri = new URI(classPath);
Path path = Paths.get(uri);
byte[] classBytes = Files.readAllBytes(path);
// 클래스 이름은 전체 경로 기준으로 정확히 일치해야 함
return defineClass(name, classBytes, 0, classBytes.length);
} catch (IOException | URISyntaxException e) {
throw new ClassNotFoundException("Failed to load class from custom path", e);
}
}
}
이제 이 커스텀 로더를 사용해 외부 클래스를 로드하고 메서드를 실행하는 테스트 코드를 작성한다.
package com.infosec.manager.controller.login;
import java.lang.reflect.Method;
public class LoaderTest {
public static void main(String[] args) {
CustomClassLoader loader = new CustomClassLoader();
try {
// 수동으로 클래스 이름 전달
Class<?> loadedClass = loader.findClass("cn.lijie.BugNew");
Object instance = loadedClass.newInstance();
Method method = loadedClass.getMethod("display");
method.invoke(instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
실행 결과로 "Custom class loaded successfully!"가 출력되면, 외부의 .class 파일이 성공적으로 로드되고 인스턴스화되었음을 의미한다.
추가적으로, 런타임 시 객체가 특정 인터페이스를 구현했는지 확인해야 할 경우 다음처럼 처리할 수 있다.
private boolean hasTargetInterface(Object target) {
if (target instanceof SynchAccountApi || target instanceof SyncUsersFromAppInterface) {
return true;
}
Class<?>[] interfaces = target.getClass().getInterfaces();
return Arrays.stream(interfaces)
.anyMatch(clazz -> clazz.getName().contains("SynchAccountApi"));
}
이 방식은 스프링의 ClassUtils.getAllInterfaces()를 직접 사용하지 않고도 인터페이스 구현 여부를 유연하게 검사할 수 있도록 도와준다.