Skip to content

Commit

Permalink
support lazy beans
Browse files Browse the repository at this point in the history
  • Loading branch information
SentryMan committed Mar 10, 2024
1 parent b60a009 commit 7d6a567
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ final class BeanReader {
private final boolean prototype;
private final boolean primary;
private final boolean secondary;
private final boolean lazy;
private final boolean proxy;
private final BeanAspects aspects;
private final BeanConditions conditions = new BeanConditions();
Expand All @@ -50,6 +51,7 @@ final class BeanReader {
this.prototype = PrototypePrism.isPresent(beanType);
this.primary = PrimaryPrism.isPresent(beanType);
this.secondary = !primary && SecondaryPrism.isPresent(beanType);
this.lazy = !FactoryPrism.isPresent(beanType) && LazyPrism.isPresent(beanType);
this.typeReader = new TypeReader(UType.parse(beanType.asType()), beanType, importTypes, factory);

typeReader.process();
Expand Down Expand Up @@ -110,8 +112,12 @@ BeanAspects aspects() {
return aspects;
}

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

boolean lazy() {
return lazy;
}

boolean importedComponent() {
Expand Down Expand Up @@ -261,7 +267,7 @@ void buildAddFor(Append writer) {
}

void buildRegister(Append writer) {
if (prototype) {
if (prototype || lazy) {
return;
}
writer.indent(" ");
Expand All @@ -278,11 +284,13 @@ void buildRegister(Append writer) {
}

void addLifecycleCallbacks(Append writer, String indent) {
if (postConstructMethod != null && !prototype) {

if (postConstructMethod != null) {
lifeCycleNotSupported("@PostConstruct");
writer.indent(indent).append(" builder.addPostConstruct($bean::%s);", postConstructMethod.getSimpleName()).eol();
}
if (preDestroyMethod != null) {
prototypeNotSupported("@PreDestroy");
lifeCycleNotSupported("@PreDestroy");
var priority = preDestroyPriority == null || preDestroyPriority == 1000 ? "" : ", " + preDestroyPriority;
writer.indent(indent).append(" builder.addPreDestroy($bean::%s%s);", preDestroyMethod.getSimpleName(), priority).eol();
} else if (typeReader.isClosable() && !prototype) {
Expand All @@ -296,9 +304,13 @@ void prototypePostConstruct(Append writer, String indent) {
}
}

private void prototypeNotSupported(String lifecycle) {
if (prototype) {
logError(beanType, "@Prototype scoped bean does not support %s lifecycle method", lifecycle);
private void lifeCycleNotSupported(String lifecycle) {
if (registerProvider()) {
logError(
beanType,
"%s scoped bean does not support the %s lifecycle method",
prototype ? "@Prototype" : "@Lazy",
lifecycle);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ final class MethodReader {
private final boolean prototype;
private final boolean primary;
private final boolean secondary;
private final boolean lazy;
private final String returnTypeRaw;
private final UType genericType;
private final String shortName;
Expand All @@ -46,11 +47,13 @@ final class MethodReader {
prototype = PrototypePrism.isPresent(element);
primary = PrimaryPrism.isPresent(element);
secondary = SecondaryPrism.isPresent(element);
lazy = LazyPrism.isPresent(element) || LazyPrism.isPresent(element.getEnclosingElement());
conditions.readAll(element);
} else {
prototype = false;
primary = false;
secondary = false;
lazy = false;
}
this.methodName = element.getSimpleName().toString();
TypeMirror returnMirror = element.getReturnType();
Expand Down Expand Up @@ -87,6 +90,9 @@ final class MethodReader {
this.initMethod = lifecycleReader.initMethod();
this.destroyMethod = lifecycleReader.destroyMethod();
}
if (lazy && prototype) {
APContext.logError("Cannot use both @Lazy and @Prototype");
}
}

@Override
Expand Down Expand Up @@ -189,14 +195,14 @@ void builderAddBeanProvider(Append writer) {
return;
}
String indent = " ";
writer.indent(indent).append(" builder.");
writer.indent(indent).append(" builder");
if (prototype) {
writer.append("asPrototype()").eol();
writer.append(".asPrototype()").eol();
} else if(secondary) {
writer.append("asSecondary()").eol();
writer.append(".asSecondary()").eol();
}

writer.indent(".registerProvider(() -> {");
writer.indent(String.format(".%s(() -> {", lazy ? "registerLazy" : "registerProvider")).eol();

writer.indent(indent).append(" return ");
writer.append("factory.%s(", methodName);
Expand Down Expand Up @@ -451,10 +457,6 @@ void builderGetDependency(Append writer, String builderName) {
writer.append(")");
}

private String providerParam() {
return Util.shortName(Util.unwrapProvider(paramType));
}

String simpleName() {
return simpleName;
}
Expand Down Expand Up @@ -542,4 +544,8 @@ Element element() {
return element;
}
}

public boolean isLazy() {
return lazy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ private void writeFactoryBeanMethod(MethodReader method) {
method.buildAddFor(writer);
method.builderGetFactory(writer, beanReader.hasConditions());
method.startTry(writer);
if (method.isProtoType() || method.isUseProviderForSecondary()) {
if (method.isLazy() || method.isProtoType() || method.isUseProviderForSecondary()) {
method.builderAddBeanProvider(writer);
method.endTry(writer);
} else {
Expand Down Expand Up @@ -177,9 +177,9 @@ private void writeStaticFactoryMethod() {
private void writeAddFor(MethodReader constructor) {
beanReader.buildConditional(writer);
beanReader.buildAddFor(writer);
if (beanReader.prototype()) {
if (beanReader.registerProvider()) {
indent += " ";
writer.append(" builder.asPrototype().registerProvider(() -> {", shortName, shortName).eol();
writer.append(" builder.%s(() -> {", beanReader.lazy() ? "registerLazy" : "asPrototype().registerProvider").eol();
}
constructor.startTry(writer);
writeCreateBean(constructor);
Expand All @@ -188,7 +188,7 @@ private void writeAddFor(MethodReader constructor) {
if (beanReader.isExtraInjectionRequired()) {
writeExtraInjection();
}
if (beanReader.prototype()) {
if (beanReader.registerProvider()) {
beanReader.prototypePostConstruct(writer, indent);
writer.indent(" return bean;").eol();
writer.indent(" });").eol();
Expand All @@ -198,7 +198,7 @@ private void writeAddFor(MethodReader constructor) {
}

private void writeBuildMethodStart() {
if (beanReader.prototype()) {
if (beanReader.registerProvider()) {
writer.append(CODE_COMMENT_BUILD_PROVIDER, shortName).eol();
} else {
writer.append(CODE_COMMENT_BUILD, shortName).eol();
Expand All @@ -215,20 +215,20 @@ private void writeCreateBean(MethodReader constructor) {
}

private void writeExtraInjection() {
if (!beanReader.prototype()) {
if (!beanReader.registerProvider()) {
writer.indent(" ").append("builder.addInjector(b -> {").eol();
writer.indent(" ").append(" // field and method injection").eol();
}
injectFields();
injectMethods();
if (!beanReader.prototype()) {
if (!beanReader.registerProvider()) {
writer.indent(" });").eol();
}
}

private void injectFields() {
String bean = beanReader.prototype() ? "bean" : "$bean";
String builder = beanReader.prototype() ? "builder" : "b";
String bean = beanReader.registerProvider() ? "bean" : "$bean";
String builder = beanReader.registerProvider() ? "builder" : "b";
for (FieldReader fieldReader : beanReader.injectFields()) {
String fieldName = fieldReader.fieldName();
String getDependency = fieldReader.builderGetDependency(builder);
Expand All @@ -238,8 +238,8 @@ private void injectFields() {

private void injectMethods() {
final var needsTry = beanReader.needsTryForMethodInjection();
final var bean = beanReader.prototype() ? "bean" : "$bean";
final var builder = beanReader.prototype() ? "builder" : "b";
final var bean = beanReader.registerProvider() ? "bean" : "$bean";
final var builder = beanReader.registerProvider() ? "builder" : "b";
if (needsTry) {
writer.indent(" try {").eol();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
@GeneratePrism(AOPFallback.class)
@GeneratePrism(Aspect.class)
@GeneratePrism(value = Aspect.Import.class, name = "AspectImportPrism")
@GeneratePrism(Assisted.class)
@GeneratePrism(AssistFactory.class)
@GeneratePrism(InjectModule.class)
@GeneratePrism(Factory.class)
@GeneratePrism(Singleton.class)
@GeneratePrism(Bean.class)
@GeneratePrism(Component.class)
@GeneratePrism(Component.Import.class)
@GeneratePrism(Prototype.class)
@GeneratePrism(Scope.class)
@GeneratePrism(Qualifier.class)
@GeneratePrism(Named.class)
@GeneratePrism(DependencyMeta.class)
@GeneratePrism(Factory.class)
@GeneratePrism(Generated.class)
@GeneratePrism(Inject.class)
@GeneratePrism(AOPFallback.class)
@GeneratePrism(Aspect.class)
@GeneratePrism(value = Aspect.Import.class, name = "AspectImportPrism")
@GeneratePrism(InjectModule.class)
@GeneratePrism(Lazy.class)
@GeneratePrism(Named.class)
@GeneratePrism(PreDestroy.class)
@GeneratePrism(Primary.class)
@GeneratePrism(Secondary.class)
@GeneratePrism(Profile.class)
@GeneratePrism(Prototype.class)
@GeneratePrism(Proxy.class)
@GeneratePrism(PreDestroy.class)
@GeneratePrism(DependencyMeta.class)
@GeneratePrism(Bean.class)
@GeneratePrism(QualifiedMap.class)
@GeneratePrism(Generated.class)
@GeneratePrism(Qualifier.class)
@GeneratePrism(RequiresBean.class)
@GeneratePrism(RequiresProperty.class)
@GeneratePrism(value = RequiresBean.Container.class, name = "RequiresBeanContainerPrism")
@GeneratePrism(value = RequiresProperty.Container.class, name = "RequiresPropertyContainerPrism")
@GeneratePrism(Profile.class)
@GeneratePrism(Singleton.class)
@GeneratePrism(Scope.class)
@GeneratePrism(Secondary.class)
package io.avaje.inject.generator;

import io.avaje.inject.*;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.avaje.inject.generator.models.valid.lazy;

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

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

import io.avaje.inject.Bean;
import io.avaje.inject.Factory;
import io.avaje.inject.Lazy;

@Lazy
@Factory
public class LazyFactory {

@Bean
Integer lazyInt() {
return 0;
}
}
11 changes: 11 additions & 0 deletions inject/src/main/java/io/avaje/inject/Lazy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.avaje.inject;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** Marks Singleton/Factory method beans to initialize lazily. */
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Lazy {}
30 changes: 27 additions & 3 deletions inject/src/main/java/io/avaje/inject/spi/Builder.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package io.avaje.inject.spi;

import io.avaje.inject.BeanScope;
import jakarta.inject.Provider;

import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

import io.avaje.inject.BeanScope;
import io.avaje.inject.MemoizedProvider;
import jakarta.inject.Provider;

/**
* Mutable builder object used when building a bean scope.
*/
Expand Down Expand Up @@ -76,6 +77,29 @@ static Builder newBuilder(Set<String> profiles, PropertyRequiresPlugin plugin, L
*/
<T> void registerProvider(Provider<T> provider);

/** Register a lazy provider into the context. */
default <T> void registerLazy(Provider<T> original) {
registerProvider(
new Provider<T>() {
Provider<T> delegate = this::firstTime;
boolean initialized;

@Override
public T get() {
return delegate.get();
}

private synchronized T firstTime() {
if (!initialized) {
T value = original.get();
delegate = () -> value;
initialized = true;
}
return delegate.get();
}
});
}

/**
* Register the bean instance into the context.
*
Expand Down

0 comments on commit 7d6a567

Please sign in to comment.