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

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more