기술자료

DBMS, DB 구축 절차, 빅데이터 기술 칼럼, 사례연구 및 세미나 자료를 소개합니다.

javax.tools를 이용한 동적 애플리케이션 생성

기술자료
DBMS별 분류
Etc
작성자
dataonair
작성일
2008-10-17 00:00
조회
2927



javax.tools를 이용한 동적 애플리케이션 생성

동적 애플리케이션 생성을 위한 javax.tools.JavaCompiler의 이해와 적용

GUI에서 사용자가 임의의 계산식을 입력할 수 있는 애플리케이션처럼, 오늘날 많은 애플리케이션은 정적 기능 이상의 동적 기능을 요구합니다. javax.tools 패키지가 자바 소스 컴파일을 위한 표준 API로 자바 SE(Java™ Platform Standard Edition) 6에 포함되었습니다. 이는 동적 기능 제공을 위한 제법 괜찮은 도구입니다. 이 글에서는 이 패키지의 주요 클래스를 소개하고, 파일 대신 자바 String 에 저장된 자바 소스를 컴파일하기 위한 라이브러리를 작성하는 방법을 제시합니다. 끝으로, 이 라이브러리를 활용해 대화형 그래프 애플리케이션을 구현해 봅니다.

개요

자바 SE 6에는 자바 소스를 컴파일하기 위한 표준 API인 javax.tools 패키지가 추가됐다. 이 패키지를 이용하면 정적 애플리케이션을 확장해 동적 기능을 추가할 수 있다. 본 글에서는 이 패키지의 주요 클래스를 소개하고, 파일 대신 자바 String , StringBuffer , 또는 CharSequence 에 저장된 자바 소스를 컴파일하기 위한 라이브러리를 작성하는 방법을 제시한다. 그리고 이 라이브러리를 활용해 사용자가 자바 문법에 따라 y = f( x )형태의 수식을 입력할 수 있는 대화형 그래프 애플리케이션을 구현해 본다. 끝으로 동적 소스 컴파일 관련 보안 위험 요소를 살펴 보고, 이러한 위험 요소를 해소하는 수단을 다루는 것으로 글을 맺겠다

자바 구문을 컴파일하고 로딩하여 애플리케이션을 확장하는 아이디어는 그리 새로운 것이 아니다. 이미 여러 기존 프레임워크에서 이런 기능을 제공한다. 자바 EE(Java Platform, Enterprise Edition)의 JSP(JavaServer Pages) 기술은 자바 소스를 생성하고 클래스로 컴파일하는, 동적 프레임워크의 잘 알려진 예이기도 하다. JSP 번역기가 .jsp 파일을 자바 서블릿으로 변환할 때, 임시 생성한 소스 코드를 컴파일해 자바 EE 서블릿 컨테이너로 로딩한다. 소스 컴파일 시, JDK에 포함된 javac 컴파일러를 직접 호출하거나, 썬이 제공하는 tools.jar에 포함된 com.sun.tools.javac.Main 을 호출한다. 썬의 라이선스는 tools.jar를 JRE(Java Runtime Environment)와 함께 배포하는 것을 허용한다. 그 외에, 자바와 통합할 수 있는 자바스크립트나 그루비(Groovy) 같은 동적 스크립트 언어를 이용하거나, 목적에 맞는 언어를 정의하고 해석기나 컴파일러를 따로 구현하는 방법 등이 있다.

넷빈즈(NetBeans)와 이클립스(Eclipse) 같은 다른 프레임워크에서는 자바 언어로 직접 코딩해 확장할 수 있다. 그러나 이런 시스템에서는 외부에서 정적으로 컴파일해야 하며, 자바 코드와 관련 파일의 소스와 바이너리를 함께 관리해야 한다. Apache Commons JCI는 실행 중인 애플리케이션에서 소스를 컴파일해 클래스를 로딩하는 메커니즘을 제공한다. Janino와 Javassist 역시 유사 기능을 제공하지만, Janino는 자바 1.4 이전 언어만 지원하는 한계가 있고, Javassist는 소스를 다루지 못하고 클래스만 조작할 수 있는 제약이 있다. 그러나 자바 개발자는 이미 자바로 코드를 작성하는 데 익숙하므로 즉석에서 자바 소스 코드를 생성하여 컴파일하고 로딩하는 시스템을 사용하는 편이, 학습 시간을 단축하는 데도 유리하고 최상의 유연성 또한 보장할 수 있다.

