Java 라이브러리(.jar) 동적 로딩
dynamicJarLoader.zip
라이브러리에 대한 동적 로딩은 사용자의 요구가 다양하게 변화하는 어플리케이션을 위한 기술이다. Java에서는 URLClassLoader라는 라이브러리 클래스를 통해서 이 기능을 지원하고 있다.
이를 이용할 상황을 하나 생각해 보도록 하자.
화면에 도형을 그리는 어플리케이션을 만들어 사용자에게 배포하였다고 가정하자. 이미 도형에 대한 클래스 모델링이 잘 되어 있기 때문에 Shape이라는 인터페이스는 이미 배포되어 있다. 그리고 몇몇 도형들 역시 배포된 상태다. 소프트웨어 내부에서는 이미 배포된 jar 파일을 통해 도형들을 생성해서 사용하고 있다.
이 상황에서 고객이 새로운 도형을 만들어 달라고 요청해 왔다. 필요한 도형은 Circle과 Triangle이다. 이 파일들은 개발자에 의해서 개발되었고 사용자에게 배포되었다. 이 때 이미 동작 중인 어플리케이션은 종료할 수 없다. 이런 경우에 어플리케이션을 중단시키지 않아도 클래스들을 로드할 수 있는 동적 로더가 활용될 수 있다.
아래 클래스 다이어그램을 보자.
위에서 설명했듯이 Shape 인터페이스는 이미 배포된 상태이다. 여기서 사용자가 원하는 Circle과 Triangle을 각각의 이름으로 된 jar 파일을 배포하였다.
여기서 사용자가 어플리케이션을 종료하지 않고 이들 jar 파일들을 로드하려면 동적 Jar 로드 기능이 탑재되어 있어야 한다. 이를 구현한 것이 DynamicJarLoader 클래스이다.
DynamicJarLoader 클래스 설명
이 클래스는 객체 생성자를 통해서 Jar 파일이 들어 있는 폴더 경로(String jarPath)를 입력 받는다.
이후 load(jarFileName : String) 메소드를 통해서 Jar 파일 이름을 입력해주면 라이브러리가 로드된다.
특히 DynamicJarLoader 클래스는 내부에 있는 loaderMap을 통해서 여러 이름의 라이브러리를 동시에 로드할 수 있다. 따라서 이 객체 하나 만으로도 여러 라이브러리 파일을 로드하고, 로드된 라이브러리들을 통해서 객체를 생성할 수 있다. 리턴되는 boolean 값은 라이브러리 로드가 성공했는지 여부를 알려준다.
unload(jarFileName : String) 메소드는 이미 로드된 라이브러리를 메모리에서 내리거나, 새로운 버전의 라이브러리를 열기 위해서 기존에 이미 열려진 라이브러리를 닫아야 할 경우에 호출하는 메소드이다.
이미 라이브러리가 로드되어 있다면 열린 라이브러리를 통해서 객체를 새로 생성할 수 있어야 한다. newInstance() 메소드는 클래스 이름(className)을 통해서 객체를 로드할 수 있도록 구현된 메소드이다. newInstance() 메소드는 두 개가 있는데, 하나는 className만으로 객체를 생성하도록 하고, 나머지 하나는 jar 파일 이름까지 입력해서 보다 정확한 객체를 생성하도록 한다.
첨부파일
맨 위쪽에 첨부된 첨부파일은 shape.jar / circle.jar / triangle.jar 파일과 DynamicJarLoader 클래스, 그리고 이들 라이브러리 동적 로딩을 테스트해 볼 수 있는 Use 클래스를 포함하고 있는 eclipse 프로젝트 파일이다.(JDK 1.7 이상을 사용하기 바란다.)
아래는 DynamicJarLoader 클래스의 소스이다.
public class DynamicJarLoader {
private String jarPath;
private Map<String, URLClassLoader> loaderMap = new HashMap<String, URLClassLoader>();
public DynamicJarLoader(String jarPath){
this.jarPath = jarPath;
this.jarPath.replaceAll("\\\\", "/");
if(this.jarPath.endsWith("/") == false) this.jarPath = this.jarPath + "/";
}
public boolean load(String jarFileName){
if(loaderMap.containsKey(jarFileName) == true) unload(jarFileName);
String jarFilePath = jarPath + jarFileName;
File jarFile = new File(jarFilePath);
try {
URL classURL = new URL("jar:" + jarFile.toURI().toURL() + "!/");
URLClassLoader classLoader = new URLClassLoader(new URL [] {classURL});
loaderMap.put(jarFileName, classLoader);
return true;
} catch (MalformedURLException e) {
return false;
}
}
public boolean unload(String jarFileName){
URLClassLoader loader = loaderMap.get(jarFileName);
if(loader == null) return true;
try {
loader.close();
return true;
} catch (IOException e) {
return false;
}
finally{
loaderMap.remove(jarFileName);
}
}
public Object newInstance(String jarFileName, String className){
URLClassLoader loader = loaderMap.get(jarFileName);
if(loader == null) return true;
try {
Class<?> clazz = loader.loadClass(className);
return clazz.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
return null;
}
}
public Object newInstance(String className){
for(String each : loaderMap.keySet()){
Object object = newInstance(each, className);
if(object != null) return object;
}
return null;
}
}