diff --git a/core/runtime/src/main/java/io/quarkus/runtime/Startup.java b/core/runtime/src/main/java/io/quarkus/runtime/Startup.java
index e57a74b26bf8c..43750635fffa9 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/Startup.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/Startup.java
@@ -12,11 +12,20 @@
import javax.enterprise.inject.spi.ObserverMethod;
/**
- * This annotation can be used to initialize a CDI bean at application startup. The behavior is similar to a declaration of an
- * observer of the {@link StartupEvent} - a contextual instance is created and lifecycle callbacks (such as
- * {@link javax.annotation.PostConstruct}) are invoked. In fact, a synthetic observer of the {@link StartupEvent} is generated
- * for each bean annotated with this annotation. Furthermore, {@link #value()} can be used to specify the priority of the
- * generated observer method and thus affect observers ordering.
+ * This annotation can be used to initialize a CDI bean at application startup:
+ *
+ * - If a bean class is annotated then a contextual instance is created and the {@link javax.annotation.PostConstruct}
+ * callbacks are invoked.
+ * - If a producer method is annotated then a contextual instance is created, i.e. the producer method is invoked.
+ * - If a producer field is annotated then a contextual instance is created, i.e. the producer field is read.
+ * - If a non-static non-producer no-args method of a bean class is annotated then a contextual instance is created, the
+ * lifecycle callbacks are invoked and finally the method itself is invoked.
+ *
+ * The behavior is similar to a declaration of a {@link StartupEvent} observer. In fact, a synthetic observer of the
+ * {@link StartupEvent} is generated for each occurence of this annotation.
+ *
+ * Furthermore, {@link #value()} can be used to specify the priority of the generated observer method and thus affects observers
+ * ordering.
*
* The contextual instance is destroyed immediately afterwards for {@link Dependent} beans.
*
@@ -25,7 +34,7 @@
*
* @ApplicationScoped
* class Bean1 {
- * void onStart(@Observes StartupEvent event) {
+ * void onStart(@Observes StartupEvent event) {
* // place the logic here
* }
* }
@@ -33,6 +42,20 @@
* @Startup
* @ApplicationScoped
* class Bean2 {
+ *
+ * @PostConstruct
+ * void init() {
+ * // place the logic here
+ * }
+ * }
+ *
+ * @ApplicationScoped
+ * class Bean3 {
+ *
+ * @Startup
+ * void init() {
+ * // place the logic here
+ * }
* }
*
*
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StartupBuildSteps.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StartupBuildSteps.java
index 3fc42d9158fcd..4e08d4f01c182 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StartupBuildSteps.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StartupBuildSteps.java
@@ -1,12 +1,20 @@
package io.quarkus.arc.deployment;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.ObserverMethod;
import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.DotName;
+import org.jboss.jandex.MethodInfo;
import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
@@ -19,6 +27,7 @@
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.arc.processor.BuildExtension;
import io.quarkus.arc.processor.BuiltinScope;
+import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.ObserverConfigurator;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
@@ -61,33 +70,62 @@ UnremovableBeanBuildItem unremovableBeans() {
}
@BuildStep
- void registerStartupObservers(ObserverRegistrationPhaseBuildItem observerRegistrationPhase,
+ void registerStartupObservers(ObserverRegistrationPhaseBuildItem observerRegistration,
BuildProducer configurators) {
- AnnotationStore annotationStore = observerRegistrationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE);
+ AnnotationStore annotationStore = observerRegistration.getContext().get(BuildExtension.Key.ANNOTATION_STORE);
- for (BeanInfo bean : observerRegistrationPhase.getContext().beans().withTarget()) {
- AnnotationInstance startupAnnotation = annotationStore.getAnnotation(bean.getTarget().get(), STARTUP_NAME);
+ for (BeanInfo bean : observerRegistration.getContext().beans().withTarget()) {
+ // First check if the target is annotated with @Startup
+ // Class for class-based bean, method for producer method, etc.
+ AnnotationTarget target = bean.getTarget().get();
+ AnnotationInstance startupAnnotation = annotationStore.getAnnotation(target, STARTUP_NAME);
if (startupAnnotation != null) {
- registerStartupObserver(observerRegistrationPhase, bean, startupAnnotation);
+ String id;
+ if (target.kind() == Kind.METHOD) {
+ id = target.asMethod().declaringClass().name() + "#" + target.asMethod().toString();
+ } else if (target.kind() == Kind.FIELD) {
+ id = target.asField().declaringClass().name() + "#" + target.asField().toString();
+ } else {
+ id = target.asClass().name().toString();
+ }
+ AnnotationValue priority = startupAnnotation.value();
+ registerStartupObserver(observerRegistration, bean, id,
+ priority != null ? priority.asInt() : ObserverMethod.DEFAULT_PRIORITY, null);
+ }
+
+ List startupMethods = Collections.emptyList();
+ if (target.kind() == Kind.CLASS) {
+ // If the target is a class then collect all non-static non-producer no-args methods annotated with @Startup
+ startupMethods = new ArrayList<>();
+ for (MethodInfo method : target.asClass().methods()) {
+ if (!method.isSynthetic()
+ && !Modifier.isStatic(method.flags())
+ && method.parametersCount() == 0
+ && annotationStore.hasAnnotation(method, STARTUP_NAME)
+ && !annotationStore.hasAnnotation(method, DotNames.PRODUCES)) {
+ startupMethods.add(method);
+ }
+ }
+ }
+ if (!startupMethods.isEmpty()) {
+ for (MethodInfo method : startupMethods) {
+ AnnotationValue priority = annotationStore.getAnnotation(method, STARTUP_NAME).value();
+ registerStartupObserver(observerRegistration, bean,
+ method.declaringClass().name() + "#" + method.toString(),
+ priority != null ? priority.asInt() : ObserverMethod.DEFAULT_PRIORITY, method);
+ }
}
}
}
- private void registerStartupObserver(ObserverRegistrationPhaseBuildItem observerRegistrationPhase, BeanInfo bean,
- AnnotationInstance startup) {
- ObserverConfigurator configurator = observerRegistrationPhase.getContext().configure()
+ private void registerStartupObserver(ObserverRegistrationPhaseBuildItem observerRegistration, BeanInfo bean, String id,
+ int priority, MethodInfo startupMethod) {
+ ObserverConfigurator configurator = observerRegistration.getContext().configure()
.beanClass(bean.getBeanClass())
.observedType(StartupEvent.class);
- if (startup.target().kind() == Kind.METHOD) {
- configurator.id(startup.target().asMethod().toString());
- } else if (startup.target().kind() == Kind.FIELD) {
- configurator.id(startup.target().asField().name());
- }
- AnnotationValue priority = startup.value();
- if (priority != null) {
- configurator.priority(priority.asInt());
- }
+ configurator.id(id);
+ configurator.priority(priority);
configurator.notify(mc -> {
// InjectableBean bean = Arc.container().bean("bflmpsvz");
ResultHandle containerHandle = mc.invokeStaticMethod(ARC_CONTAINER);
@@ -95,24 +133,29 @@ private void registerStartupObserver(ObserverRegistrationPhaseBuildItem observer
mc.load(bean.getIdentifier()));
if (BuiltinScope.DEPENDENT.is(bean.getScope())) {
// It does not make a lot of sense to support @Startup dependent beans but it's still a valid use case
- ResultHandle contextHandle = mc.newInstance(
+ ResultHandle creationalContext = mc.newInstance(
MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class),
beanHandle);
// Create a dependent instance
- ResultHandle instanceHandle = mc.invokeInterfaceMethod(CONTEXTUAL_CREATE, beanHandle,
- contextHandle);
+ ResultHandle instance = mc.invokeInterfaceMethod(CONTEXTUAL_CREATE, beanHandle,
+ creationalContext);
+ if (startupMethod != null) {
+ mc.invokeVirtualMethod(MethodDescriptor.of(startupMethod), instance);
+ }
// But destroy the instance immediately
- mc.invokeInterfaceMethod(CONTEXTUAL_DESTROY, beanHandle, instanceHandle, contextHandle);
+ mc.invokeInterfaceMethod(CONTEXTUAL_DESTROY, beanHandle, instance, creationalContext);
} else {
// Obtains the instance from the context
// InstanceHandle handle = Arc.container().instance(bean);
ResultHandle instanceHandle = mc.invokeInterfaceMethod(ARC_CONTAINER_INSTANCE, containerHandle,
beanHandle);
- if (bean.getScope().isNormal()) {
+ ResultHandle instance = mc.invokeInterfaceMethod(INSTANCE_HANDLE_GET, instanceHandle);
+ if (startupMethod != null) {
+ mc.invokeVirtualMethod(MethodDescriptor.of(startupMethod), instance);
+ } else if (bean.getScope().isNormal()) {
// We need to unwrap the client proxy
// ((ClientProxy) handle.get()).arc_contextualInstance();
- ResultHandle proxyHandle = mc.checkCast(
- mc.invokeInterfaceMethod(INSTANCE_HANDLE_GET, instanceHandle), ClientProxy.class);
+ ResultHandle proxyHandle = mc.checkCast(instance, ClientProxy.class);
mc.invokeInterfaceMethod(CLIENT_PROXY_CONTEXTUAL_INSTANCE, proxyHandle);
}
}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/startup/StartupAnnotationTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/startup/StartupAnnotationTest.java
index b8309310c7e12..85b099dbd5f62 100644
--- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/startup/StartupAnnotationTest.java
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/startup/StartupAnnotationTest.java
@@ -69,8 +69,8 @@ public void transform(TransformationContext context) {
@Test
public void testStartup() {
- // StartMe, SingletonStartMe, ProducerStartMe, DependentStartMe
- assertEquals(14, LOG.size(), "Unexpected number of log messages: " + LOG);
+ // StartMe, SingletonStartMe, ProducerStartMe, StartupMethods, DependentStartMe
+ assertEquals(17, LOG.size(), "Unexpected number of log messages: " + LOG);
assertEquals("startMe_c", LOG.get(0));
assertEquals("startMe_c", LOG.get(1));
assertEquals("startMe_pc", LOG.get(2));
@@ -82,9 +82,12 @@ public void testStartup() {
assertEquals("producer_pc", LOG.get(8));
assertEquals("produce_string", LOG.get(9));
assertEquals("producer_pd", LOG.get(10));
- assertEquals("dependent_c", LOG.get(11));
- assertEquals("dependent_pc", LOG.get(12));
- assertEquals("dependent_pd", LOG.get(13));
+ assertEquals("startup_pc", LOG.get(11));
+ assertEquals("startup_first", LOG.get(12));
+ assertEquals("startup_second", LOG.get(13));
+ assertEquals("dependent_c", LOG.get(14));
+ assertEquals("dependent_pc", LOG.get(15));
+ assertEquals("dependent_pd", LOG.get(16));
}
// This component should be started first
@@ -109,7 +112,7 @@ void destroy() {
}
- // @Startup is added by an annotation transformer
+ // @Startup is added by an annotation transformer, the priority is ObserverMethod.DEFAULT_PRIORITY
@Unremovable // only classes annotated with @Startup are made unremovable
@Singleton
static class SingletonStartMe {
@@ -152,14 +155,14 @@ void destroy() {
static class ProducerStartMe {
- @Startup(Integer.MAX_VALUE - 1)
+ @Startup(Integer.MAX_VALUE - 10)
@Produces
String produceString() {
LOG.add("produce_string");
return "ok";
}
- @Startup(Integer.MAX_VALUE - 2)
+ @Startup(Integer.MAX_VALUE - 20)
@Produces
Long produceLong() {
LOG.add("produce_long");
@@ -178,4 +181,26 @@ void destroy() {
}
+ @Singleton
+ @Unremovable // only classes annotated with @Startup are made unremovable
+ static class StartupMethods {
+
+ @Startup(Integer.MAX_VALUE - 2)
+ String first() {
+ LOG.add("startup_first");
+ return "ok";
+ }
+
+ @Startup(Integer.MAX_VALUE - 1)
+ void second() {
+ LOG.add("startup_second");
+ }
+
+ @PostConstruct
+ void init() {
+ LOG.add("startup_pc");
+ }
+
+ }
+
}