Skip to content

Commit 88b4953

Browse files
committed
Speedup Flyway by preventing it from doing classpath scanning
This PR essentially takes the substitution we had for native mode and turns it into a class transformer. This way even in JVM mode our Flyway integration take advantage of the fact that the migration script locations are known at build time, thus no classpath scanning is needed. This solution isn't great from a maintenance perspective, but the transformation is relatively straightforward and our test suite for Flyway pretty extensive, so we should easily be able to adapt to future Flyway updates (ideally we would be able to remove this if /when flyway/flyway#2822 is done) Relates to: #9428
1 parent e60c793 commit 88b4953

File tree

5 files changed

+130
-47
lines changed

5 files changed

+130
-47
lines changed

extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import io.quarkus.deployment.annotations.BuildStep;
4141
import io.quarkus.deployment.annotations.ExecutionTime;
4242
import io.quarkus.deployment.annotations.Record;
43+
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
4344
import io.quarkus.deployment.builditem.CapabilityBuildItem;
4445
import io.quarkus.deployment.builditem.FeatureBuildItem;
4546
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
@@ -65,6 +66,13 @@ CapabilityBuildItem capability() {
6566
return new CapabilityBuildItem(Capabilities.FLYWAY);
6667
}
6768

69+
@BuildStep
70+
void scannerTransformer(BuildProducer<BytecodeTransformerBuildItem> transformers) {
71+
transformers
72+
.produce(new BytecodeTransformerBuildItem(true, ScannerTransformer.FLYWAY_SCANNER_CLASS_NAME,
73+
new ScannerTransformer()));
74+
}
75+
6876
@Record(STATIC_INIT)
6977
@BuildStep
7078
void build(BuildProducer<FeatureBuildItem> featureProducer,
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package io.quarkus.flyway;
2+
3+
import static org.objectweb.asm.Opcodes.ALOAD;
4+
import static org.objectweb.asm.Opcodes.ASTORE;
5+
import static org.objectweb.asm.Opcodes.DUP;
6+
import static org.objectweb.asm.Opcodes.GETFIELD;
7+
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
8+
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
9+
import static org.objectweb.asm.Opcodes.NEW;
10+
import static org.objectweb.asm.Opcodes.POP;
11+
import static org.objectweb.asm.Opcodes.PUTFIELD;
12+
import static org.objectweb.asm.Opcodes.RETURN;
13+
14+
import java.util.function.BiFunction;
15+
16+
import org.flywaydb.core.internal.scanner.Scanner;
17+
import org.flywaydb.core.internal.scanner.classpath.ResourceAndClassScanner;
18+
import org.objectweb.asm.ClassVisitor;
19+
import org.objectweb.asm.MethodVisitor;
20+
21+
import io.quarkus.flyway.runtime.QuarkusPathLocationScanner;
22+
import io.quarkus.gizmo.Gizmo;
23+
24+
class ScannerTransformer implements BiFunction<String, ClassVisitor, ClassVisitor> {
25+
26+
static final String FLYWAY_SCANNER_CLASS_NAME = Scanner.class.getName();
27+
private static final String FLYWAY_SCANNER_INTERNAL_CLASS_NAME = FLYWAY_SCANNER_CLASS_NAME.replace('.', '/');
28+
29+
private static final String FLYWAY_RESOURCE_AND_CLASS_SCANNER_CLASS_NAME = ResourceAndClassScanner.class.getName();
30+
private static final String FLYWAY_RESOURCE_AND_CLASS_SCANNER_INTERNAL_CLASS_NAME = FLYWAY_RESOURCE_AND_CLASS_SCANNER_CLASS_NAME
31+
.replace('.', '/');
32+
33+
private static final String QUARKUS_RESOURCE_AND_CLASS_SCANNER_CLASS_NAME = QuarkusPathLocationScanner.class.getName();
34+
private static final String QUARKUS_RESOURCE_AND_CLASS_SCANNER_INTERNAL_CLASS_NAME = QUARKUS_RESOURCE_AND_CLASS_SCANNER_CLASS_NAME
35+
.replace('.', '/');
36+
37+
private static final String CTOR_METHOD_NAME = "<init>";
38+
39+
@Override
40+
public ClassVisitor apply(String s, ClassVisitor cv) {
41+
return new ScannerVisitor(cv);
42+
}
43+
44+
private static final class ScannerVisitor extends ClassVisitor {
45+
46+
public ScannerVisitor(ClassVisitor cv) {
47+
super(Gizmo.ASM_API_VERSION, cv);
48+
}
49+
50+
@Override
51+
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
52+
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
53+
if (name.equals(CTOR_METHOD_NAME)) {
54+
return new ConstructorTransformer(mv);
55+
}
56+
return mv;
57+
}
58+
59+
/**
60+
* Replaces the constructor of the {@link Scanner} with:
61+
*
62+
* <pre>
63+
* public ScannerSubstitutions(Class<?> implementedInterface, Collection<Location> locations, ClassLoader classLoader,
64+
* Charset encoding, ResourceNameCache resourceNameCache, LocationScannerCache locationScannerCache) {
65+
* ResourceAndClassScanner quarkusScanner = new QuarkusPathLocationScanner(locations);
66+
* resources.addAll(quarkusScanner.scanForResources());
67+
* classes.addAll(quarkusScanner.scanForClasses());
68+
* }
69+
* </pre>
70+
*/
71+
private static class ConstructorTransformer extends MethodVisitor {
72+
73+
public ConstructorTransformer(MethodVisitor mv) {
74+
super(Gizmo.ASM_API_VERSION, mv);
75+
}
76+
77+
@Override
78+
public void visitCode() {
79+
super.visitVarInsn(ALOAD, 0);
80+
super.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", CTOR_METHOD_NAME, "()V", false);
81+
super.visitVarInsn(ALOAD, 0);
82+
super.visitTypeInsn(NEW, "java/util/ArrayList");
83+
super.visitInsn(DUP);
84+
super.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", CTOR_METHOD_NAME, "()V", false);
85+
super.visitFieldInsn(PUTFIELD, FLYWAY_SCANNER_INTERNAL_CLASS_NAME, "resources", "Ljava/util/List;");
86+
super.visitVarInsn(ALOAD, 0);
87+
super.visitTypeInsn(NEW, "java/util/ArrayList");
88+
super.visitInsn(DUP);
89+
super.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", CTOR_METHOD_NAME, "()V", false);
90+
super.visitFieldInsn(PUTFIELD, FLYWAY_SCANNER_INTERNAL_CLASS_NAME, "classes", "Ljava/util/List;");
91+
super.visitTypeInsn(NEW, QUARKUS_RESOURCE_AND_CLASS_SCANNER_INTERNAL_CLASS_NAME);
92+
super.visitInsn(DUP);
93+
super.visitVarInsn(ALOAD, 2);
94+
super.visitMethodInsn(INVOKESPECIAL, QUARKUS_RESOURCE_AND_CLASS_SCANNER_INTERNAL_CLASS_NAME,
95+
CTOR_METHOD_NAME, "(Ljava/util/Collection;)V", false);
96+
super.visitVarInsn(ASTORE, 7);
97+
super.visitVarInsn(ALOAD, 0);
98+
super.visitFieldInsn(GETFIELD, FLYWAY_SCANNER_INTERNAL_CLASS_NAME, "resources", "Ljava/util/List;");
99+
super.visitVarInsn(ALOAD, 7);
100+
super.visitMethodInsn(INVOKEINTERFACE, FLYWAY_RESOURCE_AND_CLASS_SCANNER_INTERNAL_CLASS_NAME,
101+
"scanForResources", "()Ljava/util/Collection;", true);
102+
super.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "addAll", "(Ljava/util/Collection;)Z", true);
103+
super.visitInsn(POP);
104+
super.visitVarInsn(ALOAD, 0);
105+
super.visitFieldInsn(GETFIELD, FLYWAY_SCANNER_INTERNAL_CLASS_NAME, "classes", "Ljava/util/List;");
106+
super.visitVarInsn(ALOAD, 7);
107+
super.visitMethodInsn(INVOKEINTERFACE, FLYWAY_RESOURCE_AND_CLASS_SCANNER_INTERNAL_CLASS_NAME,
108+
"scanForClasses", "()Ljava/util/Collection;", true);
109+
super.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "addAll", "(Ljava/util/Collection;)Z", true);
110+
super.visitInsn(POP);
111+
super.visitInsn(RETURN);
112+
super.visitMaxs(3, 8);
113+
}
114+
}
115+
}
116+
}

extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import io.quarkus.agroal.runtime.DataSources;
1212
import io.quarkus.arc.Arc;
13-
import io.quarkus.flyway.runtime.graal.QuarkusPathLocationScanner;
1413
import io.quarkus.runtime.annotations.Recorder;
1514

1615
@Recorder
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.quarkus.flyway.runtime.graal;
1+
package io.quarkus.flyway.runtime;
22

33
import java.nio.charset.StandardCharsets;
44
import java.util.ArrayList;
@@ -12,6 +12,11 @@
1212
import org.flywaydb.core.internal.scanner.classpath.ResourceAndClassScanner;
1313
import org.jboss.logging.Logger;
1414

15+
/**
16+
* This class is used in order to prevent Flyway from doing classpath scanning which is both slow
17+
* and won't work in native mode
18+
*/
19+
@SuppressWarnings("rawtypes")
1520
public final class QuarkusPathLocationScanner implements ResourceAndClassScanner {
1621
private static final Logger LOGGER = Logger.getLogger(QuarkusPathLocationScanner.class);
1722
private static final String LOCATION_SEPARATOR = "/";

extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)