javax.tools 사용의 이점

javax.tools 를 사용하면 다음의 이점이 있다.

  • 이는 자바 SE의 공인된 확장으로, JSR(Java Community Process) 199를 통해 표준화되었다. com.sun.tools.javac.Main API는 문서화된 자바 플랫폼 API가 아니며 다른 업체의 JDK에서는 제공하지 않을 수도 있고, 썬 JDK의 향후 배포에서 같은 API를 제공하리라는 보장도 없다.

  • 아는 것, 즉 바이트코드가 아닌 자바 소스를 사용할 수 있다. 문법에 맞는 자바 소스를 작성하여 정확한 자바 클래스를 생성할 수 있다. 클래스, 메서드, 구문, 표현식 등 복잡하게 얽힌 바이트코드와 객체 모델의 규칙을 따로 학습할 필요가 없다.

  • 이는 코드 생성과 로딩을 위한 단순하고 표준화된 단일 메커니즘이다. 굳이 자바 소스를 파일에 생성할 필요도 없다.

  • 이는 검증된 버전의 자바 컴파일러를 사용한다.

  • 인터프리터 방식 시스템과 달리, 로딩한 클래스는 JRE의 런타임 최적화 기법을 활용할 수 있다.





자바 컴파일: 개념과 구현

javax.tools 패키지를 이해하기 앞서, 자바 컴파일의 개념과 컴파일의 구현 방식을 복습하면 좋겠다. javax.tools 패키지는 자바 컴파일의 모든 개념을 추상화하여, 파일 시스템이 아닌 다른 위치에 저장된 소스 코드도 처리할 수 있는 일반적인 수단을 제공한다.

javax.tools에서 자바 소스를 컴파일할 때 다음 요소가 필요하다.

  • 클래스경로(classpath) 는 컴파일러가 클래스 라이브러리를 얻을 수 있는 경로다. 전형적인 컴파일러 클래스경로는 먼저 컴파일한 클래스 파일을 담고 있는 파일 시스템 디렉터리와 JAR 또는 ZIP 파일 같은 압축 파일을 순차적으로 열거한 목록으로 구성된다. javax.tools에서 클래스경로는 JavaFileManager 와 ClassLoader 로 구현한다. JavaFileManager 는 다수의 소스와 클래스파일에 해당하는 JavaFileObject 객체를 관리한다. ClassLoader 는 JavaFileManager 의 생산자로 전달한다. JavaFileObject 는 FileObject 를 상속한 것으로, 컴파일러가 요구하는 파일 객체를 가리킨다. JavaFileObject 가 JavaFileObject.Kind 로 구별하는 파일 유형은 다음과 같다.
    • SOURCE
    • CLASS
    • HTML
    • OTHER
    각 소스 파일은 openInputStream() 메서드를 제공하므로 InputStream 으로 소스를 얻을 수 있다.

  • javac 옵션 은 Iterable 의 형태로 넘긴다.

  • 소스 파일 컴파일할 하나 이상의 소스 파일을 의미한다. JavaFileManager 는 소스와 파일명을 JavaFileObject 객체로 매핑할 수 있도록 추상화된 파일 시스템을 제공한다. (여기서, 파일 이란 고유한 이름과 바이트열 간의 관계를 의미한다. 클라이언트는 실제 파일 시스템을 사용하지 않아도 무방하다.) 이 글의 예제에서는 JavaFileManager 가 클래스 이름과 컴파일할 자바 소스가 저장된 CharSequence 간 매핑을 관리한다. JavaFileManager.Location 에는 파일 이름 및 그 위치가 소스 또는 출력 용도인지를 구분하는 플래그가 있다. ForwardingJavaFileManager 에는 Chain of Responsibility 패턴을 구현했다. 따라서 클래스경로와 소스 경로가 다수의 JAR와 디렉터리를 함께 연결하듯, JavaFileManager 객체를 서로 연결할 수 있다. 즉, 자바 클래스를 첫 연결 고리에서 찾지 못할 경우, 남은 연결 고리에서 찾는다.

  • 출력 디렉터리 는 컴파일러가 클래스 파일을 생성하는 위치를 가리킨다. JavaFileManager 를 생성한 클래스 파일의 목록으로 사용하여, 컴파일한 CLASS 파일에 해당하는 JavaFileObject 객체를 여기에 저장한다.

  • 컴파일러 자체 인 JavaCompiler 는 JavaCompiler.CompilationTask 객체를 생성한다. 이 객체는 JavaFileManager 내에 JavaFileObject 의 SOURCE 타입 객체로부터 소스를 컴파일하여, 새로운 출력물로 JavaFileObject 의 CLASS 타입 객체와 Diagnostic (경고와 에러)을 생성한다. ToolProvider.getSystemJavaCompiler() 메서드는 컴파일러 객체를 돌려준다.

  • 컴파일러 경고와 에러 는 Diagnostic 과 DiagnosticListener 로 구현된다. Diagnostic 은 컴파일러가 출력하는 경고 또는 컴파일 에러 하나를 가리킨다. Diagnostic 은 다음을 나타낸다.
    • Kind ( ERROR , WARNING , MANDATORY_WARNING , NOTE , OTHER 중 하나)
    • 소스 위치(라인과 칼럼 번호 포함)
    • 메세지
    클라이언트가 컴파일러에 DiagnosticListener 를 제공하면, 컴파일러는 클라이언트로 오류를 돌려 준다. DiagnosticCollector 는 간단히 DiagnosticListener 를 구현한 것이다.

