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

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

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

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay