Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Async Bean Generation #736

Closed
wants to merge 13 commits into from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ build/
inject-generator/avaje-inject
inject-generator/avaje-inject-generator
inject-generator/avaje-processors.txt
*.csv
inject-generator/util/events/ListString$Publisher$DI.java
inject-generator/util/events/ListString$Publisher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.example.myapp.async;

import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import io.avaje.inject.AsyncBean;
import io.avaje.lang.Nullable;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;

@AsyncBean
@Singleton
@Named("single")
public class BackgroundBean {

final Instant initTime;
final String threadName = Thread.currentThread().getName();

public BackgroundBean(@Nullable AtomicInteger intyAtomic) throws InterruptedException {
this.initTime = Instant.now();

if (intyAtomic != null) {
intyAtomic.incrementAndGet();
}

Thread.sleep(200);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.example.myapp.async;

import java.util.concurrent.atomic.AtomicInteger;

import io.avaje.inject.AsyncBean;
import io.avaje.inject.Bean;
import io.avaje.inject.Factory;
import io.avaje.lang.Nullable;
import jakarta.inject.Named;

@Factory
@AsyncBean
public class BackgroundBeanFactory {

@Bean
@Named("factory")
BackgroundBean lazyInt(@Nullable AtomicInteger intyAtomic) throws InterruptedException {
System.out.println("StartedInit BackgroundBean() " + Thread.currentThread().getName());
return new BackgroundBean(intyAtomic);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.example.myapp.async;

import io.avaje.inject.AsyncBean;
import io.avaje.inject.Component;
import jakarta.inject.Named;

@AsyncBean
@Component
public class MyUseOfBackground {

private final BackgroundBean backgroundBean;

public MyUseOfBackground(@Named("single") BackgroundBean backgroundBean) {
this.backgroundBean = backgroundBean;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.example.myapp.async;

import static org.assertj.core.api.Assertions.assertThat;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.jupiter.api.Test;

import io.avaje.inject.BeanScope;

class AsyncTest {

@Test
void test() {
var start = Instant.now();
var inty = new AtomicInteger();
try (var scope = BeanScope.builder().bean(AtomicInteger.class, inty).build()) {

// the async beans shouldn't slowdown initialization
assertThat(Duration.between(start, Instant.now()).toMillis()).isLessThan(300);

// prove it's not just lazy
var beforeGet = Instant.now();
var bean = scope.get(BackgroundBean.class, "single");
assertThat(inty.get()).isEqualTo(2);
assertThat(bean.initTime.isBefore(beforeGet)).isTrue();
assertThat(bean.threadName).isNotEqualTo(Thread.currentThread().getName());
}
}

@Test
void testFactory() {
var start = Instant.now();
var inty = new AtomicInteger();
try (var scope = BeanScope.builder().bean(AtomicInteger.class, inty).build()) {
// the async beans shouldn't slowdown initialization
assertThat(Duration.between(start, Instant.now()).toMillis()).isLessThan(300);

var bean = scope.get(BackgroundBean.class, "factory");
// this works on my local but not on the CI for some unknown reason.
// var beforeGet = Instant.now();
// assertThat(bean.initTime.isBefore(beforeGet)).isTrue();
assertThat(inty.get()).isEqualTo(2);
assertThat(bean.threadName).isNotEqualTo(Thread.currentThread().getName());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;

import javax.lang.model.element.Element;
Expand Down Expand Up @@ -38,6 +39,7 @@ final class BeanReader {
private final boolean prototype;
private final boolean primary;
private final boolean secondary;
private final boolean async;
private final boolean lazy;
private final boolean proxy;
private final BeanAspects aspects;
Expand All @@ -58,6 +60,7 @@ final class BeanReader {
this.primary = PrimaryPrism.isPresent(beanType);
this.secondary = !primary && SecondaryPrism.isPresent(beanType);
this.lazy = !FactoryPrism.isPresent(beanType) && LazyPrism.isPresent(beanType);
this.async = !FactoryPrism.isPresent(beanType) && AsyncBeanPrism.isPresent(beanType);
final var beantypes = BeanTypesPrism.getOptionalOn(beanType);
beantypes.ifPresent(p -> Util.validateBeanTypes(beanType, p.value()));
this.typeReader =
Expand Down Expand Up @@ -128,13 +131,17 @@ BeanAspects aspects() {
}

boolean registerProvider() {
return prototype || lazy;
return prototype || async || lazy;
}

boolean lazy() {
return lazy;
}

boolean async() {
return async;
}

boolean importedComponent() {
return importedComponent;
}
Expand Down Expand Up @@ -304,7 +311,7 @@ void buildAddFor(Append writer) {
}

void buildRegister(Append writer) {
if (prototype || lazy) {
if (prototype || lazy || async) {
return;
}
writer.indent(" ");
Expand Down Expand Up @@ -349,15 +356,20 @@ void prototypePostConstruct(Append writer, String indent) {

private void lifeCycleNotSupported(String lifecycle) {
if (registerProvider()) {
logError(
beanType,
"%s scoped bean does not support the %s lifecycle method",
prototype ? "@Prototype" : "@Lazy",
lifecycle);
String scope;
if (lazy) scope = "@Lazy";
if (async) scope = "@AsyncBean";
else scope = "@Prototype";

logError(beanType, "%s scoped bean does not support the %s lifecycle method", scope, lifecycle);
}
}

private Set<String> importTypes() {
importTypes.add(type);
if (async) {
importTypes.add(CompletableFuture.class.getCanonicalName());
}
importTypes.add(type);
typeReader.extraImports(importTypes);
requestParams.addImports(importTypes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

import static io.avaje.inject.generator.Constants.CONDITIONAL_DEPENDENCY;
import static io.avaje.inject.generator.ProcessingContext.asElement;
Expand All @@ -21,6 +22,7 @@ final class MethodReader {
private final boolean prototype;
private final boolean primary;
private final boolean secondary;
private final boolean async;
private final boolean lazy;
private final String returnTypeRaw;
private final UType genericType;
Expand Down Expand Up @@ -49,9 +51,11 @@ final class MethodReader {
prototype = PrototypePrism.isPresent(element);
primary = PrimaryPrism.isPresent(element);
secondary = SecondaryPrism.isPresent(element);
async = AsyncBeanPrism.isPresent(element) || AsyncBeanPrism.isPresent(element.getEnclosingElement());
lazy = LazyPrism.isPresent(element) || LazyPrism.isPresent(element.getEnclosingElement());
conditions.readAll(element);
} else {
async = false;
prototype = false;
primary = false;
secondary = false;
Expand Down Expand Up @@ -103,8 +107,8 @@ final class MethodReader {
this.initMethod = lifecycleReader.initMethod();
this.destroyMethod = lifecycleReader.destroyMethod();
}
if (lazy && prototype) {
APContext.logError("Cannot use both @Lazy and @Prototype");
if ((async || lazy) && prototype) {
APContext.logError("Cannot use both @AsyncBean/@Lazy and @Prototype");
}
}

Expand Down Expand Up @@ -230,7 +234,10 @@ void builderAddBeanProvider(Append writer) {
writer.append(".asSecondary()");
}

writer.indent(".registerProvider(() -> {").eol();
writer
.indent(".registerProvider(")
.append("%s() -> {", async ? "CompletableFuture.supplyAsync(" : "")
.eol();

startTry(writer, " ");
writer.indent(indent).append(" return ");
Expand All @@ -243,7 +250,7 @@ void builderAddBeanProvider(Append writer) {
}
writer.append(");").eol();
endTry(writer, " ");
writer.indent(indent).append(" });").eol();
writer.indent(indent).append(" }%s);", async ? ")::join" : "").eol();
writer.indent(indent).append("}").eol();
}

Expand Down Expand Up @@ -340,6 +347,10 @@ void addImports(ImportTypeMap importTypes) {
if (optionalType) {
importTypes.add(Constants.OPTIONAL);
}

if (async) {
importTypes.add(CompletableFuture.class.getCanonicalName());
}
conditions.addImports(importTypes);
}

Expand Down Expand Up @@ -429,6 +440,10 @@ boolean isProtoType() {
return prototype && !Util.isProvider(returnTypeRaw);
}

boolean isAsync() {
return async;
}

boolean isLazy() {
return lazy;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

import javax.lang.model.type.TypeKind;
import javax.tools.JavaFileObject;
Expand Down Expand Up @@ -143,7 +144,7 @@ private void writeFactoryBeanMethod(MethodReader method) {
method.buildConditional(writer);
method.buildAddFor(writer);
method.builderGetFactory(writer, beanReader.hasConditions());
if (method.isLazy() || method.isProtoType() || method.isUseProviderForSecondary()) {
if (method.isAsync() || method.isLazy() || method.isProtoType() || method.isUseProviderForSecondary()) {
method.builderAddBeanProvider(writer);
} else {
method.startTry(writer);
Expand Down Expand Up @@ -177,7 +178,9 @@ private void writeAddFor(MethodReader constructor) {
indent += " ";

final String registerProvider;
if (beanReader.lazy()) {
if (beanReader.async()) {
registerProvider = "registerProvider(CompletableFuture.supplyAsync";
} else if (beanReader.lazy()) {
registerProvider = "registerProvider";
} else {
registerProvider = "asPrototype().registerProvider";
Expand All @@ -196,18 +199,18 @@ private void writeAddFor(MethodReader constructor) {
beanReader.prototypePostConstruct(writer, indent);
writer.indent(" return bean;").eol();
if (!constructor.methodThrows()) {
writer.indent(" });").eol();
writer.indent(" }").append(beanReader.async() ? ")::join);" : ");").eol();
}
}
writeObserveMethods();
constructor.endTry(writer);
writer.append(" }");

if (beanReader.registerProvider() && constructor.methodThrows()) {
writer.append(" }");
writer.append(");").eol();
writer.append("%s);", beanReader.async() ? ")::join" : "").eol();
writer.append(" }");
}

writer.append(" }");
writer.eol();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@GeneratePrism(value = Aspect.Import.class, name = "AspectImportPrism")
@GeneratePrism(Assisted.class)
@GeneratePrism(AssistFactory.class)
@GeneratePrism(AsyncBean.class)
@GeneratePrism(Bean.class)
@GeneratePrism(Component.class)
@GeneratePrism(Component.Import.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.avaje.inject.generator.models.valid.async;

import io.avaje.inject.AsyncBean;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.inject.Singleton;

@Singleton
@AsyncBean
public class BackgroundBean {
@Inject Provider<Integer> intProvider;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.avaje.inject.generator.models.valid.async;

import io.avaje.inject.AsyncBean;
import io.avaje.inject.Bean;
import io.avaje.inject.Factory;
import jakarta.inject.Named;

@Factory
@AsyncBean
public class BackgroundBeanFactory {

@Bean
@Named("factory")
BackgroundBean lazyInt() throws InterruptedException {

return new BackgroundBean();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public class LazyFactory {

@Bean
Integer lazyInt() {
Integer lazyInt() throws Exception {
return 0;
}
}
Loading