그림 1은 javac 의 개념과 javax.tools 구현간의 매핑을 나타낸다.


그림 1. javac 개념과 javax.tools 인터페이스간 상관 관계도
081017_dsf1.gif

이러한 개념을 염두에 두고서, CharSequence 에 저장된 자바 소스를 컴파일하기 위한 라이브러리를 작성하는 방법을 살펴보자.






CharSequence 객체에서 자바 소스 컴파일

본 절에서는 javax.tools.JavaCompiler 를 사용하는 라이브러리를 작성한다. javaxtools.compiler.CharSequenceCompiler 클래스( 다운로드 참조)는 String , StringBuffer , StringBuilder 같은 임의의 java.lang.CharSequence 에 저장된 자바 소스를 컴파일하여 Class 하나를 돌려준다. CharSequenceCompiler 는 다음 API를 가진다.

  • public CharSequenceCompiler(ClassLoader loader, Iterable options) : 본 생성자는 자바 컴파일러로 전달하기 위한 ClassLoader 를 인자로 받는다. 이 ClassLoader를 통해 컴파일러는 필요한 클래스를 읽는다. Iterable options 인자로 javac 옵션에 해당하는 추가적인 컴파일러 옵션을 전달할 수 있다.

  • public Map> compile(Map classes, final DiagnosticCollector diagnostics) throws CharSequenceCompilerException, ClassCastException : 이는 다수의 소스를 한 번에 컴파일하기 위한 일반적인 컴파일 메서드다. A.java는 B.java에 의존하며, B.java는 C.java에 의존하고, 다시 C.java는 A.java에 의존하는 식의 클래스간 순환 그래프를 자바 컴파일러는 처리해야 한다. 이 메서드의 첫 인자는 Map 인데, 전체 클래스 이름(fully qualified class name)이 키가 되고 소스 코드가 저장된 CharSequence 가 값이 된다. 다음 예를 보자.
    • "mypackage.A" ⇒ "package mypackage; public class A { ... }";
    • "mypackage.B" ⇒ "package mypackage; class B extends A implements C { ... }";
    • "mypackage.C" ⇒ "package mypackage; interface C { ... }"
    컴파일 중에 Diagnostic 은 DiagnosticCollector 인자에 추가된다. 제네릭 타입 인자 T 는 생성한 클래스를 타입 변환하기 위한 타입이다. CharSequenceCompiler는 단일 클래스 이름과 컴파일할 CharSequence 를 인자로 취하는 다른 compile() 메서드도 제공한다.

  • public ClassLoader getClassLoader() : 본 메서드는 클래스 파일 생성 시 컴파일러가 사용할 클래스로더를 돌려 준다. 이 클래스로더를 통해 다른 클래스나 자원을 로딩할 수 있다.

  • public Class loadClass(final String qualifiedClassName) throws ClassNotFoundException : compile() 메서드가 중첩 클래스(nested class)를 비롯한 여러 클래스를 요구하면, 본 메서드에서 이러한 보조 클래스를 로딩한다

