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

Imagine monitoring actually built for developers

Billboard image

Join Vercel, CrowdStrike, and thousands of other teams that trust Checkly to streamline monitor creation and configuration with Monitoring as Code.

Start Monitoring

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay