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