CharSequenceCompiler API에 필요한 javax.tools 의 인터페이스를 JavaFileObjectImpl ( CharSequence 소스와 컴파일러가 내놓는 CLASS 출력을 저장하는 목적)과 JavaFileManagerImpl (이름과 JavaFileObjectImpl 객체를 매핑하여, 소스 목록과 컴파일러가 내놓은 바이트코드를 함께 관리하기 위한 목적)로 구현했다.

JavaFileObjectImpl

Listing 1 에 소개된 JavaFileObjectImpl 은 JavaFileObject 를 구현한 것으로 CharSequence 소스( SOURCE 타입) 또는 ByteArrayOutputStream 바이트코드(바이트코드 CLASS 타입)를 저장한다. 핵심 메서드는 CharSequence getCharContent(final boolean ignoreEncodingErrors) 이며, 이 메서드를 통해 컴파일러는 소스 텍스트를 얻는다. 코드 예제의 전체 소스는 다운로드 를 참조하라


Listing 1. JavaFileObjectImpl (소스 일부)
final class JavaFileObjectImpl extends SimpleJavaFileObject {
private final CharSequence source;
JavaFileObjectImpl(final String baseName, final CharSequence source) {
super(CharSequenceCompiler.toURI(baseName + ".java"), Kind.SOURCE);
this.source = source;
}
@Override
public CharSequence getCharContent(final boolean ignoreEncodingErrors)
throws UnsupportedOperationException {
if (source == null)
throw new UnsupportedOperationException("getCharContent()");
return source;
}
}

FileManagerImpl

Listing 2의 FileManagerImpl 은 ForwardingJavaFileManager 를 확장한 것으로, 전체 클래스 이름과 JavaFileObjectImpl 객체를 매핑한다.


Listing 2. FileManagerImpl (소스 일부)
final class FileManagerImpl extends ForwardingJavaFileManager {
private final ClassLoaderImpl classLoader;
private final Map fileObjects
= new HashMap();
public FileManagerImpl(JavaFileManager fileManager, ClassLoaderImpl classLoader) {
super(fileManager);
this.classLoader = classLoader;
}
@Override
public FileObject getFileForInput(Location location, String packageName,
String relativeName) throws IOException {
FileObject o = fileObjects.get(uri(location, packageName, relativeName));
if (o != null)
return o;
return super.getFileForInput(location, packageName, relativeName);
}
public void putFileForInput(StandardLocation location, String packageName,
String relativeName, JavaFileObject file) {
fileObjects.put(uri(location, packageName, relativeName), file);
}
}

CharSequenceCompiler

ToolProvider.getSystemJavaCompiler()가 JavaCompiler를 생성하지 못한다면

tools.jar가 애플리케이션의 클래스패스에 없으면, ToolProvider.getSystemJavaCompiler() 는 null 을 돌려 줄 수 있다. CharStringCompiler 클래스가 이러한 설정상의 문제를 탐지해 문제를 해결하도록 예외를 던진다. 썬의 라이선스에서 JRE와 함께 tools.jar를 재배포할 수 있음을 기억하자.

