mirror of
https://github.com/jlengrand/quarkus.git
synced 2026-03-10 08:41:22 +00:00
52
core/runtime/src/main/java/io/quarkus/runtime/Startup.java
Normal file
52
core/runtime/src/main/java/io/quarkus/runtime/Startup.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package io.quarkus.runtime;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.enterprise.context.Dependent;
|
||||
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.
|
||||
* <p>
|
||||
* The contextual instance is destroyed immediately afterwards for {@link Dependent} beans.
|
||||
* <p>
|
||||
* The following examples are functionally equivalent.
|
||||
*
|
||||
* <pre>
|
||||
* @ApplicationScoped
|
||||
* class Bean1 {
|
||||
* void onStart(@Observes StartupEvent event) {
|
||||
* // place the logic here
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Startup
|
||||
* @ApplicationScoped
|
||||
* class Bean2 {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @see StartupEvent
|
||||
*/
|
||||
@Target({ TYPE, METHOD, FIELD })
|
||||
@Retention(RUNTIME)
|
||||
public @interface Startup {
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the priority
|
||||
* @see javax.annotation.Priority
|
||||
*/
|
||||
int value() default ObserverMethod.DEFAULT_PRIORITY;
|
||||
|
||||
}
|
||||
@@ -102,6 +102,32 @@ See link:writing-extensions#bootstrap-three-phases[Three Phases of Bootstrap and
|
||||
|
||||
NOTE: In CDI applications, an event with qualifier `@Initialized(ApplicationScoped.class)` is fired when the application context is initialized. See https://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#application_context[the spec, window="_blank"] for more info.
|
||||
|
||||
=== Using `@Startup` to initialize a CDI bean at application startup
|
||||
|
||||
A bean represented by a class, producer method or field annotated with `@Startup` is initialized at application startup:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
package org.acme.events;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
|
||||
@Startup // <1>
|
||||
@ApplicationScoped
|
||||
public class EagerAppBean {
|
||||
|
||||
private final String name;
|
||||
|
||||
EagerAppBean(NameGenerator generator) { // <2>
|
||||
this.name = generator.createName();
|
||||
}
|
||||
}
|
||||
----
|
||||
1. For each bean annotated with `@Startup` a synthetic observer of `StartupEvent` is generated. The default priority is used.
|
||||
2. The bean constructor is called when the application starts and the resulting contextual instance is stored in the application context.
|
||||
|
||||
NOTE: `@Dependent` beans are destroyed immediately afterwards to follow the behavior of observers declared on `@Dependent` beans.
|
||||
|
||||
== Package and run the application
|
||||
|
||||
Run the application with: `./mvnw compile quarkus:dev`, the logged message is printed.
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package io.quarkus.arc.deployment;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.enterprise.context.spi.Contextual;
|
||||
import javax.enterprise.context.spi.CreationalContext;
|
||||
|
||||
import org.jboss.jandex.AnnotationInstance;
|
||||
import org.jboss.jandex.AnnotationValue;
|
||||
import org.jboss.jandex.DotName;
|
||||
|
||||
import io.quarkus.arc.Arc;
|
||||
import io.quarkus.arc.ArcContainer;
|
||||
import io.quarkus.arc.ClientProxy;
|
||||
import io.quarkus.arc.InjectableBean;
|
||||
import io.quarkus.arc.InstanceHandle;
|
||||
import io.quarkus.arc.deployment.ObserverRegistrationPhaseBuildItem.ObserverConfiguratorBuildItem;
|
||||
import io.quarkus.arc.impl.CreationalContextImpl;
|
||||
import io.quarkus.arc.processor.AnnotationStore;
|
||||
import io.quarkus.arc.processor.BeanInfo;
|
||||
import io.quarkus.arc.processor.BuildExtension;
|
||||
import io.quarkus.arc.processor.BuiltinScope;
|
||||
import io.quarkus.arc.processor.ObserverConfigurator;
|
||||
import io.quarkus.deployment.annotations.BuildProducer;
|
||||
import io.quarkus.deployment.annotations.BuildStep;
|
||||
import io.quarkus.gizmo.MethodDescriptor;
|
||||
import io.quarkus.gizmo.ResultHandle;
|
||||
import io.quarkus.runtime.Startup;
|
||||
import io.quarkus.runtime.StartupEvent;
|
||||
|
||||
public class StartupBuildSteps {
|
||||
|
||||
static final DotName STARTUP_NAME = DotName.createSimple(Startup.class.getName());
|
||||
|
||||
static final MethodDescriptor ARC_CONTAINER = MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class);
|
||||
static final MethodDescriptor ARC_CONTAINER_BEAN = MethodDescriptor.ofMethod(ArcContainer.class, "bean",
|
||||
InjectableBean.class, String.class);
|
||||
static final MethodDescriptor ARC_CONTAINER_INSTANCE = MethodDescriptor.ofMethod(ArcContainer.class, "instance",
|
||||
InstanceHandle.class, InjectableBean.class);
|
||||
static final MethodDescriptor INSTANCE_HANDLE_GET = MethodDescriptor.ofMethod(InstanceHandle.class, "get", Object.class);
|
||||
static final MethodDescriptor CLIENT_PROXY_CONTEXTUAL_INSTANCE = MethodDescriptor.ofMethod(ClientProxy.class,
|
||||
"arc_contextualInstance", Object.class);
|
||||
static final MethodDescriptor CONTEXTUAL_CREATE = MethodDescriptor.ofMethod(Contextual.class,
|
||||
"create", Object.class, CreationalContext.class);
|
||||
static final MethodDescriptor CONTEXTUAL_DESTROY = MethodDescriptor.ofMethod(Contextual.class,
|
||||
"destroy", void.class, Object.class, CreationalContext.class);
|
||||
|
||||
@BuildStep
|
||||
UnremovableBeanBuildItem unremovableBeans() {
|
||||
// Make all classes annotated with @Startup unremovable
|
||||
return new UnremovableBeanBuildItem(new Predicate<BeanInfo>() {
|
||||
@Override
|
||||
public boolean test(BeanInfo bean) {
|
||||
if (bean.isClassBean()) {
|
||||
return bean.getTarget().get().asClass().annotations().containsKey(STARTUP_NAME);
|
||||
} else if (bean.isProducerMethod()) {
|
||||
return bean.getTarget().get().asMethod().hasAnnotation(STARTUP_NAME);
|
||||
} else if (bean.isProducerField()) {
|
||||
return bean.getTarget().get().asField().hasAnnotation(STARTUP_NAME);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@BuildStep
|
||||
void registerStartupObservers(ObserverRegistrationPhaseBuildItem observerRegistrationPhase,
|
||||
BuildProducer<ObserverConfiguratorBuildItem> configurators) {
|
||||
|
||||
AnnotationStore annotationStore = observerRegistrationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE);
|
||||
|
||||
for (BeanInfo bean : observerRegistrationPhase.getContext().beans().withTarget()) {
|
||||
AnnotationInstance startupAnnotation = annotationStore.getAnnotation(bean.getTarget().get(), STARTUP_NAME);
|
||||
if (startupAnnotation != null) {
|
||||
registerStartupObserver(observerRegistrationPhase, bean, startupAnnotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerStartupObserver(ObserverRegistrationPhaseBuildItem observerRegistrationPhase, BeanInfo bean,
|
||||
AnnotationInstance startup) {
|
||||
ObserverConfigurator configurator = observerRegistrationPhase.getContext().configure()
|
||||
.beanClass(bean.getBeanClass())
|
||||
.observedType(StartupEvent.class);
|
||||
AnnotationValue priority = startup.value();
|
||||
if (priority != null) {
|
||||
configurator.priority(priority.asInt());
|
||||
}
|
||||
configurator.notify(mc -> {
|
||||
// InjectableBean<Foo> bean = Arc.container().bean("bflmpsvz");
|
||||
ResultHandle containerHandle = mc.invokeStaticMethod(ARC_CONTAINER);
|
||||
ResultHandle beanHandle = mc.invokeInterfaceMethod(ARC_CONTAINER_BEAN, containerHandle,
|
||||
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(
|
||||
MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class),
|
||||
beanHandle);
|
||||
// Create a dependent instance
|
||||
ResultHandle instanceHandle = mc.invokeInterfaceMethod(CONTEXTUAL_CREATE, beanHandle,
|
||||
contextHandle);
|
||||
// But destroy the instance immediately
|
||||
mc.invokeInterfaceMethod(CONTEXTUAL_DESTROY, beanHandle, instanceHandle, contextHandle);
|
||||
} else {
|
||||
// Obtains the instance from the context
|
||||
// InstanceHandle<Foo> handle = Arc.container().instance(bean);
|
||||
ResultHandle instanceHandle = mc.invokeInterfaceMethod(ARC_CONTAINER_INSTANCE, containerHandle,
|
||||
beanHandle);
|
||||
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);
|
||||
mc.invokeInterfaceMethod(CLIENT_PROXY_CONTEXTUAL_INSTANCE, proxyHandle);
|
||||
}
|
||||
}
|
||||
mc.returnValue(null);
|
||||
});
|
||||
configurator.done();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package io.quarkus.arc.test.startup;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.enterprise.context.Dependent;
|
||||
import javax.enterprise.inject.Produces;
|
||||
import javax.enterprise.inject.spi.ObserverMethod;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jboss.jandex.AnnotationTarget;
|
||||
import org.jboss.jandex.AnnotationTarget.Kind;
|
||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import io.quarkus.arc.Unremovable;
|
||||
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
|
||||
import io.quarkus.arc.processor.AnnotationsTransformer;
|
||||
import io.quarkus.builder.BuildChainBuilder;
|
||||
import io.quarkus.builder.BuildContext;
|
||||
import io.quarkus.builder.BuildStep;
|
||||
import io.quarkus.runtime.Startup;
|
||||
import io.quarkus.test.QuarkusUnitTest;
|
||||
|
||||
public class StartupAnnotationTest {
|
||||
|
||||
static final List<String> LOG = new CopyOnWriteArrayList<String>();
|
||||
|
||||
@RegisterExtension
|
||||
static final QuarkusUnitTest config = new QuarkusUnitTest()
|
||||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
|
||||
.addClasses(StartMe.class, SingletonStartMe.class, DependentStartMe.class, ProducerStartMe.class))
|
||||
.addBuildChainCustomizer(buildCustomizer());
|
||||
|
||||
static Consumer<BuildChainBuilder> buildCustomizer() {
|
||||
return new Consumer<BuildChainBuilder>() {
|
||||
|
||||
@Override
|
||||
public void accept(BuildChainBuilder builder) {
|
||||
builder.addBuildStep(new BuildStep() {
|
||||
|
||||
@Override
|
||||
public void execute(BuildContext context) {
|
||||
context.produce(new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {
|
||||
|
||||
@Override
|
||||
public boolean appliesTo(Kind kind) {
|
||||
return AnnotationTarget.Kind.CLASS.equals(kind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TransformationContext context) {
|
||||
if (context.getTarget().asClass().name().toString().endsWith("SingletonStartMe")) {
|
||||
context.transform().add(Startup.class).done();
|
||||
}
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
}).produces(AnnotationsTransformerBuildItem.class).build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartup() {
|
||||
// StartMe, SingletonStartMe, ProducerStartMe, DependentStartMe
|
||||
assertEquals(11, 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));
|
||||
assertEquals("singleton_c", LOG.get(3));
|
||||
assertEquals("singleton_pc", LOG.get(4));
|
||||
assertEquals("producer_pc", LOG.get(5));
|
||||
assertEquals("producer", LOG.get(6));
|
||||
assertEquals("producer_pd", LOG.get(7));
|
||||
assertEquals("dependent_c", LOG.get(8));
|
||||
assertEquals("dependent_pc", LOG.get(9));
|
||||
assertEquals("dependent_pd", LOG.get(10));
|
||||
}
|
||||
|
||||
// This component should be started first
|
||||
@Startup(ObserverMethod.DEFAULT_PRIORITY - 1)
|
||||
@ApplicationScoped
|
||||
static class StartMe {
|
||||
|
||||
public StartMe() {
|
||||
// This constructor will be invoked 2x - for proxy and contextual instance
|
||||
LOG.add("startMe_c");
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
LOG.add("startMe_pc");
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
void destroy() {
|
||||
LOG.add("startMe_pd");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @Startup is added by an annotation transformer
|
||||
@Unremovable // only classes annotated with @Startup are made unremovable
|
||||
@Singleton
|
||||
static class SingletonStartMe {
|
||||
|
||||
public SingletonStartMe() {
|
||||
LOG.add("singleton_c");
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
LOG.add("singleton_pc");
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
void destroy() {
|
||||
LOG.add("singleton_pd");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Dependent
|
||||
@Startup(Integer.MAX_VALUE)
|
||||
static class DependentStartMe {
|
||||
|
||||
public DependentStartMe() {
|
||||
LOG.add("dependent_c");
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
LOG.add("dependent_pc");
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
void destroy() {
|
||||
LOG.add("dependent_pd");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ProducerStartMe {
|
||||
|
||||
@Startup(Integer.MAX_VALUE - 1)
|
||||
@Produces
|
||||
String produceString() {
|
||||
LOG.add("producer");
|
||||
return "ok";
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
LOG.add("producer_pc");
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
void destroy() {
|
||||
LOG.add("producer_pd");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1018,8 +1018,12 @@ public class BeanDeployment {
|
||||
|
||||
@Override
|
||||
public ObserverConfigurator configure() {
|
||||
return new ObserverConfigurator(DotName.createSimple(extension.getClass().getName()),
|
||||
beanDeployment::addSyntheticObserver);
|
||||
ObserverConfigurator configurator = new ObserverConfigurator(beanDeployment::addSyntheticObserver);
|
||||
if (extension != null) {
|
||||
// Extension may be null if called directly from the ObserverRegistrationPhaseBuildItem
|
||||
configurator.beanClass(DotName.createSimple(extension.getClass().getName()));
|
||||
}
|
||||
return configurator;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -153,6 +153,10 @@ public class BeanInfo implements InjectionTargetInfo {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the annotation target or an empty optional in case of synthetic beans
|
||||
*/
|
||||
public Optional<AnnotationTarget> getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class BeanStream implements Iterable<BeanInfo> {
|
||||
return withBeanClass(DotName.createSimple(beanClass.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the new stream of beans
|
||||
* @see BeanInfo#getTarget()
|
||||
*/
|
||||
public BeanStream withTarget() {
|
||||
stream = stream.filter(bean -> bean.getTarget().isPresent());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param beanClass
|
||||
|
||||
@@ -20,7 +20,7 @@ public final class ObserverConfigurator implements Consumer<AnnotationInstance>
|
||||
|
||||
final Consumer<ObserverConfigurator> consumer;
|
||||
|
||||
final DotName beanClass;
|
||||
DotName beanClass;
|
||||
|
||||
Type observedType;
|
||||
|
||||
@@ -34,8 +34,7 @@ public final class ObserverConfigurator implements Consumer<AnnotationInstance>
|
||||
|
||||
Consumer<MethodCreator> notifyConsumer;
|
||||
|
||||
public ObserverConfigurator(DotName beanClass, Consumer<ObserverConfigurator> consumer) {
|
||||
this.beanClass = beanClass;
|
||||
public ObserverConfigurator(Consumer<ObserverConfigurator> consumer) {
|
||||
this.consumer = consumer;
|
||||
this.observedQualifiers = new HashSet<>();
|
||||
this.priority = ObserverMethod.DEFAULT_PRIORITY;
|
||||
@@ -43,6 +42,11 @@ public final class ObserverConfigurator implements Consumer<AnnotationInstance>
|
||||
this.transactionPhase = TransactionPhase.IN_PROGRESS;
|
||||
}
|
||||
|
||||
public ObserverConfigurator beanClass(DotName beanClass) {
|
||||
this.beanClass = beanClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ObserverConfigurator observedType(Class<?> observedType) {
|
||||
this.observedType = Type.create(DotName.createSimple(observedType.getName()), Kind.CLASS);
|
||||
return this;
|
||||
@@ -91,6 +95,15 @@ public final class ObserverConfigurator implements Consumer<AnnotationInstance>
|
||||
}
|
||||
|
||||
public void done() {
|
||||
if (beanClass == null) {
|
||||
throw new IllegalStateException("Observer bean class must be set!");
|
||||
}
|
||||
if (observedType == null) {
|
||||
throw new IllegalStateException("Observed type must be set!");
|
||||
}
|
||||
if (notifyConsumer == null) {
|
||||
throw new IllegalStateException("Bytecode generator for notify() method must be set!");
|
||||
}
|
||||
consumer.accept(this);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user