Skip to content

Determine Priority Annotations at Compile Time #852

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 7 commits into from
Jul 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ final class BeanReader {
private boolean suppressGeneratedImport;
private Set<UType> allUTypes;
private final boolean delayed;
private final Integer priority;

BeanReader(TypeElement beanType, boolean factory, boolean importedComponent) {
this.beanType = beanType;
Expand All @@ -69,6 +70,7 @@ final class BeanReader {
|| importedComponent && ProcessingContext.isImportedPrototype(actualType);
this.primary = PrimaryPrism.isPresent(actualType);
this.secondary = !primary && SecondaryPrism.isPresent(actualType);
this.priority = Util.priority(actualType);
var beanTypes =
BeanTypesPrism.getOptionalOn(actualType)
.map(BeanTypesPrism::value)
Expand Down Expand Up @@ -368,6 +370,8 @@ void buildRegister(Append writer) {
writer.append("asPrimary().");
} else if (secondary) {
writer.append("asSecondary().");
} else if (priority != null) {
writer.append("asPriority(%s).", priority);
}
writer.append("register(bean);").eol();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static io.avaje.inject.generator.Constants.CONDITIONAL_DEPENDENCY;
import static io.avaje.inject.generator.ProcessingContext.asElement;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -37,6 +38,7 @@ final class MethodReader {
private final boolean prototype;
private final boolean primary;
private final boolean secondary;
private final Integer priority;
private final boolean lazy;
private final boolean proxyLazy;
private final TypeElement lazyProxyType;
Expand Down Expand Up @@ -68,6 +70,7 @@ final class MethodReader {
prototype = PrototypePrism.isPresent(element);
primary = PrimaryPrism.isPresent(element);
secondary = SecondaryPrism.isPresent(element);
priority = Util.priority(element);
lazy = LazyPrism.isPresent(element) || LazyPrism.isPresent(element.getEnclosingElement());
conditions.readAll(element);
this.lazyProxyType = lazy ? Util.lazyProxy(element) : null;
Expand All @@ -76,6 +79,7 @@ final class MethodReader {
prototype = false;
primary = false;
secondary = false;
priority = null;
lazy = false;
this.proxyLazy = false;
this.lazyProxyType = null;
Expand Down Expand Up @@ -273,6 +277,8 @@ void builderAddBeanProvider(Append writer) {
writer.append(".asPrototype()");
} else if (secondary) {
writer.append(".asSecondary()");
} else if (priority != null) {
writer.append(".asPriority(%s)", priority);
}

if (proxyLazy) {
Expand Down Expand Up @@ -331,6 +337,8 @@ void builderBuildAddBean(Append writer) {
writer.append(".asPrimary()");
} else if (secondary) {
writer.append(".asSecondary()");
} else if (priority != null) {
writer.append(".asPriority(%s)", priority);
} else if (prototype) {
writer.append(".asPrototype()");
}
Expand Down Expand Up @@ -525,8 +533,15 @@ boolean isLazy() {
return lazy;
}

/**
* Use a Provider with Secondary or Priority annotation.
*
* <p> As a Provider, the bean won't be instantiated unless it is needed
* by being wired as the highest priority, or wired in a List/Set/Map, or
* by being accessed via {@link io.avaje.inject.BeanScope#list(Type)} etc.
*/
boolean isUseProviderForSecondary() {
return secondary && !optionalType && !Util.isProvider(returnTypeRaw);
return (secondary || priority != null) && !optionalType && !Util.isProvider(returnTypeRaw);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if it's got a @Priority also wire as Provider<T> which has the effect of only instantiating when needed.

Needed by .. being the highest priority or BeanScope#list or injecting a List/Set/Map of that type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, the appearance of a priority annotation invites the possibility that a higher priority bean might exist

}

boolean isPublic() {
Expand Down
21 changes: 19 additions & 2 deletions inject-generator/src/main/java/io/avaje/inject/generator/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,23 @@ static boolean hasNoArgConstructor(TypeElement beanType) {
}

public static String shortNameLazyProxy(TypeElement lazyProxyType) {
return shortName(lazyProxyType.getQualifiedName().toString())
.replace(".", "_"); }
return shortName(lazyProxyType.getQualifiedName().toString()).replace(".", "_");
}

static Integer priority(Element element) {
for (final var mirror : element.getAnnotationMirrors()) {
if (isPriorityAnnotation(mirror) && mirror.getElementValues().size() == 1) {
var value = mirror.getElementValues().values().iterator().next().getValue();
if (value instanceof Integer) {
var val = (Integer) value;
return val <= Integer.MIN_VALUE + 1 ? Integer.MIN_VALUE + 2 : val;
}
}
}
return null;
}

private static boolean isPriorityAnnotation(AnnotationMirror mirror) {
return mirror.getAnnotationType().asElement().getSimpleName().toString().contains("Priority");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.example.coffee.priority.base.BBasei;
import org.example.coffee.priority.base.BaseIface;
import org.example.coffee.priority.base.CBasei;
import org.example.coffee.priority.base.PriorityFactory.DBasei;
import org.junit.jupiter.api.Test;

import javax.annotation.Priority;
Expand All @@ -23,11 +24,12 @@ public class SystemContextTest {
public void getBeansByPriority() {
try (BeanScope context = BeanScope.builder().build()) {
final List<BaseIface> beans = context.listByPriority(BaseIface.class);
assertThat(beans).hasSize(3);
assertThat(beans).hasSize(4);

assertThat(beans.get(0)).isInstanceOf(CBasei.class);
assertThat(beans.get(1)).isInstanceOf(BBasei.class);
assertThat(beans.get(2)).isInstanceOf(ABasei.class);
assertThat(beans.get(3)).isInstanceOf(DBasei.class);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ void beanScope_all() {
assertThat(entry.qualifierName()).isEqualTo("B");
assertThat(entry.keys()).containsExactlyInAnyOrder(name(BSomei.class), name(Somei.class));
assertThat(entry.type()).isEqualTo(BSomei.class);
assertThat(entry.priority()).isEqualTo(0);
assertThat(entry.priority()).isEqualTo(1);
assertThat(entry.bean()).isEqualTo(context.get(Somei.class, "B"));
assertThat(entry.bean()).isEqualTo(context.get(BSomei.class));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.example.coffee.priority.base;

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

@Factory
public class PriorityFactory {

@Bean
@Priority(69)
BaseIface iface() {
return new DBasei();
}

public static class DBasei implements BaseIface {

@Override
public String other() {
return "b";
}
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
package org.example.coffee.priority.base;

import io.avaje.inject.xtra.ApplicationScope;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import org.example.coffee.priority.base.PriorityFactory.DBasei;
import org.junit.jupiter.api.Test;

public class PriorityTest {
import io.avaje.inject.xtra.ApplicationScope;

class PriorityTest {

@Test
void listByPriority() {
final List<BaseIface> sorted = ApplicationScope.listByPriority(BaseIface.class);
assertExpectedOrder(sorted);
}

@Test
void testGet() {
assertThat(ApplicationScope.get(BaseIface.class)).isInstanceOf(CBasei.class);
}

private void assertExpectedOrder(List<BaseIface> sorted) {
assertThat(sorted.get(0)).isInstanceOf(CBasei.class);
assertThat(sorted.get(1)).isInstanceOf(BBasei.class);
assertThat(sorted.get(2)).isInstanceOf(ABasei.class);
assertThat(sorted.get(3)).isInstanceOf(DBasei.class);
}
}
8 changes: 4 additions & 4 deletions inject/src/main/java/io/avaje/inject/BeanEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ public interface BeanEntry {
/**
* Priority of externally supplied bean.
*/
int SUPPLIED = 2;
int SUPPLIED = Integer.MIN_VALUE;

/**
* Priority of <code>@Primary</code> bean.
*/
int PRIMARY = 1;
int PRIMARY = Integer.MIN_VALUE + 1;

/**
* Priority of normal bean.
Expand All @@ -27,7 +27,7 @@ public interface BeanEntry {
/**
* Priority of <code>@Secondary</code> bean.
*/
int SECONDARY = -1;
int SECONDARY = Integer.MAX_VALUE;

/**
* Return the bean name.
Expand All @@ -45,7 +45,7 @@ public interface BeanEntry {
Class<?> type();

/**
* Return the priority indicating if the bean is Supplied Primary, Normal or Secondary.
* Return the wiring priority of the bean.
*/
int priority();

Expand Down
24 changes: 12 additions & 12 deletions inject/src/main/java/io/avaje/inject/BeanScope.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,21 +198,21 @@ default <T> T get(Type type) {
*/
<T> List<T> list(Type type);

/**
* Return the list of beans that implement the interface sorting by priority.
*/
<T> List<T> listByPriority(Class<T> type);
/** Return the list of beans that implement the class sorting by priority. */
default <T> List<T> listByPriority(Class<T> type) {
return listByPriority((Type) type);
}

/** Return the list of beans that implement the type sorting by priority. */
<T> List<T> listByPriority(Type type);

/**
* Return the beans that implement the interface sorting by the priority annotation used.
* <p>
* The priority annotation will typically be either <code>javax.annotation.Priority</code>
* or <code>jakarta.annotation.Priority</code>.
*
* @param type The interface type of the beans to return
* @param priority The priority annotation used to sort the beans
* @deprecated use {@link #listByPriority(Class)}
*/
<T> List<T> listByPriority(Class<T> type, Class<? extends Annotation> priority);
@Deprecated(forRemoval = true)
default <T> List<T> listByPriority(Class<T> type, Class<? extends Annotation> priority) {
return listByPriority(type);
}

/**
* Return the beans for this type mapped by their qualifier name.
Expand Down
24 changes: 12 additions & 12 deletions inject/src/main/java/io/avaje/inject/Priority.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
package io.avaje.inject;

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.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* The <code>Priority</code> annotation can be applied to classes to indicate
* in what order they should be returned via @{@link BeanScope#listByPriority(Class)}.
* <p>
* Beans can be returned using other Priority annotation such as <code>javax.annotation.Priority</code>
* or any custom priority annotation that has an <code>int value()</code> attribute.
* </p>
* The <code>Priority</code> annotation can be applied to classes to indicate the wiring priority of
* a bean to resolve cases where multiple beans of the same type exist.
*
* <p>Beans can be returned using other Priority annotation such as <code>
* jakartak.annotation.Priority
* </code> or any custom priority annotation that has an <code>int value()</code> attribute.
*
* @see BeanScope#listByPriority(Class)
* @see BeanScope#listByPriority(Class, Class)
* @see BeanScope#listByPriority(Type)
*/
@Documented
@Retention(RUNTIME)
@Target(TYPE)
@Target({TYPE, METHOD})
public @interface Priority {
int value();
}
10 changes: 8 additions & 2 deletions inject/src/main/java/io/avaje/inject/spi/Builder.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,22 @@ default boolean isBeanAbsent(Type... types) {

/**
* Register the next bean as having Primary priority.
* Highest priority, will be used over any other matching beans.
* Highest priority, wired over any other matching beans.
*/
Builder asPrimary();

/**
* Register the next bean as having Secondary priority.
* Lowest priority, only used if no other matching beans are available.
* Lowest priority, wired when no other matching beans are available.
*/
Builder asSecondary();

/**
* Register the next bean as having the given priority. Wired only if no other higher priority
* matching beans are available.
*/
Builder asPriority(int priority);

/**
* Register the next bean as having Prototype scope.
*/
Expand Down
22 changes: 19 additions & 3 deletions inject/src/main/java/io/avaje/inject/spi/DBeanMap.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package io.avaje.inject.spi;

import io.avaje.inject.BeanEntry;
import io.avaje.inject.BeanScope;
import jakarta.inject.Provider;
import static java.util.stream.Collectors.toList;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

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

/**
* Map of types (class types, interfaces and annotations) to a DContextEntry where the
* entry holds a list of bean instances for that type.
Expand Down Expand Up @@ -121,6 +124,19 @@ <T> T get(Type type, String name) {
return (T) entry.get(name, currentModule);
}

public <T> List<T> listByPriority(Type type) {

DContextEntry entry = beans.get(type.getTypeName());
if (entry == null) {
return List.of();
}

return entry.entries().stream()
.sorted(Comparator.comparingInt(DContextEntryBean::priority))
.map(e -> (T) e.bean())
.collect(toList());
}

@SuppressWarnings("unchecked")
<T> Provider<T> provider(Type type, String name) {
DContextEntry entry = beans.get(type.getTypeName());
Expand Down
Loading