화면에 도형을 그리는 어플리케이션을 만들어 사용자에게 배포하였다고 가정하자. 이미 도형에 대한 클래스 모델링이 잘 되어 있기 때문에 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 파일 이름까지 입력해서 보다 정확한 객체를 생성하도록 한다.

 

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;
    }
}

'Java (+ Spring)' 카테고리의 다른 글

Spring: Spring MVC 설정 하기  (0) 2020.07.26
Android: tutorial page  (0) 2020.07.18
Android: Launch screen  (0) 2020.07.16
Reflection을 활용한 범용 toString() 함수 만들기  (0) 2020.07.08
enum의 활용법 - 계산기  (0) 2020.07.07

+ Recent posts