Skip to content

Commit f08fd91

Browse files
committed
ArC: make it possible to initialize synthetic beans eagerly
- using a configurator method - resolves quarkusio#41159
1 parent 3c6e8f9 commit f08fd91

File tree

6 files changed

+174
-51
lines changed

6 files changed

+174
-51
lines changed

extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StartupBuildSteps.java

+43-45
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import java.lang.reflect.Modifier;
66
import java.util.ArrayList;
77
import java.util.Collection;
8-
import java.util.Collections;
98
import java.util.List;
9+
import java.util.OptionalInt;
1010
import java.util.function.Predicate;
1111

1212
import jakarta.enterprise.context.spi.Contextual;
@@ -113,58 +113,56 @@ public boolean test(BeanInfo bean) {
113113

114114
@BuildStep
115115
void registerStartupObservers(ObserverRegistrationPhaseBuildItem observerRegistration,
116+
List<SyntheticBeanBuildItem> syntheticBeans,
116117
BuildProducer<ObserverConfiguratorBuildItem> configurators) {
117118

118119
AnnotationStore annotationStore = observerRegistration.getContext().get(BuildExtension.Key.ANNOTATION_STORE);
119120

120-
for (BeanInfo bean : observerRegistration.getContext().beans().withTarget()) {
121-
// First check if the target is annotated with @Startup
122-
// Class for class-based bean, method for producer method, etc.
123-
AnnotationTarget target = bean.getTarget().get();
124-
AnnotationInstance startupAnnotation = annotationStore.getAnnotation(target, STARTUP_NAME);
125-
if (startupAnnotation != null) {
126-
String id;
127-
if (target.kind() == Kind.METHOD) {
128-
id = target.asMethod().declaringClass().name() + "#" + target.asMethod().toString();
129-
} else if (target.kind() == Kind.FIELD) {
130-
id = target.asField().declaringClass().name() + "#" + target.asField().toString();
131-
} else {
132-
id = target.asClass().name().toString();
121+
for (BeanInfo bean : observerRegistration.getContext().beans()) {
122+
if (bean.isSynthetic()) {
123+
OptionalInt startupPriority = bean.getStartupPriority();
124+
if (startupPriority.isPresent()) {
125+
registerStartupObserver(observerRegistration, bean, bean.getIdentifier(),
126+
startupPriority.getAsInt(), null);
133127
}
134-
AnnotationValue priority = startupAnnotation.value();
135-
registerStartupObserver(observerRegistration, bean, id,
136-
priority != null ? priority.asInt() : ObserverMethod.DEFAULT_PRIORITY, null);
137-
}
138-
139-
List<MethodInfo> startupMethods = Collections.emptyList();
140-
if (target.kind() == Kind.CLASS) {
141-
// If the target is a class then collect all non-static non-producer no-args methods annotated with @Startup
142-
startupMethods = new ArrayList<>();
143-
for (MethodInfo method : target.asClass().methods()) {
144-
if (annotationStore.hasAnnotation(method, STARTUP_NAME)) {
145-
if (!method.isSynthetic()
146-
&& !Modifier.isPrivate(method.flags())
147-
&& !Modifier.isStatic(method.flags())
148-
&& method.parametersCount() == 0
149-
&& !annotationStore.hasAnnotation(method, DotNames.PRODUCES)) {
150-
startupMethods.add(method);
151-
} else {
152-
if (!annotationStore.hasAnnotation(method, DotNames.PRODUCES)) {
153-
// Producer methods annotated with @Startup are valid and processed above
154-
LOG.warnf("Ignored an invalid @Startup method declared on %s: %s",
155-
method.declaringClass().name(),
156-
method);
128+
} else {
129+
// First check if the target is annotated with @Startup
130+
// Class for class-based bean, method for producer method, etc.
131+
AnnotationTarget target = bean.getTarget().get();
132+
AnnotationInstance startupAnnotation = annotationStore.getAnnotation(target, STARTUP_NAME);
133+
if (startupAnnotation != null) {
134+
AnnotationValue priority = startupAnnotation.value();
135+
registerStartupObserver(observerRegistration, bean, bean.getIdentifier(),
136+
priority != null ? priority.asInt() : ObserverMethod.DEFAULT_PRIORITY, null);
137+
}
138+
if (target.kind() == Kind.CLASS) {
139+
// If the target is a class then collect all non-static non-producer no-args methods annotated with @Startup
140+
List<MethodInfo> startupMethods = new ArrayList<>();
141+
for (MethodInfo method : target.asClass().methods()) {
142+
if (annotationStore.hasAnnotation(method, STARTUP_NAME)) {
143+
if (!method.isSynthetic()
144+
&& !Modifier.isPrivate(method.flags())
145+
&& !Modifier.isStatic(method.flags())
146+
&& method.parametersCount() == 0
147+
&& !annotationStore.hasAnnotation(method, DotNames.PRODUCES)) {
148+
startupMethods.add(method);
149+
} else {
150+
if (!annotationStore.hasAnnotation(method, DotNames.PRODUCES)) {
151+
// Producer methods annotated with @Startup are valid and processed above
152+
LOG.warnf("Ignored an invalid @Startup method declared on %s: %s",
153+
method.declaringClass().name(),
154+
method);
155+
}
157156
}
158157
}
159158
}
160-
}
161-
}
162-
if (!startupMethods.isEmpty()) {
163-
for (MethodInfo method : startupMethods) {
164-
AnnotationValue priority = annotationStore.getAnnotation(method, STARTUP_NAME).value();
165-
registerStartupObserver(observerRegistration, bean,
166-
method.declaringClass().name() + "#" + method.toString(),
167-
priority != null ? priority.asInt() : ObserverMethod.DEFAULT_PRIORITY, method);
159+
if (!startupMethods.isEmpty()) {
160+
for (MethodInfo method : startupMethods) {
161+
AnnotationValue priority = annotationStore.getAnnotation(method, STARTUP_NAME).value();
162+
registerStartupObserver(observerRegistration, bean, bean.getIdentifier() + method.toString(),
163+
priority != null ? priority.asInt() : ObserverMethod.DEFAULT_PRIORITY, method);
164+
}
165+
}
168166
}
169167
}
170168
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package io.quarkus.arc.test.startup;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import java.util.concurrent.atomic.AtomicBoolean;
7+
import java.util.function.Consumer;
8+
9+
import jakarta.enterprise.context.ApplicationScoped;
10+
import jakarta.enterprise.inject.Vetoed;
11+
import jakarta.inject.Inject;
12+
import jakarta.inject.Provider;
13+
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.api.extension.RegisterExtension;
16+
17+
import io.quarkus.arc.BeanCreator;
18+
import io.quarkus.arc.SyntheticCreationalContext;
19+
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
20+
import io.quarkus.builder.BuildChainBuilder;
21+
import io.quarkus.builder.BuildContext;
22+
import io.quarkus.builder.BuildStep;
23+
import io.quarkus.test.QuarkusUnitTest;
24+
25+
public class SyntheticBeanStartupTest {
26+
27+
@RegisterExtension
28+
static final QuarkusUnitTest config = new QuarkusUnitTest()
29+
.withApplicationRoot(
30+
root -> root.addClasses(SyntheticBeanStartupTest.class, SynthBean.class, SynthBeanCreator.class))
31+
.addBuildChainCustomizer(buildCustomizer());
32+
33+
static Consumer<BuildChainBuilder> buildCustomizer() {
34+
return new Consumer<BuildChainBuilder>() {
35+
36+
@Override
37+
public void accept(BuildChainBuilder builder) {
38+
builder.addBuildStep(new BuildStep() {
39+
@Override
40+
public void execute(BuildContext context) {
41+
context.produce(SyntheticBeanBuildItem.configure(SynthBean.class)
42+
.scope(ApplicationScoped.class)
43+
.identifier("ok")
44+
.startup()
45+
.creator(SynthBeanCreator.class)
46+
.done());
47+
}
48+
}).produces(SyntheticBeanBuildItem.class).build();
49+
}
50+
};
51+
}
52+
53+
@Inject
54+
Provider<SynthBean> synthBean;
55+
56+
@Test
57+
public void testStartup() {
58+
assertTrue(SynthBeanCreator.CREATED.get());
59+
assertEquals("foo", synthBean.get().getValue());
60+
}
61+
62+
public static class SynthBeanCreator implements BeanCreator<SynthBean> {
63+
64+
static final AtomicBoolean CREATED = new AtomicBoolean();
65+
66+
@Override
67+
public SynthBean create(SyntheticCreationalContext<SynthBean> context) {
68+
CREATED.set(true);
69+
return new SynthBean("foo");
70+
}
71+
}
72+
73+
@Vetoed
74+
public static class SynthBean {
75+
76+
private final String value;
77+
78+
public SynthBean(String value) {
79+
this.value = value;
80+
}
81+
82+
public String getValue() {
83+
return value;
84+
}
85+
}
86+
}

independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ public void done() {
8787
.defaultBean(defaultBean)
8888
.removable(removable)
8989
.forceApplicationClass(forceApplicationClass)
90-
.targetPackageName(targetPackageName);
90+
.targetPackageName(targetPackageName)
91+
.startupPriority(startupPriority);
9192

9293
if (!injectionPoints.isEmpty()) {
9394
builder.injections(Collections.singletonList(Injection.forSyntheticBean(injectionPoints)));

independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java

+23
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import jakarta.enterprise.context.NormalScope;
1414
import jakarta.enterprise.context.spi.CreationalContext;
1515
import jakarta.enterprise.inject.Default;
16+
import jakarta.enterprise.inject.spi.ObserverMethod;
1617

1718
import org.jboss.jandex.AnnotationInstance;
1819
import org.jboss.jandex.AnnotationValue;
@@ -54,6 +55,7 @@ public abstract class BeanConfiguratorBase<THIS extends BeanConfiguratorBase<THI
5455
protected String targetPackageName;
5556
protected Integer priority;
5657
protected final Set<TypeAndQualifiers> injectionPoints;
58+
protected Integer startupPriority;
5759

5860
protected BeanConfiguratorBase(DotName implClazz) {
5961
this.implClazz = implClazz;
@@ -94,6 +96,7 @@ public THIS read(BeanConfiguratorBase<?, ?> base) {
9496
priority = base.priority;
9597
injectionPoints.clear();
9698
injectionPoints.addAll(base.injectionPoints);
99+
startupPriority = base.startupPriority;
97100
return self();
98101
}
99102

@@ -255,6 +258,26 @@ public THIS addInjectionPoint(Type requiredType, AnnotationInstance... requiredQ
255258
return self();
256259
}
257260

261+
/**
262+
* Initialize the bean eagerly at application startup.
263+
*
264+
* @param priority
265+
* @return self
266+
*/
267+
public THIS startup(int priority) {
268+
this.startupPriority = priority;
269+
return self();
270+
}
271+
272+
/**
273+
* Initialize the bean eagerly at application startup.
274+
*
275+
* @return self
276+
*/
277+
public THIS startup() {
278+
return startup(ObserverMethod.DEFAULT_PRIORITY);
279+
}
280+
258281
public <U extends T> THIS creator(Class<? extends BeanCreator<U>> creatorClazz) {
259282
return creator(mc -> {
260283
// return new FooBeanCreator().create(syntheticCreationalContext)

independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java

+19-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.Map.Entry;
1616
import java.util.Objects;
1717
import java.util.Optional;
18+
import java.util.OptionalInt;
1819
import java.util.Set;
1920
import java.util.function.Consumer;
2021
import java.util.stream.Collectors;
@@ -81,6 +82,8 @@ public class BeanInfo implements InjectionTargetInfo {
8182

8283
private final boolean defaultBean;
8384

85+
private final List<MethodInfo> aroundInvokes;
86+
8487
// Following fields are only used by synthetic beans
8588

8689
private final boolean removable;
@@ -95,15 +98,15 @@ public class BeanInfo implements InjectionTargetInfo {
9598

9699
private final String targetPackageName;
97100

98-
private final List<MethodInfo> aroundInvokes;
101+
private final Integer startupPriority;
99102

100103
BeanInfo(AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope, Set<Type> types,
101104
Set<AnnotationInstance> qualifiers, List<Injection> injections, BeanInfo declaringBean, DisposerInfo disposer,
102105
boolean alternative, List<StereotypeInfo> stereotypes, String name, boolean isDefaultBean, String targetPackageName,
103106
Integer priority, Set<Type> unrestrictedTypes) {
104107
this(null, null, target, beanDeployment, scope, types, qualifiers, injections, declaringBean, disposer,
105108
alternative, stereotypes, name, isDefaultBean, null, null, Collections.emptyMap(), true, false,
106-
targetPackageName, priority, null, unrestrictedTypes);
109+
targetPackageName, priority, null, unrestrictedTypes, null);
107110
}
108111

109112
BeanInfo(ClassInfo implClazz, Type providerType, AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope,
@@ -112,7 +115,7 @@ public class BeanInfo implements InjectionTargetInfo {
112115
List<StereotypeInfo> stereotypes, String name, boolean isDefaultBean, Consumer<MethodCreator> creatorConsumer,
113116
Consumer<MethodCreator> destroyerConsumer, Map<String, Object> params, boolean isRemovable,
114117
boolean forceApplicationClass, String targetPackageName, Integer priority, String identifier,
115-
Set<Type> unrestrictedTypes) {
118+
Set<Type> unrestrictedTypes, Integer startupPriority) {
116119

117120
this.target = Optional.ofNullable(target);
118121
if (implClazz == null && target != null) {
@@ -152,6 +155,7 @@ public class BeanInfo implements InjectionTargetInfo {
152155
this.lifecycleInterceptors = Collections.emptyMap();
153156
this.forceApplicationClass = forceApplicationClass;
154157
this.targetPackageName = targetPackageName;
158+
this.startupPriority = startupPriority;
155159
this.aroundInvokes = isInterceptor() || isDecorator() ? List.of() : Beans.getAroundInvokes(implClazz, beanDeployment);
156160
}
157161

@@ -551,6 +555,10 @@ public boolean isDefaultBean() {
551555
return defaultBean;
552556
}
553557

558+
public OptionalInt getStartupPriority() {
559+
return startupPriority != null ? OptionalInt.of(startupPriority) : OptionalInt.empty();
560+
}
561+
554562
/**
555563
* @param requiredType
556564
* @param requiredQualifiers
@@ -1076,6 +1084,8 @@ static class Builder {
10761084

10771085
private Integer priority;
10781086

1087+
private Integer startupPriority;
1088+
10791089
Builder() {
10801090
injections = Collections.emptyList();
10811091
stereotypes = Collections.emptyList();
@@ -1170,6 +1180,11 @@ Builder defaultBean(boolean isDefaultBean) {
11701180
return this;
11711181
}
11721182

1183+
Builder startupPriority(Integer value) {
1184+
this.startupPriority = value;
1185+
return this;
1186+
}
1187+
11731188
Builder creator(Consumer<MethodCreator> creatorConsumer) {
11741189
this.creatorConsumer = creatorConsumer;
11751190
return this;
@@ -1199,7 +1214,7 @@ BeanInfo build() {
11991214
return new BeanInfo(implClazz, providerType, target, beanDeployment, scope, types, qualifiers, injections,
12001215
declaringBean, disposer, alternative, stereotypes, name, isDefaultBean, creatorConsumer,
12011216
destroyerConsumer, params, removable, forceApplicationClass, targetPackageName, priority,
1202-
identifier, null);
1217+
identifier, null, startupPriority);
12031218
}
12041219

12051220
public Builder forceApplicationClass(boolean forceApplicationClass) {

independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public class InterceptorInfo extends BeanInfo implements Comparable<InterceptorI
6666
mc.returnValue(ret);
6767
},
6868
null, params, true, false, null, priority, creatorClass.getName() + (identifier != null ? identifier : ""),
69-
null);
69+
null, null);
7070
this.bindings = bindings;
7171
this.interceptionType = interceptionType;
7272
this.creatorClass = creatorClass;

0 commit comments

Comments
 (0)