이상 클래스와 더불어 이제 CharSequenceCompiler 를 정의한다. 이는 런타임 ClassLoader 와 컴파일러 옵션으로 생성한다. 이는 ToolProvider.getSystemJavaCompiler() 를 써서 JavaCompiler 객체를 얻고, 컴파일러의 표준 파일 관리자로 전달할 JavaFileManagerImpl 을 생성한다.

compile() 메서드는 입력 맵을 돌면서 각 name/ CharSequence 에서 JavaFileObjectImpl 을 생성하여 JavaFileManager 에 추가한다. 따라서 JavaCompiler 는 파일관리자의 getFileForInput() 메서드를 호출할 때 이들을 찾을 수 있다. 이제 compile() 메서드는 JavaCompiler.Task 객체를 생성하고 실행한다. 컴파일 실패는 CharSequenceCompilerException 으로 던진다. 이렇게 각 compile() 메서드에 전달한 소스는 Class 로 로드되어 결과 Map 에 저장된다.

Listing 3에서 보는 바와 같이 CharSequenceCompiler 와 연계된 클래스로더는 JavaFileManagerImpl 객체에서 클래스의 바이트코드를 찾는 ClassLoaderImpl 객체이며, 이 클래스로더는 본 컴파일러가 생성한 클래스 파일을 돌려 준다.


Listing 3. CharSequenceCompiler (소스 일부)
public class CharSequenceCompiler {
private final ClassLoaderImpl classLoader;
private final JavaCompiler compiler;
private final List options;
private DiagnosticCollector diagnostics;
private final FileManagerImpl javaFileManager;
public CharSequenceCompiler(ClassLoader loader, Iterable options) {
compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
throw new IllegalStateException(
"Cannot find the system Java compiler. "
+ "Check that your class path includes tools.jar");
}
classLoader = new ClassLoaderImpl(loader);
diagnostics = new DiagnosticCollector();
final JavaFileManager fileManager = compiler.getStandardFileManager(diagnostics,
null, null);
javaFileManager = new FileManagerImpl(fileManager, classLoader);
this.options = new ArrayList();
if (options != null) {
for (String option : options) {
this.options.add(option);
}
}
}
public synchronized Map>
compile(final Map classes,
final DiagnosticCollector diagnosticsList)
throws CharSequenceCompilerException, ClassCastException {
List sources = new ArrayList();
for (Entry entry : classes.entrySet()) {
String qualifiedClassName = entry.getKey();
CharSequence javaSource = entry.getValue();
if (javaSource != null) {
final int dotPos = qualifiedClassName.lastIndexOf('.');
final String className = dotPos == -1
qualifiedClassName
: qualifiedClassName.substring(dotPos + 1);
final String packageName = dotPos == -1
""
: qualifiedClassName .substring(0, dotPos);
final JavaFileObjectImpl source =
new JavaFileObjectImpl(className, javaSource);
sources.add(source);
javaFileManager.putFileForInput(StandardLocation.SOURCE_PATH, packageName,
className + ".java", source);
}
}
final CompilationTask task = compiler.getTask(null, javaFileManager, diagnostics,
options, null, sources);
final Boolean result = task.call();
if (result == null || !result.booleanValue()) {
throw new CharSequenceCompilerException("Compilation failed.",
classes.keySet(), diagnostics);
}
try {
Map> compiled =
new HashMap>();
for (Entry entry : classes.entrySet()) {
String qualifiedClassName = entry.getKey();
final Class newClass = loadClass(qualifiedClassName);
compiled.put(qualifiedClassName, newClass);
}
return compiled;
} catch (ClassNotFoundException e) {
throw new CharSequenceCompilerException(classes.keySet(), e, diagnostics);
} catch (IllegalArgumentException e) {
throw new CharSequenceCompilerException(classes.keySet(), e, diagnostics);
} catch (SecurityException e) {
throw new CharSequenceCompilerException(classes.keySet(), e, diagnostics);
}
}
}






그래프 애플리케이션

