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];
}
}
Top comments (0)