А как в 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<? extends TypeElement> 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.<JCTree.JCExpression>nil(),
treeMaker.Select(
treeMaker.Ident(
elementUtils.getName("this")
),
elementUtils.getName("savePacket")
),
List.<JCTree.JCExpression>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. И возможно, напишу ещё третий ответ - про написание плагинов для компилятора.
В Java так просто не делают. Если уж очень надо, то самое лучшее (из того, что пришло мне в голову) - это АОП.
UPDATE: Покажу пример использования АОП с AspectJ. Для наглядности не буду использовать ни внедрения аспектов контейнером, ни связывания Maven'ом, всё ручками.
Аннотация для активации отладочных действий:
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.RUNTIME)
@Target(ElementType.METHOD)
public @interface Debug {}
Жертва эксперимента:
package com.example;
public class SomeServer {
@Debug
public void receivePacket() {
System.out.println("Packet received");
}
public static void main(String[] args) {
SomeServer srv = new SomeServer();
srv.receivePacket();
}
}
Аспект:
package com.example;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.JoinPoint;
aspect DebugAspect {
// Добавляем в класс сервера метод
// выполняющий отладочные действия
private void SomeServer.savePacket() {
System.out.println("Packet saved");
}
// Вызываем добавленный метод при выполнении
// любого метода помеченного аннотацией @Debug
@After("@annotation(Debug) && execution(* *(..))")
public void after(JoinPoint joinPoint) {
SomeServer srv = (SomeServer) joinPoint.getTarget();
srv.savePacket();
}
}
Создаём в каталоге с исходными файлами подкаталог deps и скачиваем в него aspectjrt-1.9.1.jar, aspectjtools-1.9.1.jar и aspectjweaver-1.9.1.jar.
Выполняем сборку со связыванием
$ java -cp deps/* org.aspectj.tools.ajc.Main -d build -source 1.8 -target 1.8 DebugAspect.aj Debug.java SomeServer.java
Запускаем
$ java -cp deps/* com.example.SomeServer
и получаем
Packet received
Packet saved
Если выполнить сборку без связывания (или убрать аннотацию @Debug
), будет выведена только строка "Packet received", несмотря на то, что код остаётся неизменным.
Виртуальный выделенный сервер (VDS) становится отличным выбором
нужно заменить данное выражение на одно replaceAll с несколькими вхождениями, то есть либо , либо </td>, либо <td>
Имеются показания гироскопа и акселерометра, как из них построить ФРТ? ФРТ планируется использовать для деконволюции изображения методом...
Пишу домашку, нужно написать поиск, который будет выбирать границы и искать в той части массива, что может идти не так в моём коде?
На сайте есть 3 ошибки валидации WC3 - как их исправить?