이제 소스 컴파일 용도의 간단한 API를 가졌으니, 이 API를 활용해 함수 그래프를 그리는 스윙 기반 애플리케이션을 구현해 보겠다. 그림 2는 애플리케이션이 x * sin(x) * cos(x) 함수 그래프를 그린 것이다.


그림 2. javaxtools.compiler 패키지를 사용한 동적 애플리케이션
081017_dsf2.jpg

이 애플리케이션은 Listing 4에 정의한 Function 인터페이스를 사용한다.


Listing 4. Function 인터페이스
package javaxtools.compiler.examples.plotter;
public interface Function {
double f(double x);
}

이 애플리케이션이 제공하는 텍스트필드에 사용자가 자바 수식을 입력할 수 있다. 이 때, 수식은 double x 가 입력 인자로 선언되어 있다고 가정하고, 이 인자를 써서 double 값을 돌려 주어야 한다. 이 애플리케이션은 Listing 5에 제시한 코드 템플릿에서 $expression 이라고 적힌 위치에 수식 텍스트를 삽입한다. 또한, 템플릿의 $className 을 치환하여 매번 새로운 클래스 이름을 생성한다. 패키지 이름 또한 템플릿 변수다.


Listing 5. Function 템플릿
package $packageName;
import static java.lang.Math.*;
public class $className
implements javaxtools.compiler.examples.plotter.Function {
public double f(double x) {
return ($expression) ;
}
}

애플리케이션은 fillTemplate(packageName, className, expr) 을 통해 템플릿을 채운다. 이 메서드는 String 객체를 돌려 주어 CharSequenceCompiler 를 써서 컴파일한다. 예외나 컴파일 오류는 log() 메서드로 전달하거나 애플리케이션의 errors 스크롤 콤포넌트에 직접 쓴다.

Listing 6의 newFunction() 메서드는 Function 인터페이스를 구현한 객체를 돌려준다( Listing 5 의 소스 템플릿을 보라).


Listing 6. Plotter 클래스의 Function newFunction(String expr) 메서드
Function newFunction(final String expr) {
errors.setText("");
try {
// generate semi-secure unique package and class names
final String packageName = PACKAGE_NAME + digits();
final String className = "Fx_" + (classNameSuffix++) + digits();
final String qName = packageName + '.' + className;
// generate the source class as String
final String source = fillTemplate(packageName, className, expr);
// compile the generated Java source
final DiagnosticCollector errs =
new DiagnosticCollector();
Class compiledFunction = stringCompiler.compile(qName, source, errs,
new Class< >[] { Function.class });
log(errs);
return compiledFunction.newInstance();
} catch (CharSequenceCompilerException e) {
log(e.getDiagnostics());
} catch (InstantiationException e) {
errors.setText(e.getMessage());
} catch (IllegalAccessException e) {
errors.setText(e.getMessage());
} catch (IOException e) {
errors.setText(e.getMessage());
}
return NULL_FUNCTION;
}

일반적으로 소스 코드를 생성할 때는, 이미 알려진 베이스 클래스를 확장하거나 특정 인터페이스를 구현하게 한다. 이렇게 새로 생성한 객체는 그 베이스 클래스 또는 인터페이스 타입으로 변환하여, 그 타입의 알려진 API를 통해 메서드를 호출할 수 있다. CharSequenceCompiler 를 생성할 때 제네릭 타입 인자 T 로 Function 클래스를 사용한 것을 주의하라. 이렇게 하여 compiledFunction 을 Class 으로 선언할 수도 있고 compiledFunction.newInstance() 는 별도의 타입 변환 없이 Function 객체를 돌려준다.

동적으로 생성된 Function 객체에서 특정범위의 x 를 따라 y 값을 생성한다. 그리고 오픈 소스 JFreeChart API를 써서 ( x , y ) 값의 그래프를 그린다. 스윙 애플리케이션의 전체 소스는 다운로드 의 javaxtools.comiler.examples.plotter 패키지에 있다.

본 애플리케이션에서는 단순 필요에 의해 소스 코드를 생성하였다. 다른 애플리케이션에서는 Apache Velocity와 같은 더욱 섬세한 소스 템플릿 기능을 활용할 수 있다.





