Страницы

Поиск по вопросам

вторник, 15 января 2019 г.

Условная компиляция

А как в Java реализуется условная компиляция?
Скажем, у меня есть TCP сервер. Мне нужно на этапе отладки складывать в файл пришедшие пакеты. Потом, в релиз версии, этот функционал нужно будет отключить.
Как такое принято реализовывать? В Си это делается так
#ifdef DEBUG savePacket(); #endif
а в Java?


Ответ

Есть ещё один способ эмулировать макросы препроцессора - процессоры аннотаций + Java Compiler API. Этот способ сложнее, но не требует дополнительных библиотек и не выполняет никаких дополнительных действий в рантайме, все изменения в код вносятся в процессе его компиляции, между этапом разбора исходного текста и преобразования его в байткод.
Аннотация для активации отладочных действий:
package com.example;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE) // Аннотация существует только до компиляции @Target(ElementType.METHOD) public @interface Debug { // Чтобы отключать аннотацию, не убирая её // но можно и без этого boolean value() default true; }
Жертва эксперимента:
import com.example.Debug;
public class SomeServer { private void savePacket() { System.out.println("Packet saved"); }
@Debug private void receivePacket() { System.out.println("Packet received"); }
public static void main(String[] args) { SomeServer srv = new SomeServer(); srv.receivePacket(); } }
Процессор
package com.example;
import com.sun.tools.javac.model.JavacElements; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic;
@SupportedOptions("debug") @SupportedAnnotationTypes("com.example.Debug") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class DebugProcessor extends AbstractProcessor { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) return false;
// Получаем параметр указывающий на отладочную сборку boolean enabled = Boolean.parseBoolean( processingEnv.getOptions().getOrDefault("debug", "false"));
Context ctx = ((JavacProcessingEnvironment) processingEnv).getContext(); JavacElements elementUtils = (JavacElements) processingEnv.getElementUtils(); TreeMaker treeMaker = TreeMaker.instance(ctx);
// Обходим методы помеченные аннотацией @Debug for (Element element : roundEnv.getElementsAnnotatedWith(Debug.class)) { Debug debug = element.getAnnotation(Debug.class);
if (debug.value() && enabled) { JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) elementUtils.getTree(element);
// И добавляем в конец метода вызов другого метода treeMaker.pos = jcMethodDecl.pos; jcMethodDecl.body = treeMaker.Block(0, List.of( jcMethodDecl.body, treeMaker.Exec( treeMaker.Apply( List.nil(), treeMaker.Select( treeMaker.Ident( elementUtils.getName("this") ), elementUtils.getName("savePacket") ), List.nil() ) ) )); } }
return false; } }
Собираем аннотацию
$ javac -d build Debug.java
Собираем процессор
$ javac -cp build:"$JAVA_HOME/lib/tools.jar" -d build DebugProcessor.java
А теперь собираем SomeServer с использованием процессора
$ javac -cp build -processor com.example.DebugProcessor -Adebug=true SomeServer.java
Или можно собрать сервисный jar и положить в classpath, чтобы процессор использовался автоматически. Для этого DebugProcessor.class надо упаковать в jar вместе с файлом META-INF/services/javax.annotation.processing.Processor содержащим строку com.example.DebugProcessor. Тогда при сборке останется только указывать параметр -Adebug=true, когда нужны отладочные действия, и просто не указывать в другом случае.
P.S. По-хорошему надо было ещё метод savePacket тоже генерировать на лету, но я несколько притомился. Может дополню ответ в другой день. P.P.S. И возможно, напишу ещё третий ответ - про написание плагинов для компилятора.

Комментариев нет:

Отправить комментарий