А как в 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.
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. И возможно, напишу ещё третий ответ - про написание плагинов для компилятора.