Skip to content

Generate Compile Time Proxies for certain @Lazy beans #831

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

Merged
merged 12 commits into from
Jun 16, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ public class LazyBean {

@Inject @Nullable AtomicBoolean initialized;

public LazyBean() throws Exception {}

@PostConstruct
void init(BeanScope scope) {
// note that nested test scopes will not be lazy
if (initialized != null) initialized.set(true);
}

void something() {}

public void other() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.example.myapp.lazy;

import java.util.concurrent.atomic.AtomicBoolean;

import org.example.myapp.aspect.MyTimed;
import org.jspecify.annotations.Nullable;

import io.avaje.inject.BeanScope;
import io.avaje.inject.Lazy;
import io.avaje.inject.PostConstruct;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;

@Lazy
@Singleton
@Named("single")
@MyTimed(name = "AOP")
public class LazyBeanAOP {

@Inject @Nullable AtomicBoolean initialized;

@PostConstruct
void init(BeanScope scope) {
// note that nested test scopes will not be lazy
if (initialized != null) initialized.set(true);
}

void something() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.jspecify.annotations.Nullable;

import io.avaje.inject.Bean;
import io.avaje.inject.BeanTypes;
import io.avaje.inject.Factory;
import io.avaje.inject.Lazy;
import jakarta.inject.Named;
Expand All @@ -22,6 +23,25 @@ LazyBean lazyInt(@Nullable AtomicBoolean initialized) throws Exception {
return new LazyBean();
}

@Bean
@Named("factory")
LazyInterface lazyInterFace(@Nullable AtomicBoolean initialized) throws Exception {

// note that nested test scopes will not be lazy
if (initialized != null) initialized.set(true);
return new LazyImpl(initialized);
}

@Bean
@BeanTypes(LazyInterface.class)
@Named("factoryBeanType")
LazyImpl factoryBeanType(@Nullable AtomicBoolean initialized) throws Exception {

// note that nested test scopes will not be lazy
if (initialized != null) initialized.set(true);
return new LazyImpl(initialized);
}

@Bean
@Named("factoryThrows")
LazyBean lazyIntThrows(@Nullable AtomicBoolean initialized) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.example.myapp.lazy;

import java.util.concurrent.atomic.AtomicBoolean;

import io.avaje.inject.BeanScope;
import io.avaje.inject.BeanTypes;
import io.avaje.inject.Lazy;
import io.avaje.inject.PostConstruct;
import io.github.resilience4j.core.lang.Nullable;
import jakarta.inject.Named;
import jakarta.inject.Singleton;

@Lazy
@Singleton
@Named("single")
@BeanTypes(LazyInterface.class)
public class LazyImpl implements LazyInterface {

AtomicBoolean initialized;

public LazyImpl(@Nullable AtomicBoolean initialized) {
this.initialized = initialized;
}

@PostConstruct
void init(BeanScope scope) {
// note that nested test scopes will not be lazy
if (initialized != null) initialized.set(true);
}

@Override
public void something() {}

@Override
public void otherThing() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.example.myapp.lazy;

public interface LazyInterface {

void something();

void otherThing();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.example.myapp.lazy;

import java.util.concurrent.atomic.AtomicBoolean;

import io.avaje.inject.BeanScope;
import io.avaje.inject.Lazy;
import io.avaje.inject.PostConstruct;
import io.github.resilience4j.core.lang.Nullable;
import jakarta.inject.Named;
import jakarta.inject.Singleton;

@Lazy
@Singleton
@Named("single")
public class OldLazy {

AtomicBoolean initialized;

public OldLazy(@Nullable AtomicBoolean initialized) {
this.initialized = initialized;
}

@PostConstruct
void init(BeanScope scope) {
// note that nested test scopes will not be lazy
if (initialized != null) initialized.set(true);
}

void something() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ void test() {
var initialized = new AtomicBoolean();
try (var scope = BeanScope.builder().beans(initialized).build()) {
assertThat(initialized).isFalse();
LazyBean lazy = scope.get(LazyBean.class, "single");
assertThat(initialized).isTrue();
var lazy = scope.get(LazyBean.class, "single");
assertThat(lazy).isNotNull();
assertThat(initialized).isFalse();
lazy.something();
assertThat(initialized).isTrue();

LazyBean lazyAgain = scope.get(LazyBean.class, "single");
var lazyAgain = scope.get(LazyBean.class, "single");
assertThat(lazyAgain).isSameAs(lazy);
}
}
Expand All @@ -29,9 +31,84 @@ void testFactory() {
var initialized = new AtomicBoolean();
try (var scope = BeanScope.builder().beans(initialized).build()) {
assertThat(initialized).isFalse();
LazyBean prov = scope.get(LazyBean.class, "factory");
var prov = scope.get(LazyBean.class, "factory");
assertThat(initialized).isFalse();
prov.something();
assertThat(initialized).isTrue();
assertThat(prov).isNotNull();
}
}

@Test
void testInterface() {
var initialized = new AtomicBoolean();
try (var scope = BeanScope.builder().beans(initialized).build()) {
assertThat(initialized).isFalse();
var lazy = scope.get(LazyInterface.class, "single");
assertThat(lazy).isNotNull();
assertThat(initialized).isFalse();
lazy.something();
assertThat(initialized).isTrue();

var lazyAgain = scope.get(LazyInterface.class, "single");
assertThat(lazyAgain).isSameAs(lazy);
}
}

@Test
void testFactoryInterface() {
var initialized = new AtomicBoolean();
try (var scope = BeanScope.builder().beans(initialized).build()) {
assertThat(initialized).isFalse();
var prov = scope.get(LazyInterface.class, "factory");
assertThat(initialized).isFalse();
prov.something();
assertThat(initialized).isTrue();
assertThat(prov).isNotNull();
}
}

@Test
void factoryBeanType() {
var initialized = new AtomicBoolean();
try (var scope = BeanScope.builder().beans(initialized).build()) {
assertThat(initialized).isFalse();
var prov = scope.get(LazyInterface.class, "factoryBeanType");
assertThat(initialized).isFalse();
prov.something();
assertThat(initialized).isTrue();
assertThat(prov).isNotNull();
}
}

@Test
void testAOP() {
var initialized = new AtomicBoolean();
try (var scope = BeanScope.builder().beans(initialized).build()) {
assertThat(initialized).isFalse();
var lazy = scope.get(LazyBeanAOP.class, "single");
assertThat(lazy).isNotNull();
assertThat(initialized).isFalse();
lazy.something();
assertThat(initialized).isTrue();

var lazyAgain = scope.get(LazyBeanAOP.class, "single");
assertThat(lazyAgain).isSameAs(lazy);
}
}

@Test
void testOldLazyBehavior() {
var initialized = new AtomicBoolean();
try (var scope = BeanScope.builder().beans(initialized).build()) {
assertThat(initialized).isFalse();
var lazy = scope.get(OldLazy.class, "single");
assertThat(lazy).isNotNull();
assertThat(initialized).isTrue();
lazy.something();

var lazyAgain = scope.get(OldLazy.class, "single");
assertThat(lazyAgain).isSameAs(lazy);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,12 @@ private void invokeSuper(Append writer, String simpleName) {
writer.append(")");
}

void writeSetupFields(Append writer) {
writer.append(" private final Method %s;", localName).eol();
void writeSetupFields(Append writer, boolean lazy) {
var isFinal = lazy ? "" : "final ";
writer.append(" private %sMethod %s;", isFinal, localName).eol();
for (AspectPair aspectPair : aspectPairs) {
String sn = aspectPair.annotationShortName();
writer.append(" private final MethodInterceptor %s%s;", localName, sn).eol();
writer.append(" private %sMethodInterceptor %s%s;", isFinal, localName, sn).eol();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package io.avaje.inject.generator;

import io.avaje.inject.generator.MethodReader.MethodParam;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;

import javax.lang.model.element.*;
import javax.lang.model.util.ElementFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;

import io.avaje.inject.generator.MethodReader.MethodParam;

final class AssistBeanReader {

Expand All @@ -34,12 +39,7 @@ final class AssistBeanReader {
this.beanType = beanType;
this.type = beanType.getQualifiedName().toString();
this.typeReader =
new TypeReader(
Optional.empty(),
UType.parse(beanType.asType()),
beanType,
importTypes,
false);
new TypeReader(List.of(), UType.parse(beanType.asType()), beanType, importTypes, false);

typeReader.process();
qualifierName = typeReader.name();
Expand Down
Loading