Complete JavaFX MVVM examples, bindings and library documentation
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
# Injection
|
||||
|
||||
## Introduction
|
||||
|
||||
`injection` is a small container. You mark concrete classes, it scans a package, keeps one shared instance of each scanned class, and fills dependencies by type. [`fx`](../fx/README.md) uses it for controllers, components, and converters. You can use it without JavaFX.
|
||||
|
||||
The worked example is the person desk in `fx-example`, package `de.neitzel.fx.example`. This guide uses that desk for every feature the desk exercises, then covers the container rules the desk does not hit.
|
||||
|
||||
## Quick start
|
||||
|
||||
Depend on the module. It brings Reflections and `jakarta.inject-api`. The parent artifact is `de.neitzel.lib:neitzellib:1.0-SNAPSHOT`. Java 21.
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>de.neitzel.lib</groupId>
|
||||
<artifactId>injection</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Mark a class with `jakarta.inject.Singleton` or `jakarta.inject.Named`, and take dependencies with `jakarta.inject.Inject`. Open a context on the package that contains those classes.
|
||||
|
||||
The desk does it this way. `DeskApp` names the scan root and opens the context. `ViewLoader` then asks that context for the controller, the forms, and the converters. `AgeSuffix` is a `@Named` service. `AgeConverter` takes that service in its constructor.
|
||||
|
||||
```java
|
||||
ApplicationContext context = new ApplicationContext(DeskApp.class);
|
||||
LoadedView loaded = ViewLoader.load(
|
||||
DeskController.class.getResource("Desk.fxml"), DeskApp.sample(), context);
|
||||
```
|
||||
|
||||
`DeskApp.start` is that load, then the scene. The class carries `@Config(basePackage = "de.neitzel.fx.example")`.
|
||||
|
||||
```java
|
||||
@Named
|
||||
public class AgeSuffix {
|
||||
public String prefix() {
|
||||
return "years ";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`AgeConverter` in `fx-example` is the full class. It keeps the suffix and implements `Converter`. `getComponent` returns the same instance on every call. `AgeConverter` and `AgeSuffix` are each created once, and every use of the converter shares that one suffix.
|
||||
|
||||
## Mark a component
|
||||
|
||||
The sections from here on are the container's rules. The desk is the example where a rule shows up on the screen. Rules the desk does not hit are stated on their own.
|
||||
|
||||
The scanner looks for `jakarta.inject.Singleton` and `jakarta.inject.Named` on the class. Both annotations mean the same thing here: the class is a component, and it is stored as a singleton. `@Named`'s value is never read. There is no lookup by name.
|
||||
|
||||
`AgeSuffix` carries `@Named`. `AgeConverter`, `StreetConverter`, `AddressForm`, `VisitForm`, `NoteForm`, `VisitRow`, `DeskController`, `FormHeading`, `StreetPolicy`, and `VisitedOnConverter` carry `@Singleton`. The age field still shows `years 36`, which is how the desk shows that `@Named` was found.
|
||||
|
||||
Put the annotation on each concrete class you want created. The scan uses Reflections' "annotated types" query, which also returns subtypes of an annotated type that sit inside the scan root. An unannotated subclass of a `@Singleton` class can therefore be picked up. Mark the classes you intend to create.
|
||||
|
||||
The scanner does not read `@Qualifier`, `Provider`, `Instance`, producer methods, `@PostConstruct`, or `@PreDestroy`. `@Named` on a parameter or a field is not a qualifier. Injection matches the raw class after generics are erased, so a `List<Address>` is looked up as `List`.
|
||||
|
||||
There is no `@Component` stereotype. Comments on `Scope` and `@Config` still mention one. Discovery is `@Singleton` and `@Named`.
|
||||
|
||||
## Where the container injects
|
||||
|
||||
For each component the scanner chooses one constructor, then the `@Inject` fields, then the `@Inject` setters. Members are taken from that class only. Inherited fields and methods are left alone. The chosen constructor and the injected members are made accessible, so they may be non-public.
|
||||
|
||||
Constructor choice:
|
||||
|
||||
1. More than one constructor annotated with `@Inject` makes the component invalid. It is left out of the context.
|
||||
2. Exactly one `@Inject` constructor is used, even when other constructors exist.
|
||||
3. Otherwise the single declared constructor is used. A compiler-generated public no-arg constructor counts. Parameters are allowed, and `@Inject` is not required on that lone constructor.
|
||||
4. Otherwise a zero-argument constructor is used when one exists.
|
||||
5. Otherwise the component is invalid.
|
||||
|
||||
After `new`, each `@Inject` field is set, then each valid `@Inject` setter is called. A setter is an `@Inject` method that is `void`, has one parameter, and whose name starts with `set`. An `@Inject` method of another shape is skipped and recorded as an error. The component can still be created.
|
||||
|
||||
The desk uses all three places:
|
||||
|
||||
| Place | Desk class | What is injected |
|
||||
| --- | --- | --- |
|
||||
| Constructor | `AgeConverter` | `AgeSuffix` |
|
||||
| Constructor | `VisitRow` | `DeskController` |
|
||||
| Field | `StreetConverter` | `StreetPolicy`, on a private field, after the no-arg constructor |
|
||||
| Setter | `AddressForm.setHeading` | `FormHeading` |
|
||||
|
||||
`AddressForm.initialize` runs later, when FXML loads the form, and copies `heading.text()` onto the label. The setter has already run by then. The home form shows the word `Address`.
|
||||
|
||||
`VisitRow` is created again for every table row. Each row receives the same `DeskController`, because the controller is a singleton and dependencies are loaded with `getComponent`.
|
||||
|
||||
## Ask for an instance
|
||||
|
||||
```java
|
||||
public <T> T getComponent(Class<? extends T> type)
|
||||
public <T> T createComponent(Class<? extends T> type)
|
||||
public <T> void addSingleton(Class<? extends T> type, T instance)
|
||||
```
|
||||
|
||||
`getComponent` returns the cached instance. The first call creates it and stores it. Later calls return that object.
|
||||
|
||||
`createComponent` always builds a new instance and does not store it. Dependencies of that instance still come from `getComponent`, so they are the shared singletons. `fx` uses this for a second component of the same class in one load, and for every repeated row and table row. The home `AddressForm` is the cached instance. Each `VisitForm` and each `VisitRow` is a new instance, and each of them still sees the one `DeskController` and the one `StreetConverter`.
|
||||
|
||||
A type the context does not know produces `IllegalArgumentException` with the message `No component found for type:` and the class name. A failure while calling the constructor or writing a member is wrapped as `RuntimeException` whose message starts with `Fehler beim Erstellen der Instanz für`, with the original failure as the cause.
|
||||
|
||||
The context is a plain map. It is not safe to share across threads without external locking. There is no shutdown and no list-all method.
|
||||
|
||||
## Choose the scan root
|
||||
|
||||
```java
|
||||
public ApplicationContext(String basePackage)
|
||||
public ApplicationContext(Class<?> configClass)
|
||||
```
|
||||
|
||||
The string constructor scans that package and its subpackages. The desk uses the class constructor:
|
||||
|
||||
```java
|
||||
ApplicationContext context = new ApplicationContext(DeskApp.class);
|
||||
```
|
||||
|
||||
`@Config` on the class supplies the package when `basePackage` is not empty. `DeskApp` sets it to `de.neitzel.fx.example`, which is also the class's own package. Subpackages are included, so `de.neitzel.fx.example.view` is scanned. Without the annotation, or with `basePackage` left as `""`, the scan root is `configClass.getPackageName()`. The annotation is not inherited, so a superclass `@Config` is ignored.
|
||||
|
||||
`@Config` only chooses the scan root. It does not bind properties.
|
||||
|
||||
Scan failures are not thrown from either constructor. A package full of broken components still produces a context. Those types are simply absent, and `getComponent` fails later with `No component found for type:`. To see why, scan with `ComponentScanner` and read `getErrors()`.
|
||||
|
||||
## Types you can inject
|
||||
|
||||
`getComponent` accepts the concrete class. It also accepts a superclass or an interface when exactly one scanned component has that super type. The context stores one `ComponentData` under the concrete class and under each such unique super type, and both keys return the same instance.
|
||||
|
||||
A super type implemented by two or more components is ambiguous. It is not a key. `getComponent` on it throws `No component found for type:`. The concrete classes stay available. The desk does not use this: every injection point names a concrete class.
|
||||
|
||||
Super types considered for a component are its superclass chain, stopping before `Object`, and the interfaces declared on that class. Interfaces declared only on a superclass are not included. The class itself is not registered twice under this rule.
|
||||
|
||||
`ComponentScanner` is the way to see the result before you build a context:
|
||||
|
||||
```java
|
||||
ComponentScanner scanner = new ComponentScanner("de.neitzel.fx.example");
|
||||
scanner.getInstantiableComponents(); // concrete classes and unique super types
|
||||
scanner.getNotUniqueTypes(); // super types with two or more components
|
||||
scanner.getErrors(); // messages, empty when the scan is clean
|
||||
```
|
||||
|
||||
The lists and the map are the scanner's own collections.
|
||||
|
||||
A cycle never enters the map. Each side is reported as a missing dependency.
|
||||
|
||||
## When creation fails
|
||||
|
||||
`new ApplicationContext(...)` stays quiet. Inspect `ComponentScanner.getErrors()` when a type you expected is missing. The messages are:
|
||||
|
||||
| Situation | Message |
|
||||
| --- | --- |
|
||||
| Two or more `@Inject` constructors | `Multiple @Inject constructors found for ` plus the class name |
|
||||
| Several constructors, none of them `@Inject`, and none of them zero-argument | `No @Inject constructor and no no-arg constructor found for ` plus the class name |
|
||||
| `@Inject` method that is not a one-argument `void` method named `set...` | `Invalid @Inject method detected in ` plus the class name, `: `, and the method name. The method is skipped |
|
||||
| A dependency never becomes a known type | `Component cannot be instantiated: ` plus the class name, then ` \| Missing constructor dependencies: `, ` \| Missing field dependencies: `, or ` \| Missing setter dependencies: `, each followed by the list of classes |
|
||||
| Every dependency is either known or an ambiguous super type | the same prefix plus ` \| Conflicting super types detected` |
|
||||
|
||||
A class with a single `String` constructor and no `@Inject` takes that constructor (rule 3 above). `String` is not a component, so the class is omitted and the error names `class java.lang.String` as the missing constructor dependency.
|
||||
|
||||
## Register an instance yourself
|
||||
|
||||
`addSingleton` stores an object you already built.
|
||||
|
||||
- The type is absent: the instance is stored and `getComponent` returns it. The context does not construct it, and it does not scan again. Components already dropped because a dependency was missing stay dropped.
|
||||
- The concrete class was found by the scan: `IllegalStateException` with `Type cannot be replaced:` and the class name.
|
||||
- The key is a unique super type, whose stored component class is the implementation: the super-type entry is replaced. The concrete-class entry is left as it was, so the interface and the class can then return different objects.
|
||||
|
||||
`createComponent` on a manually added type has no injection constructor and fails.
|
||||
|
||||
The desk does not call `addSingleton`. Its services are the scanned classes.
|
||||
|
||||
## Scope
|
||||
|
||||
`Scope` has two constants, `SINGLETON` and `PROTOTYPE`. Every class the scanner accepts is stored as `SINGLETON`. Nothing in the scanner assigns `PROTOTYPE`. `getComponent` on a singleton returns the cached instance. `createComponent` is how you get another object of a singleton class, which is what the desk's repeated forms and table rows do.
|
||||
|
||||
## What this container does not do
|
||||
|
||||
It does not match qualifiers, read `@Named` values, inject by name, bind configuration properties, or call lifecycle methods. It does not create a new instance per injection. `fx` view models are built by `fx`, not by this container.
|
||||
@@ -19,6 +19,10 @@ import java.util.Objects;
|
||||
* for resolving and creating components.
|
||||
*/
|
||||
public class ApplicationContext {
|
||||
/**
|
||||
* Components this context can create, keyed by the registered type.
|
||||
* The component scan fills the map, and manual singleton registration updates it.
|
||||
*/
|
||||
private final Map<Class<?>, ComponentData> components;
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,33 +12,54 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
|
||||
/**
|
||||
* Checks that {@link ApplicationContext} injects constructor, field, and setter dependencies.
|
||||
*/
|
||||
class ApplicationContextInjectionTest {
|
||||
|
||||
/**
|
||||
* Context scanned from the successful injection fixtures.
|
||||
*/
|
||||
private ApplicationContext context;
|
||||
|
||||
/**
|
||||
* Scans the successful injection fixtures before each test.
|
||||
*/
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = new ApplicationContext("de.neitzel.injection.testcomponents.injection.success");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a service whose dependency is passed to the constructor.
|
||||
*/
|
||||
@Test
|
||||
void resolvesConstructorInjection() {
|
||||
ConstructorInjectedService service = context.getComponent(ConstructorInjectedService.class);
|
||||
assertNotNull(service.getRootDependency());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a service whose dependency is injected into a field.
|
||||
*/
|
||||
@Test
|
||||
void resolvesFieldInjection() {
|
||||
FieldInjectedService service = context.getComponent(FieldInjectedService.class);
|
||||
assertNotNull(service.getRootDependency());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a service whose dependency is injected through a setter.
|
||||
*/
|
||||
@Test
|
||||
void resolvesSetterInjection() {
|
||||
SetterInjectedService service = context.getComponent(SetterInjectedService.class);
|
||||
assertNotNull(service.getRootDependency());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a mixed graph and reuses the same singleton dependency throughout.
|
||||
*/
|
||||
@Test
|
||||
void resolvesMixedInjectionGraphAndSingletonReuse() {
|
||||
MixedInjectionService service = context.getComponent(MixedInjectionService.class);
|
||||
@@ -48,6 +69,9 @@ class ApplicationContextInjectionTest {
|
||||
assertSame(rootDependency, service.getSetterInjectedService().getRootDependency());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance while singleton dependencies stay the instances already in the context.
|
||||
*/
|
||||
@Test
|
||||
void createComponentInjectsANewInstanceAndKeepsTheSingleton() {
|
||||
MixedInjectionService singleton = context.getComponent(MixedInjectionService.class);
|
||||
@@ -63,6 +87,9 @@ class ApplicationContextInjectionTest {
|
||||
assertSame(singleton.getSetterInjectedService(), created.getSetterInjectedService());
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps a created instance out of the singleton cache.
|
||||
*/
|
||||
@Test
|
||||
void createComponentDoesNotBecomeTheSingleton() {
|
||||
MixedInjectionService created = context.createComponent(MixedInjectionService.class);
|
||||
|
||||
@@ -7,8 +7,14 @@ import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Checks that the component scanner reports unusable injection plans.
|
||||
*/
|
||||
class ComponentScannerInjectionPlanTest {
|
||||
|
||||
/**
|
||||
* Reports an error when a component declares more than one {@code @Inject} constructor.
|
||||
*/
|
||||
@Test
|
||||
void reportsMultipleInjectConstructorsAsError() {
|
||||
ComponentScanner scanner = new ComponentScanner("de.neitzel.injection.testcomponents.injection.error");
|
||||
@@ -16,6 +22,9 @@ class ComponentScannerInjectionPlanTest {
|
||||
assertTrue(scanner.getErrors().stream().anyMatch(error -> error.contains(MultipleInjectConstructorsComponent.class.getName())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports an error when every constructor needs arguments and none is marked {@code @Inject}.
|
||||
*/
|
||||
@Test
|
||||
void reportsMissingUsableConstructorAsError() {
|
||||
ComponentScanner scanner = new ComponentScanner("de.neitzel.injection.testcomponents.injection.error");
|
||||
|
||||
@@ -13,6 +13,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Checks which types the component scanner accepts in the test component packages.
|
||||
*/
|
||||
class InjectableComponentScannerTest {
|
||||
|
||||
/**
|
||||
|
||||
+13
@@ -3,12 +3,25 @@ package de.neitzel.injection.testcomponents.injection.error;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Fixture with two {@code @Inject} constructors, which the scanner must reject.
|
||||
*/
|
||||
@Singleton
|
||||
public class MultipleInjectConstructorsComponent {
|
||||
/**
|
||||
* Declares an injectable constructor that takes a string.
|
||||
*
|
||||
* @param firstDependency unused string dependency
|
||||
*/
|
||||
@Inject
|
||||
public MultipleInjectConstructorsComponent(String firstDependency) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares a second injectable constructor that takes an integer.
|
||||
*
|
||||
* @param secondDependency unused integer dependency
|
||||
*/
|
||||
@Inject
|
||||
public MultipleInjectConstructorsComponent(Integer secondDependency) {
|
||||
}
|
||||
|
||||
+13
@@ -2,11 +2,24 @@ package de.neitzel.injection.testcomponents.injection.error;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Fixture with no default constructor and no {@code @Inject} constructor.
|
||||
*/
|
||||
@Singleton
|
||||
public class NoInjectConstructorWithoutDefault {
|
||||
/**
|
||||
* Accepts a string and is not marked for injection.
|
||||
*
|
||||
* @param firstDependency unused string dependency
|
||||
*/
|
||||
public NoInjectConstructorWithoutDefault(String firstDependency) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts an integer and is not marked for injection.
|
||||
*
|
||||
* @param secondDependency unused integer dependency
|
||||
*/
|
||||
public NoInjectConstructorWithoutDefault(Integer secondDependency) {
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -3,15 +3,31 @@ package de.neitzel.injection.testcomponents.injection.success;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Fixture whose dependency is passed to the constructor.
|
||||
*/
|
||||
@Singleton
|
||||
public class ConstructorInjectedService {
|
||||
/**
|
||||
* Dependency received through the constructor.
|
||||
*/
|
||||
private final RootDependency rootDependency;
|
||||
|
||||
/**
|
||||
* Stores the constructor dependency.
|
||||
*
|
||||
* @param rootDependency the dependency
|
||||
*/
|
||||
@Inject
|
||||
public ConstructorInjectedService(RootDependency rootDependency) {
|
||||
this.rootDependency = rootDependency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the constructor dependency.
|
||||
*
|
||||
* @return the dependency
|
||||
*/
|
||||
public RootDependency getRootDependency() {
|
||||
return rootDependency;
|
||||
}
|
||||
|
||||
+11
@@ -3,11 +3,22 @@ package de.neitzel.injection.testcomponents.injection.success;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Fixture whose dependency is injected into a field.
|
||||
*/
|
||||
@Singleton
|
||||
public class FieldInjectedService {
|
||||
/**
|
||||
* Dependency injected into the field.
|
||||
*/
|
||||
@Inject
|
||||
private RootDependency rootDependency;
|
||||
|
||||
/**
|
||||
* Returns the injected dependency.
|
||||
*
|
||||
* @return the dependency
|
||||
*/
|
||||
public RootDependency getRootDependency() {
|
||||
return rootDependency;
|
||||
}
|
||||
|
||||
+37
@@ -3,32 +3,69 @@ package de.neitzel.injection.testcomponents.injection.success;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Fixture that receives dependencies by constructor, field, and setter.
|
||||
*/
|
||||
@Singleton
|
||||
public class MixedInjectionService {
|
||||
/**
|
||||
* Dependency received through the constructor.
|
||||
*/
|
||||
private final ConstructorInjectedService constructorInjectedService;
|
||||
|
||||
/**
|
||||
* Dependency injected into the field.
|
||||
*/
|
||||
@Inject
|
||||
private FieldInjectedService fieldInjectedService;
|
||||
|
||||
/**
|
||||
* Dependency injected through the setter.
|
||||
*/
|
||||
private SetterInjectedService setterInjectedService;
|
||||
|
||||
/**
|
||||
* Stores the constructor dependency.
|
||||
*
|
||||
* @param constructorInjectedService the constructor dependency
|
||||
*/
|
||||
@Inject
|
||||
public MixedInjectionService(ConstructorInjectedService constructorInjectedService) {
|
||||
this.constructorInjectedService = constructorInjectedService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the constructor dependency.
|
||||
*
|
||||
* @return the constructor dependency
|
||||
*/
|
||||
public ConstructorInjectedService getConstructorInjectedService() {
|
||||
return constructorInjectedService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field dependency.
|
||||
*
|
||||
* @return the field dependency
|
||||
*/
|
||||
public FieldInjectedService getFieldInjectedService() {
|
||||
return fieldInjectedService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setter dependency.
|
||||
*
|
||||
* @return the setter dependency
|
||||
*/
|
||||
public SetterInjectedService getSetterInjectedService() {
|
||||
return setterInjectedService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the setter dependency.
|
||||
*
|
||||
* @param setterInjectedService the setter dependency
|
||||
*/
|
||||
@Inject
|
||||
public void setSetterInjectedService(SetterInjectedService setterInjectedService) {
|
||||
this.setterInjectedService = setterInjectedService;
|
||||
|
||||
+3
@@ -2,6 +2,9 @@ package de.neitzel.injection.testcomponents.injection.success;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Shared singleton dependency used by the injection fixtures.
|
||||
*/
|
||||
@Singleton
|
||||
public class RootDependency {
|
||||
}
|
||||
|
||||
+16
@@ -3,14 +3,30 @@ package de.neitzel.injection.testcomponents.injection.success;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Fixture whose dependency is injected through a setter.
|
||||
*/
|
||||
@Singleton
|
||||
public class SetterInjectedService {
|
||||
/**
|
||||
* Dependency injected through the setter.
|
||||
*/
|
||||
private RootDependency rootDependency;
|
||||
|
||||
/**
|
||||
* Returns the injected dependency.
|
||||
*
|
||||
* @return the dependency
|
||||
*/
|
||||
public RootDependency getRootDependency() {
|
||||
return rootDependency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the injected dependency.
|
||||
*
|
||||
* @param rootDependency the dependency
|
||||
*/
|
||||
@Inject
|
||||
public void setRootDependency(RootDependency rootDependency) {
|
||||
this.rootDependency = rootDependency;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
package de.neitzel.injection.testcomponents.test1ok;
|
||||
|
||||
/**
|
||||
* Shared supertype of more than one scanned component.
|
||||
*/
|
||||
public class SuperClass {
|
||||
}
|
||||
|
||||
+6
@@ -2,8 +2,14 @@ package de.neitzel.injection.testcomponents.test1ok;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Singleton component in the root of the successful scan package.
|
||||
*/
|
||||
@Singleton
|
||||
public class TestComponent1_1 extends SuperClass implements TestInterface1_2 {
|
||||
/**
|
||||
* Creates the component with no dependencies.
|
||||
*/
|
||||
public TestComponent1_1() {
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -1,4 +1,7 @@
|
||||
package de.neitzel.injection.testcomponents.test1ok;
|
||||
|
||||
/**
|
||||
* Interface implemented by exactly one scanned component.
|
||||
*/
|
||||
public interface TestInterface1_1 {
|
||||
}
|
||||
|
||||
+3
@@ -1,4 +1,7 @@
|
||||
package de.neitzel.injection.testcomponents.test1ok;
|
||||
|
||||
/**
|
||||
* Interface implemented by more than one scanned component.
|
||||
*/
|
||||
public interface TestInterface1_2 {
|
||||
}
|
||||
|
||||
+6
@@ -5,8 +5,14 @@ import de.neitzel.injection.testcomponents.test1ok.TestInterface1_1;
|
||||
import de.neitzel.injection.testcomponents.test1ok.TestInterface1_2;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Singleton component in a subpackage of the successful scan package.
|
||||
*/
|
||||
@Singleton
|
||||
public class TestComponent1_2 extends SuperClass implements TestInterface1_1, TestInterface1_2 {
|
||||
/**
|
||||
* Creates the component with no dependencies.
|
||||
*/
|
||||
public TestComponent1_2() {
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -7,6 +7,11 @@ import jakarta.inject.Singleton;
|
||||
*/
|
||||
@Singleton
|
||||
public class TestComponent2_1 {
|
||||
/**
|
||||
* Requires a string the scanner cannot resolve.
|
||||
*
|
||||
* @param test unresolved constructor dependency
|
||||
*/
|
||||
public TestComponent2_1(String test) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user