Add core-di: constructor vs setter vs field injection and @Autowired candidate resolution on Boot 4.1
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JoVmf2fWvcpoXndcDSwRf7
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package com.ankurm.coredi;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* Only used by scripts/capture-failure-analysis.sh: it starts a real Boot application so the
|
||||
* "APPLICATION FAILED TO START" report for a circular dependency can be captured verbatim.
|
||||
* Everything else in this module runs from the tests.
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = "com.ankurm.coredi.boot")
|
||||
public class DiApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
var ctx = SpringApplication.run(DiApplication.class, args);
|
||||
var cycleBeans = java.util.Arrays.stream(ctx.getBeanDefinitionNames())
|
||||
.filter(n -> n.startsWith("bootCycles.")).sorted().toList();
|
||||
System.out.println("STARTED. Beans from the cycle: " + cycleBeans);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.ankurm.coredi.boot;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** Three ways to start the same application with a cycle in it. Pick with --spring.profiles.active. */
|
||||
public final class BootCycles {
|
||||
|
||||
private BootCycles() {
|
||||
}
|
||||
|
||||
@Component
|
||||
@Profile("ctor-cycle")
|
||||
public static class CtorOne {
|
||||
public CtorOne(CtorTwo two) {
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
@Profile("ctor-cycle")
|
||||
public static class CtorTwo {
|
||||
public CtorTwo(CtorOne one) {
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
@Profile("field-cycle")
|
||||
public static class FieldOne {
|
||||
@Autowired
|
||||
FieldTwo two;
|
||||
}
|
||||
|
||||
@Component
|
||||
@Profile("field-cycle")
|
||||
public static class FieldTwo {
|
||||
@Autowired
|
||||
FieldOne one;
|
||||
}
|
||||
|
||||
@Component
|
||||
@Profile("lazy-cycle")
|
||||
public static class LazyOne {
|
||||
public LazyOne(@Lazy LazyTwo two) {
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
@Profile("lazy-cycle")
|
||||
public static class LazyTwo {
|
||||
public LazyTwo(LazyOne one) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/** One bean using all three styles at once, only to make Spring's order of operations visible. */
|
||||
public class AllThreeStyles {
|
||||
|
||||
@Autowired
|
||||
private Notifier fieldDependency;
|
||||
|
||||
private PaymentGateway setterDependency;
|
||||
|
||||
private final Notifier constructorDependency;
|
||||
|
||||
public AllThreeStyles(Notifier constructorDependency) {
|
||||
this.constructorDependency = constructorDependency;
|
||||
Trace.log("constructor : constructor arg=" + describe(constructorDependency)
|
||||
+ ", field=" + describe(fieldDependency) + ", setter=" + describe(setterDependency));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setSetterDependency(PaymentGateway gateway) {
|
||||
this.setterDependency = gateway;
|
||||
Trace.log("setter : setter arg=" + describe(gateway) + ", field=" + describe(fieldDependency));
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void afterInjection() {
|
||||
Trace.log("@PostConstruct: constructor=" + describe(constructorDependency)
|
||||
+ ", field=" + describe(fieldDependency) + ", setter=" + describe(setterDependency));
|
||||
}
|
||||
|
||||
private static String describe(Object o) {
|
||||
return o == null ? "null" : "set";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/** The two collaborators, registered once so every service variant can be pointed at them. */
|
||||
@Configuration
|
||||
public class BaseBeans {
|
||||
|
||||
@Bean
|
||||
PaymentGateway paymentGateway() {
|
||||
return new LoggingPaymentGateway();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Notifier notifier() {
|
||||
return new RecordingNotifier();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
/**
|
||||
* Constructor injection. One constructor, so {@code @Autowired} is not needed. Both fields are
|
||||
* {@code final}, and the class cannot be instantiated without its collaborators.
|
||||
*/
|
||||
public class ConstructorOrderService implements OrderService {
|
||||
|
||||
private final PaymentGateway gateway;
|
||||
private final Notifier notifier;
|
||||
|
||||
public ConstructorOrderService(PaymentGateway gateway, Notifier notifier) {
|
||||
this.gateway = gateway;
|
||||
this.notifier = notifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String place(String orderId, int cents) {
|
||||
String receipt = gateway.charge(orderId, cents);
|
||||
notifier.send(receipt);
|
||||
return receipt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
public class CtorA {
|
||||
public CtorA(CtorB b) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
public class CtorB {
|
||||
public CtorB(CtorA a) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class FieldA {
|
||||
@Autowired
|
||||
public FieldB b;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class FieldB {
|
||||
@Autowired
|
||||
public FieldA a;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/** Field injection. Shortest to write; the only way to supply the fields outside Spring is reflection. */
|
||||
public class FieldOrderService implements OrderService {
|
||||
|
||||
@Autowired
|
||||
private PaymentGateway gateway;
|
||||
|
||||
@Autowired
|
||||
private Notifier notifier;
|
||||
|
||||
@Override
|
||||
public String place(String orderId, int cents) {
|
||||
String receipt = gateway.charge(orderId, cents);
|
||||
notifier.send(receipt);
|
||||
return receipt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/** Uses a field-injected collaborator in its constructor, before Spring has had a chance to set it. */
|
||||
public class FieldTrapService {
|
||||
|
||||
@Autowired
|
||||
private PaymentGateway gateway;
|
||||
|
||||
private final String warmup;
|
||||
|
||||
public FieldTrapService() {
|
||||
this.warmup = gateway.charge("warm-up", 0);
|
||||
}
|
||||
|
||||
public String warmup() {
|
||||
return warmup;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
/** The cycle is still there; {@code @Lazy} turns one edge into a proxy that resolves on first use. */
|
||||
public class LazyA {
|
||||
|
||||
public final LazyB b;
|
||||
|
||||
public LazyA(@Lazy LazyB b) {
|
||||
this.b = b;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
public class LazyB {
|
||||
|
||||
final LazyA a;
|
||||
|
||||
public LazyB(LazyA a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public String hello() {
|
||||
return "LazyB reached through LazyA's proxy";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LoggingPaymentGateway implements PaymentGateway {
|
||||
|
||||
private final List<String> charges = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public String charge(String orderId, int cents) {
|
||||
String receipt = "charged " + cents + " cents for " + orderId;
|
||||
charges.add(receipt);
|
||||
return receipt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> charges() {
|
||||
return charges;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface Notifier {
|
||||
|
||||
void send(String message);
|
||||
|
||||
List<String> sent();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/** Setter injection's honest use: a dependency that is allowed to be absent. */
|
||||
public class OptionalAudit {
|
||||
|
||||
private Notifier notifier;
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setNotifier(Notifier notifier) {
|
||||
this.notifier = notifier;
|
||||
}
|
||||
|
||||
public String status() {
|
||||
return notifier == null ? "no notifier configured, auditing silently" : "notifier present";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
public interface OrderService {
|
||||
|
||||
/** Charges the customer, then tells them. The order matters: see the setter and field variants. */
|
||||
String place(String orderId, int cents);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
/** The collaborator every order service needs. */
|
||||
public interface PaymentGateway {
|
||||
|
||||
String charge(String orderId, int cents);
|
||||
|
||||
/** Every charge that really happened, so a test can prove a half-built service still took money. */
|
||||
java.util.List<String> charges();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RecordingNotifier implements Notifier {
|
||||
|
||||
private final List<String> sent = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void send(String message) {
|
||||
sent.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> sent() {
|
||||
return sent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/** Setter injection. The object exists, half-built, before Spring calls the setters. */
|
||||
public class SetterOrderService implements OrderService {
|
||||
|
||||
private PaymentGateway gateway;
|
||||
private Notifier notifier;
|
||||
|
||||
@Autowired
|
||||
public void setGateway(PaymentGateway gateway) {
|
||||
this.gateway = gateway;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setNotifier(Notifier notifier) {
|
||||
this.notifier = notifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String place(String orderId, int cents) {
|
||||
String receipt = gateway.charge(orderId, cents);
|
||||
notifier.send(receipt);
|
||||
return receipt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** A shared event log so a test can print the order in which Spring touches a bean. */
|
||||
public final class Trace {
|
||||
|
||||
private static final List<String> EVENTS = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private Trace() {
|
||||
}
|
||||
|
||||
public static void log(String event) {
|
||||
EVENTS.add(event);
|
||||
}
|
||||
|
||||
public static List<String> drain() {
|
||||
List<String> copy;
|
||||
synchronized (EVENTS) {
|
||||
copy = new ArrayList<>(EVENTS);
|
||||
EVENTS.clear();
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/** The fix: say which constructor Spring should use. */
|
||||
public class TwoConstructorsAnnotated {
|
||||
|
||||
private final PaymentGateway gateway;
|
||||
|
||||
@Autowired
|
||||
public TwoConstructorsAnnotated(PaymentGateway gateway) {
|
||||
this.gateway = gateway;
|
||||
Trace.log("annotated constructor used");
|
||||
}
|
||||
|
||||
public TwoConstructorsAnnotated(PaymentGateway gateway, Notifier notifier) {
|
||||
this.gateway = gateway;
|
||||
Trace.log("two-arg constructor used");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
/** Two constructors, no no-argument one, none marked {@code @Autowired}: Spring cannot choose. */
|
||||
public class TwoConstructorsNoDefault {
|
||||
|
||||
public TwoConstructorsNoDefault(PaymentGateway gateway) {
|
||||
}
|
||||
|
||||
public TwoConstructorsNoDefault(PaymentGateway gateway, Notifier notifier) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ankurm.coredi.injection;
|
||||
|
||||
/** Two public constructors, none marked {@code @Autowired}: Spring falls back to the no-argument one. */
|
||||
public class TwoConstructorsService {
|
||||
|
||||
private PaymentGateway gateway;
|
||||
|
||||
public TwoConstructorsService() {
|
||||
Trace.log("no-arg constructor used");
|
||||
}
|
||||
|
||||
public TwoConstructorsService(PaymentGateway gateway) {
|
||||
this.gateway = gateway;
|
||||
Trace.log("one-arg constructor used");
|
||||
}
|
||||
|
||||
public boolean hasGateway() {
|
||||
return gateway != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.ankurm.coredi.injection.Notifier;
|
||||
|
||||
/** Receives every Notifier three ways: as a List, a Map keyed by bean name, and a Set. */
|
||||
public class Broadcaster {
|
||||
|
||||
public final List<Notifier> asList;
|
||||
public final Map<String, Notifier> asMap;
|
||||
public final Set<Notifier> asSet;
|
||||
|
||||
public Broadcaster(List<Notifier> asList, Map<String, Notifier> asMap, Set<Notifier> asSet) {
|
||||
this.asList = asList;
|
||||
this.asMap = asMap;
|
||||
this.asSet = asSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import com.ankurm.coredi.injection.PaymentGateway;
|
||||
|
||||
public class CheckoutFast {
|
||||
|
||||
private final PaymentGateway gateway;
|
||||
|
||||
public CheckoutFast(@Fast PaymentGateway gateway) {
|
||||
this.gateway = gateway;
|
||||
}
|
||||
|
||||
public String gatewayClass() {
|
||||
return gateway.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import com.ankurm.coredi.injection.PaymentGateway;
|
||||
|
||||
/** The parameter name is the only hint: it matches the bean name {@code paypalGateway}. */
|
||||
public class CheckoutNamed {
|
||||
|
||||
private final PaymentGateway paypalGateway;
|
||||
|
||||
public CheckoutNamed(PaymentGateway paypalGateway) {
|
||||
this.paypalGateway = paypalGateway;
|
||||
}
|
||||
|
||||
public String gatewayClass() {
|
||||
return paypalGateway.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import com.ankurm.coredi.injection.PaymentGateway;
|
||||
|
||||
/** Asks for a PaymentGateway and says nothing more. */
|
||||
public class CheckoutPlain {
|
||||
|
||||
private final PaymentGateway gateway;
|
||||
|
||||
public CheckoutPlain(PaymentGateway gateway) {
|
||||
this.gateway = gateway;
|
||||
}
|
||||
|
||||
public String gatewayClass() {
|
||||
return gateway.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import com.ankurm.coredi.injection.PaymentGateway;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
public class CheckoutQualified {
|
||||
|
||||
private final PaymentGateway gateway;
|
||||
|
||||
public CheckoutQualified(@Qualifier("paypalGateway") PaymentGateway gateway) {
|
||||
this.gateway = gateway;
|
||||
}
|
||||
|
||||
public String gatewayClass() {
|
||||
return gateway.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import com.ankurm.coredi.injection.RecordingNotifier;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
@Order(2)
|
||||
public class EmailNotifier extends RecordingNotifier {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import org.springframework.context.annotation.Fallback;
|
||||
|
||||
/** Wins only when nothing else qualifies. */
|
||||
@Fallback
|
||||
public class FallbackGateway extends PaypalGateway {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
/** A custom qualifier: a meta-annotation over {@code @Qualifier}, so call sites do not repeat a string. */
|
||||
@Target({ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Qualifier
|
||||
public @interface Fast {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
@Fast
|
||||
public class FastGateway extends StripeGateway {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** The generic type argument is part of the injection point: only Handler<String> beans arrive. */
|
||||
public class GenericConsumer {
|
||||
|
||||
public final List<Handler<String>> stringHandlers;
|
||||
public final Handler<Integer> integerHandler;
|
||||
|
||||
public GenericConsumer(List<Handler<String>> stringHandlers, Handler<Integer> integerHandler) {
|
||||
this.stringHandlers = stringHandlers;
|
||||
this.integerHandler = integerHandler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
public interface Handler<T> {
|
||||
|
||||
String handle(T input);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
public class IntegerHandler implements Handler<Integer> {
|
||||
|
||||
@Override
|
||||
public String handle(Integer input) {
|
||||
return "integer:" + input;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.ankurm.coredi.injection.Notifier;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/** Every way to say "this dependency may not exist", one nested class each. */
|
||||
public final class OptionalConsumers {
|
||||
|
||||
private OptionalConsumers() {
|
||||
}
|
||||
|
||||
public static class Required {
|
||||
public Required(Notifier notifier) {
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithOptional {
|
||||
public final Optional<Notifier> notifier;
|
||||
|
||||
public WithOptional(Optional<Notifier> notifier) {
|
||||
this.notifier = notifier;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithNullable {
|
||||
public final Notifier notifier;
|
||||
|
||||
public WithNullable(@Nullable Notifier notifier) {
|
||||
this.notifier = notifier;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithProvider {
|
||||
public final ObjectProvider<Notifier> provider;
|
||||
|
||||
public WithProvider(ObjectProvider<Notifier> provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithRequiredFalse {
|
||||
public Notifier notifier;
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setNotifier(Notifier notifier) {
|
||||
this.notifier = notifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import com.ankurm.coredi.injection.LoggingPaymentGateway;
|
||||
|
||||
public class PaypalGateway extends LoggingPaymentGateway {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
public interface Plugin {
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/** What a collection injection point does when zero beans of the element type exist. */
|
||||
public final class PluginHosts {
|
||||
|
||||
private PluginHosts() {
|
||||
}
|
||||
|
||||
public static class Plain {
|
||||
public Plain(List<Plugin> plugins) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Same collection, but injected into a field: no constructor "empty collection" fallback applies. */
|
||||
public static class FieldInjected {
|
||||
@Autowired
|
||||
public List<Plugin> plugins;
|
||||
}
|
||||
|
||||
/** Same collection, injected through a required setter. */
|
||||
public static class SetterInjected {
|
||||
public List<Plugin> plugins;
|
||||
|
||||
@Autowired
|
||||
public void setPlugins(List<Plugin> plugins) {
|
||||
this.plugins = plugins;
|
||||
}
|
||||
}
|
||||
|
||||
/** Two constructors, one marked: the "single constructor" fallback for empty collections does not apply. */
|
||||
public static class TwoConstructors {
|
||||
public List<Plugin> plugins;
|
||||
|
||||
@Autowired
|
||||
public TwoConstructors(List<Plugin> plugins) {
|
||||
this.plugins = plugins;
|
||||
}
|
||||
|
||||
public TwoConstructors() {
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithOptional {
|
||||
public final Optional<List<Plugin>> plugins;
|
||||
|
||||
public WithOptional(Optional<List<Plugin>> plugins) {
|
||||
this.plugins = plugins;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithNullable {
|
||||
public final List<Plugin> plugins;
|
||||
|
||||
public WithNullable(@Nullable List<Plugin> plugins) {
|
||||
this.plugins = plugins;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithProvider {
|
||||
public final List<Plugin> plugins;
|
||||
|
||||
public WithProvider(ObjectProvider<Plugin> provider) {
|
||||
this.plugins = provider.orderedStream().toList();
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithRequiredFalse {
|
||||
public List<Plugin> plugins = List.of();
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setPlugins(List<Plugin> plugins) {
|
||||
this.plugins = plugins;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
@Primary
|
||||
public class PrimaryStripeGateway extends StripeGateway {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
|
||||
@Priority(1)
|
||||
public class PriorityOneGateway extends StripeGateway {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import jakarta.annotation.Priority;
|
||||
|
||||
@Priority(2)
|
||||
public class PriorityTwoGateway extends PaypalGateway {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import com.ankurm.coredi.injection.RecordingNotifier;
|
||||
|
||||
/** No {@code @Order}: sorts after every ordered bean. */
|
||||
public class PushNotifier extends RecordingNotifier {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import com.ankurm.coredi.injection.PaymentGateway;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
/** {@code @Resource} matches by name first; the field name is the default name. */
|
||||
public class ResourceConsumer {
|
||||
|
||||
@Resource
|
||||
PaymentGateway paypalGateway;
|
||||
|
||||
@Resource(name = "stripeGateway")
|
||||
PaymentGateway whateverIWantToCallIt;
|
||||
|
||||
public String describe() {
|
||||
return "paypalGateway field -> " + paypalGateway.getClass().getSimpleName()
|
||||
+ ", whateverIWantToCallIt field -> " + whateverIWantToCallIt.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import com.ankurm.coredi.injection.RecordingNotifier;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
@Order(1)
|
||||
public class SmsNotifier extends RecordingNotifier {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
public class StringHandler implements Handler<String> {
|
||||
|
||||
@Override
|
||||
public String handle(String input) {
|
||||
return "string:" + input;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.ankurm.coredi.resolution;
|
||||
|
||||
import com.ankurm.coredi.injection.LoggingPaymentGateway;
|
||||
|
||||
public class StripeGateway extends LoggingPaymentGateway {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
spring:
|
||||
application:
|
||||
name: core-di
|
||||
main:
|
||||
banner-mode: off
|
||||
Reference in New Issue
Block a user