2011-04-28 5 views
6

Java 컴파일러 API를 사용하여 일부 Java 클래스를 컴파일하려고합니다. 그 클래스는, 문맥 ClassLoader에 의해로드 할 수있는 jar 파일로부터 몇개의 패키지를 임포트합니다. 시스템 클래스 로더가 아닌 X를 X라고 부르 자. 컴파일을 실행하면 컴파일러에서 가져 오기를 인식하지 못한다고 불평합니다. classloader를 전달하기 위해 fileManager를 지정하려했지만 도움이되지 않습니다.Java 컴파일러 API ClassLoader

컴파일 메소드가 호출되면, 먼저 "CLASS LOADED"를 인쇄하므로 컨텍스트 ClassLoader가 종속성 클래스를 찾을 수 있습니다. 그러나 컴파일 자체가 실패하고 ("컴파일 실패"메시지가 표시됨) 컴파일 중에 다음과 같은 오류가 발생합니다.

/path/to/my/Source.java:3 : 패키지 my.dependency가 존재하지 않습니다. import my.dependency.MyClass; ^

내가 뭘 잘못하고 있니? 커스텀 클래스 로더를 compilationTask에 전달하는 올바른 방법은 무엇입니까? URLClassLoader가 아니기 때문에 ClassLoader에서 URL을 추출 할 수 없습니다.

내 방법은 여기에 있습니다 :

public void compile(List<File> filesToCompile) { 
     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 

     StandardJavaFileManager stdFileManager = 
       compiler.getStandardFileManager(null, null, null); 
     Iterable<? extends JavaFileObject> fileObjects = stdFileManager 
       .getJavaFileObjectsFromFiles(filesToCompile); 

     FileManagerImpl fileManager = new FileManagerImpl(stdFileManager); 

     CompilationTask task = compiler.getTask(null, fileManager, null, null, null, fileObjects); 
     Boolean result = task.call(); 
     if (result == true) { 
      System.out.println("Compilation has succeeded"); 
     } else { 
      System.out.println("Compilation FAILED"); 
     } 
} 

private final class FileManagerImpl extends ForwardingJavaFileManager<JavaFileManager> { 

     public FileManagerImpl(JavaFileManager fileManager) { 
      super(fileManager); 
     } 

     @Override 
     public ClassLoader getClassLoader(JavaFileManager.Location location) { 
      ClassLoader def = getContextClassLoader(); 
      try { 
       def.loadClass("my.dependency.MyClass"); 
       System.out.println("CLASS LOADED"); 
      } catch (ClassNotFoundException ex) { 
       System.out.println("NOT LOADED"); 
      } 
      return def; 
     } 
} 

답변

0

This question has the answer. getTask() 메소드를 사용하여 옵션 목록을 통해 클래스 경로를 설정해야합니다 (허용 된 답변에 자세히 설명되어 있음).

+1

고마워, 내가 조사해 볼게. 그러나 기존의 classLoader를 설정할 방법이 없습니까? –

+0

@Pavel S. - 컴파일러 도구에 실제 응용 프로그램 클래스 경로와 별개의 클래스 경로를 제공하는 것이 좋습니다. 클래스의 한 버전을로드했지만 클래스를 컴파일하기 위해 다른 버전을 사용하려고한다고 가정 해보십시오. * * 클래스 분리가 필요합니다. –

0

당신이 반사 API를 시도해 볼 수도 다른 jar 파일에서 클래스를로드하는 것은 다음 링크를 http://download.oracle.com/javase/tutorial/reflect/index.html ..refer 쉬운 방법입니다 ..

+0

답변 해 주셔서 감사합니다. 그러나 그것은 내가 필요한 것만은 아닙니다. * .java 소스를 컴파일하고 컴파일러가 종속 패키지를로드하는 방법을 알게해야합니다. –

3

요점은 클래스 로더가 클래스를로드하는 동안 javac는 JavaFileManager#list()을 호출하여 패키지의 모든 파일 목록을 가져옵니다.

따라서 사용자 정의 클래스 로더를 사용하려면이 클래스를 수정 (또는 확장)하여 JavaFileManager#list()을 무시해야합니다. 다행스럽게도 클래스 로딩에 사용 된 로직 중 일부를 재사용 할 수 있기를 바랍니다.

JavaFileObject의 구현을 사용하여 클래스 개체를 나타낼 수 있습니다. 그러면 JavaFileManager#inferBinaryName()을 무시해야합니다 (그렇지 않으면 javac 버전이 중단됩니다). JavaFileObject의 구현은 (최소한) JavaFileObject#openInputStream을 재정의해야합니다.

는 여기에 몇 가지 포인터 : http://atamur.blogspot.be/2009/10/using-built-in-javacompiler-with-custom.html 또한

, 그것은해야하고 ForwardingJavaFileManagerSimpleJavaFileObject을 확장하는 것보다 당신의 인생을 어렵게하지 않습니다.

는 참고로, 여기 예제 구현 : pkgObjectsJavaFileObject에 패키지 이름에서지도가

@Override public Iterable<JavaFileObject> list(Location location, 
    String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) 
    throws IOException 
    { 
    Iterable<JavaFileObject> stdResults = 
     fileManager.list(location, packageName, kinds, recurse); 

    if (location != StandardLocation.CLASS_PATH 
    || !kinds.contains(JavaFileObject.Kind.CLASS)) 
    { 
     return stdResults; 
    } 

    Set<JavaFileObject> additional = pkgObjects.get(packageName); 

    if (additional == null || additional.isEmpty()) { 
     return stdResults; 
    } 

    List<JavaFileObject> out = new ArrayList<>(); 

    for (JavaFileObject obj : additional) { 
     out.add(obj); 
    } 
    for (JavaFileObject obj : stdResults) { 
     out.add(obj); 
    } 

    return out; 
    } 

. 이 맵을 채우는 방법은 클래스 로더의 작동 방식에 따라 다릅니다.