DEV Community

Salad Lam
Salad Lam

Posted on

Quarkus extension

There is 2 files for a extension, for example Liquibase

  • quarkus-liquibase-3.17.7.jar
  • quarkus-liquibase-deployment-3.17.7.jar

File quarkus-liquibase is the runtime module and quarkus-liquibase-deployment is deployment module, and is used during the augmentation phase of the build.

Deployment module

  • contain classes (build step processor) of methods which annotated with @BuildStep (build step)

Runtime module

  • contain runtime and Recorder classes

Subclass of io.quarkus.builder.item.BuildItem

  • final and immutable class
  • created by @BuildStep, store information for other @BuildStep

io.quarkus.deployment.annotations.BuildStep annotation

  • produce BuildItems (e.g. io.quarkus.jdbc.h2.deployment.JDBCH2Processor#feature)
  • consume BuildItems and then produce BuildItems (e.g. io.quarkus.jdbc.h2.deployment.JDBCH2Processor#registerDriver)
  • consume BuildItems (e.g. io.quarkus.deployment.steps.ShutdownListenerBuildStep#setupShutdown)
  • accept BuildItem, List, BuildProducer, Config or Recorder as parameters
  • translation, create something
  • if @Record is annotated also, proxy recorder provided is also

Classes contain BuildSteps are listed in META-INF/quarkus-build-steps.list

Recorder example

Following is a sample code to demonstrate bytecode generation during the augmentation phase

Recorder, Processor and BuildItem class

@Recorder
public class MyMessageRecorder {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyMessageRecorder.class);

    // will not be call in buildtime augmentation
    public void sayHello(String message) {
        LOGGER.info("Hello {}!", message);
    }

}

public class MyMessageProcessor {

    @BuildStep
    @Record(ExecutionTime.RUNTIME_INIT)
    @Consume(MyMessageBuildItem.class)
    public void printMessage(MyMessageRecorder recorder, MyMessageBuildItem myMessageBuildItem) {
        recorder.sayHello(myMessageBuildItem.getMessage());
    }

}

public final class MyMessageBuildItem extends SimpleBuildItem {

    private final String message;

    public MyMessageBuildItem(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

}
Enter fullscreen mode Exit fullscreen mode

Test class

class TestExt {

    @Test
    void testBytecode() {
        ClassLoader originalCl = Thread.currentThread().getContextClassLoader();
        try {
            ClassLoader cl = QuarkusClassLoader.builder("CodeGenerator Config ClassLoader", originalCl, false).build();
            Thread.currentThread().setContextClassLoader(cl);
            BytecodeRecorderImpl bytecodeRecorder = new BytecodeRecorderImpl(
                    true,
                    "MyMessageProcessor",
                    "printMessage",
                    Integer.toString(Math.abs("printMessage".hashCode())),
                    true,
                    s -> {
                        throw new RuntimeException("Not implemented for testing");
                    });

            MyMessageBuildItem buildItem = new MyMessageBuildItem("world");
            MyMessageRecorder myMessageRecorder = bytecodeRecorder.getRecordingProxy(MyMessageRecorder.class);
            MyMessageProcessor processor = new MyMessageProcessor();
            processor.printMessage(myMessageRecorder, buildItem);

            GeneratedClassOutput generatedClassOutput = new GeneratedClassOutput();
            bytecodeRecorder.writeBytecode(generatedClassOutput);
            for (GeneratedClass c : generatedClassOutput.getOutput()) {
                try (FileOutputStream outputStream = new FileOutputStream("target/" + c.getName() + ".class")) {
                    outputStream.write(c.getData());
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            Thread.currentThread().setContextClassLoader(originalCl);
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

Finally a bytecode class file is generated. Following is the class

package io.quarkus.deployment.steps;

import io.quarkus.runtime.StartupContext;
import io.quarkus.runtime.StartupTask;
import MyMessageRecorder;

// $FF: synthetic class
public class MyMessageProcessor$printMessage1094908058 implements StartupTask {
    public MyMessageProcessor$printMessage1094908058() {
    }

    public void deploy(StartupContext var1) {
        var1.setCurrentBuildStepName("MyMessageProcessor.printMessage");
        Object[] var2 = this.$quarkus$createArray();
        this.deploy_0(var1, var2);
    }

    public void deploy_0(StartupContext var1, Object[] var2) {
        (new MyMessageRecorder()).sayHello("world");
    }

    public Object[] $quarkus$createArray() {
        return new Object[0];
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)