보안 위험과 전략

사용자가 임의의 자바 소스를 입력할 수 있는 애플리케이션은 잠재적인 보안 위험 요소를 안는다. SQL 주입(injection)과 비슷하게, 자바 소스를 입력할 수 있는 시스템을 사용자나 다른 에이전트에서 악성 코드 생성 목적으로 악용할 수 있다. 예를 들어 여기 예시한 Plotter 애플리케이션에서도 무명의 중첩 클래스(anonymous nested class)를 정상적인 자바 수식에 포함하여 시스템 자원에 접근, 서비스 거부(denial-of-service) 공격을 위한 스레드 분기, 그 외 다른 악의적인 코드 수행이 가능하다. 이런 악용 방식을 자바 주입( Java injection )이라 부른다. 서블릿 형태의 자바 EE 서버나 애플릿 등의 경우에서, 신뢰할 수 없는 사용자가 접근할 수 있는 보안 위험 지역에 이런 류의 애플리케이션을 설치하면 안 된다. javax.tools 를 사용하는 클라이언트는 사용자 입력을 제한하고 사용자 요구를 안전한 소스 코드로 변환해야 한다.

이 패키지를 사용할 때 보안을 유지하기 위한 전략은 다음과 같다.

  • 무명의 클래스 또는 직접 제어할 수 없는 다른 클래스를 로딩할 수 없도록, 맞춤식 SecurityManager 나 ClassLoader 를 사용하라.

  • 의심스러운 코드를 생성하려는 입력은 버리는 소스 코드 스캐너 또는 전처리기를 사용하라. 예를 들어, Plotter 애플리케이션은 java.io.StreamTokenizer 를 써서 여는 괄호 { 가 포함된 입력은 버린다. 이런 식으로 무명의 중첩 클래스 선언을 효과적으로 차단한다.

  • JavaFileManager 에서 예상치 못한 CLASS 파일의 기록을 버릴 수 있다. 예를 들어, 한 클래스를 컴파일하면서 예상치 못한 클래스를 함께 저장하려 하면, JavaFileManager 에서 SecurityExeception 을 던지도록 한다. 가급적, 사용자가 예측할 수 없는 패키지 또는 클래스 이름을 생성하도록 한다. Plotter 애션리케이션의 newFunction 에서 이러한 전략을 사용했다.





결론

지금까지 javax.tools 패키지의 개념과 주요 인터페이스를 설명하고, String 이나 다른 CharSequence 에 저장된 자바 소스를 컴파일하는 라이브러리를 작성했다. 그리고 이 라이브러리로 임의의 f( x ) 함수 그래프를 그리는 샘플 애플리케이션을 구현했다. 본 기술의 다른 유용한 적용 예는 다음과 같다.

  • 임의 데이터 기술 언어로부터 바이너리 파일을 읽고 쓰는 코드를 생성

  • JAXB(Java Architecture for XML Binding) 또는 Persistence Framework와 비슷한 포맷 변환 코드 생성

  • JSP와 같이 다른 언어를 자바 소스로 해석하고 컴파일해 로딩하는, 분야별 특화 언어 전용 해석기 구현

  • 규칙 엔진(rule engine) 구현

  • 그 외 개발자의 상상력이 허용하는 모든 곳

앞으로 애플리케이션 개발에서 동적 기능이 필요하다면, javax.tools 에서 다양성과 유용성을 살려 보라.







다운로드 하십시오

설명
이름
크기
다운로드 방식
이 기사의 예제 코드 j-jcomp.zip 166KB HTTP

필자소개

081017_dsf.jpg David Biesack

David Biesack은 SAS Institute사의 Advanced Computing 연구소에서 수석 시스템 개발자로 일하면서, 고등 해석학과 분산 처리를 연구하는 중이다. 그는 SAS에서 19년 동안 근무하면서, 그 중 12년을 자바 언어로 시스템을 디자인하고 개발하였다. 또한, JSR 201에 참여하여 자바 5에 여러 새로운 문법을 추가했다.