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;

    }

}


Posted by 이세영2
,