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; } } 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); } } } 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]; } }

Jan 27, 2025 - 18:06
 0
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;
    }

}

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);
        }
    }

}

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];
    }
}