refactor: annotation-based dependency-injection

This commit is contained in:
David Guler 2022-11-14 21:15:27 +01:00
parent 15279838b7
commit 5ef3f6c587
9 changed files with 188 additions and 139 deletions

View File

@ -1,5 +1,6 @@
package ch.zhaw.gartenverwaltung; package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import ch.zhaw.gartenverwaltung.io.PlantList; import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.models.Garden; import ch.zhaw.gartenverwaltung.models.Garden;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
@ -9,7 +10,6 @@ import ch.zhaw.gartenverwaltung.types.Crop;
import ch.zhaw.gartenverwaltung.types.Pest; import ch.zhaw.gartenverwaltung.types.Pest;
import ch.zhaw.gartenverwaltung.types.Plant; import ch.zhaw.gartenverwaltung.types.Plant;
import ch.zhaw.gartenverwaltung.types.Task; import ch.zhaw.gartenverwaltung.types.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.Button; import javafx.scene.control.Button;
@ -26,18 +26,16 @@ import java.util.logging.Logger;
public class CropDetailController { public class CropDetailController {
private Crop crop; private Crop crop;
@Inject
private PlantList plantList; private PlantList plantList;
@Inject
private GardenSchedule gardenSchedule; private GardenSchedule gardenSchedule;
@Inject
private Garden garden; private Garden garden;
private static final Logger LOG = Logger.getLogger(CropDetailController.class.getName()); private static final Logger LOG = Logger.getLogger(CropDetailController.class.getName());
@SuppressWarnings("unused")
public void injectDependencies(Garden garden, PlantList plantList, GardenSchedule gardenSchedule) {
this.garden = garden;
this.plantList = plantList;
this.gardenSchedule = gardenSchedule;
}
@FXML @FXML
private ImageView imageView; private ImageView imageView;
@ -75,23 +73,23 @@ public class CropDetailController {
private Label spacing_label; private Label spacing_label;
@FXML @FXML
void editTaskList(ActionEvent event) { void editTaskList() {
} }
@FXML @FXML
void goBack(ActionEvent event) { void goBack() {
Stage stage = (Stage) imageView.getScene().getWindow(); Stage stage = (Stage) imageView.getScene().getWindow();
stage.close(); stage.close();
} }
@FXML @FXML
void setArea(ActionEvent event) { void setArea() {
} }
@FXML @FXML
void setLocation(ActionEvent event) { void setLocation() {
} }

View File

@ -1,53 +1,21 @@
package ch.zhaw.gartenverwaltung; package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.io.CropList; import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import ch.zhaw.gartenverwaltung.io.JsonCropList;
import ch.zhaw.gartenverwaltung.io.JsonPlantList;
import ch.zhaw.gartenverwaltung.io.JsonTaskList;
import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.io.TaskList;
import ch.zhaw.gartenverwaltung.models.Garden;
import ch.zhaw.gartenverwaltung.models.GardenSchedule;
import ch.zhaw.gartenverwaltung.models.PlantListModel;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class MainFXMLController implements Initializable { public class MainFXMLController implements Initializable {
/**
* Caching the panes
*/
private final Map<String, Pane> panes = new HashMap<>();
private static final Logger LOG = Logger.getLogger(MainFXMLController.class.getName()); private static final Logger LOG = Logger.getLogger(MainFXMLController.class.getName());
private final AppLoader appLoader = new AppLoader(this);
private final PlantList plantList = new JsonPlantList();
private final CropList cropList = new JsonCropList();
private final TaskList taskList = new JsonTaskList();
private final GardenSchedule gardenSchedule = new GardenSchedule(taskList, plantList);
private final Garden garden = new Garden(gardenSchedule, cropList);
@FXML @FXML
private Button home_button; private Button home_button;
@ -67,25 +35,25 @@ public class MainFXMLController implements Initializable {
} }
@FXML @FXML
void goToHome(ActionEvent event) throws IOException { void goToHome() throws IOException {
showPaneAsMainView("Home.fxml"); showPaneAsMainView("Home.fxml");
styleChangeButton(home_button); styleChangeButton(home_button);
} }
@FXML @FXML
void goToMyPlants(ActionEvent event) throws IOException { void goToMyPlants() throws IOException {
showPaneAsMainView("MyGarden.fxml"); showPaneAsMainView("MyGarden.fxml");
styleChangeButton(myGarden_button); styleChangeButton(myGarden_button);
} }
@FXML @FXML
void goToMySchedule(ActionEvent event) throws IOException { void goToMySchedule() throws IOException {
showPaneAsMainView("MySchedule.fxml"); showPaneAsMainView("MySchedule.fxml");
styleChangeButton(mySchedule_button); styleChangeButton(mySchedule_button);
} }
@FXML @FXML
void goToPlants(ActionEvent event) throws IOException { void goToPlants() throws IOException {
showPaneAsMainView("Plants.fxml"); showPaneAsMainView("Plants.fxml");
styleChangeButton(plants_button); styleChangeButton(plants_button);
} }
@ -98,67 +66,17 @@ public class MainFXMLController implements Initializable {
* @throws IOException exception when file does not exist * @throws IOException exception when file does not exist
*/ */
public void showPaneAsMainView(String fxmlFile) throws IOException { public void showPaneAsMainView(String fxmlFile) throws IOException {
Pane anchorPane = panes.get(fxmlFile); Pane anchorPane = appLoader.loadPane(fxmlFile);
if (anchorPane == null) {
loadAndCacheFxml(fxmlFile);
anchorPane = panes.get(fxmlFile);
}
mainPane.getChildren().setAll(anchorPane); mainPane.getChildren().setAll(anchorPane);
anchorPane.prefWidthProperty().bind(mainPane.widthProperty()); anchorPane.prefWidthProperty().bind(mainPane.widthProperty());
anchorPane.prefHeightProperty().bind(mainPane.heightProperty()); anchorPane.prefHeightProperty().bind(mainPane.heightProperty());
} }
public Object loadSceneToStage(String fxmlFile, Stage appendee) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
Pane root = loader.load();
appendee.setScene(new Scene(root));
Object controller = loader.getController();
injectDependencies(controller);
return controller;
}
public FXMLLoader loadAndCacheFxml(String fxmlFile) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
AnchorPane anchorPane = loader.load();
panes.put(fxmlFile, anchorPane);
injectDependencies(loader.getController());
return loader;
}
public void injectDependencies(Object controller) {
Optional<Method> injectMethod = Arrays.stream(controller.getClass().getMethods())
.filter(method -> method.getName().equals("injectDependencies") )
.findFirst();
if (injectMethod.isPresent()) {
Method injectee = injectMethod.get();
List<Object> params = new ArrayList<>();
for (Class<?> parameterType : injectee.getParameterTypes()) {
Object toInject = switch (parameterType.getSimpleName()) {
case "Garden" -> garden;
case "PlantList" -> plantList;
case "PlantListModel" -> new PlantListModel(plantList);
case "GardenSchedule" -> gardenSchedule;
case "MainFXMLController" -> this;
default -> null;
};
if (toInject != null) {
params.add(toInject);
}
}
try {
injectee.invoke(controller, params.toArray());
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
private void preloadPanes() throws IOException { private void preloadPanes() throws IOException {
loadAndCacheFxml("MyGarden.fxml"); appLoader.loadAndCacheFxml("MyGarden.fxml");
loadAndCacheFxml("MySchedule.fxml"); appLoader.loadAndCacheFxml("MySchedule.fxml");
loadAndCacheFxml("Plants.fxml").getController(); appLoader.loadAndCacheFxml("Plants.fxml");
} }
private void styleChangeButton(Button button) { private void styleChangeButton(Button button) {

View File

@ -1,5 +1,8 @@
package ch.zhaw.gartenverwaltung; package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AfterInject;
import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import ch.zhaw.gartenverwaltung.io.PlantList; import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.models.Garden; import ch.zhaw.gartenverwaltung.models.Garden;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
@ -28,19 +31,21 @@ import java.util.logging.Logger;
public class MyGardenController { public class MyGardenController {
private static final Logger LOG = Logger.getLogger(MyGardenController.class.getName()); private static final Logger LOG = Logger.getLogger(MyGardenController.class.getName());
@Inject
AppLoader appLoader;
@Inject
MainFXMLController mainController; MainFXMLController mainController;
@Inject
private Garden garden; private Garden garden;
@Inject
private PlantList plantList; private PlantList plantList;
@FXML @FXML
private VBox myPlants_vbox; private VBox myPlants_vbox;
@AfterInject
@SuppressWarnings("unused") @SuppressWarnings("unused")
public void injectDependencies(Garden garden, PlantList plantList, MainFXMLController mainController) { public void init() {
this.garden = garden;
this.plantList = plantList;
this.mainController = mainController;
garden.getPlantedCrops().addListener((observable, oldValue, newValue) -> { garden.getPlantedCrops().addListener((observable, oldValue, newValue) -> {
try { try {
createPlantView(newValue); createPlantView(newValue);
@ -96,7 +101,7 @@ public class MyGardenController {
return (event) -> { return (event) -> {
try { try {
Stage stage = new Stage(); Stage stage = new Stage();
if (mainController.loadSceneToStage("CropDetail.fxml", stage) instanceof CropDetailController controller) { if (appLoader.loadSceneToStage("CropDetail.fxml", stage) instanceof CropDetailController controller) {
controller.setPlantFromCrop(crop); controller.setPlantFromCrop(crop);
} }
stage.initModality(Modality.APPLICATION_MODAL); stage.initModality(Modality.APPLICATION_MODAL);

View File

@ -1,5 +1,7 @@
package ch.zhaw.gartenverwaltung; package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AfterInject;
import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import ch.zhaw.gartenverwaltung.io.PlantList; import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.models.Garden; import ch.zhaw.gartenverwaltung.models.Garden;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
@ -27,20 +29,16 @@ public class MyScheduleController {
private static final Logger LOG = Logger.getLogger(MyScheduleController.class.getName()); private static final Logger LOG = Logger.getLogger(MyScheduleController.class.getName());
private Crop selectedCrop = null; private Crop selectedCrop = null;
@Inject
private GardenSchedule gardenSchedule; private GardenSchedule gardenSchedule;
@Inject
private Garden garden; private Garden garden;
@Inject
private PlantList plantList; private PlantList plantList;
private final ListProperty<Crop> cropListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); private final ListProperty<Crop> cropListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
@SuppressWarnings("unused")
public void injectDependencies(Garden garden, GardenSchedule gardenSchedule, PlantList plantList) {
this.garden = garden;
this.gardenSchedule = gardenSchedule;
this.plantList = plantList;
init();
}
@FXML @FXML
private Label day1_label; private Label day1_label;
@ -89,6 +87,8 @@ public class MyScheduleController {
@FXML @FXML
private ListView<Crop> scheduledPlants_listview; private ListView<Crop> scheduledPlants_listview;
@AfterInject
@SuppressWarnings("unused")
public void init() { public void init() {
List<Crop> cropList; List<Crop> cropList;
try { try {

View File

@ -1,5 +1,8 @@
package ch.zhaw.gartenverwaltung; package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AfterInject;
import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
import ch.zhaw.gartenverwaltung.models.PlantListModel; import ch.zhaw.gartenverwaltung.models.PlantListModel;
import ch.zhaw.gartenverwaltung.types.HardinessZone; import ch.zhaw.gartenverwaltung.types.HardinessZone;
@ -8,7 +11,6 @@ import ch.zhaw.gartenverwaltung.types.Seasons;
import javafx.beans.property.ListProperty; import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty; import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.control.*; import javafx.scene.control.*;
@ -25,21 +27,18 @@ import java.util.logging.Logger;
public class PlantsController { public class PlantsController {
private static final Logger LOG = Logger.getLogger(PlantsController.class.getName()); private static final Logger LOG = Logger.getLogger(PlantsController.class.getName());
@Inject
private PlantListModel plantListModel; private PlantListModel plantListModel;
private MainFXMLController mainController; @Inject
private AppLoader appLoader;
private Plant selectedPlant = null; private Plant selectedPlant = null;
private final HardinessZone DEFAULT_HARDINESS_ZONE = HardinessZone.ZONE_8A; private final HardinessZone DEFAULT_HARDINESS_ZONE = HardinessZone.ZONE_8A;
// TODO: move to model // TODO: move to model
private final ListProperty<Plant> plantListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); private final ListProperty<Plant> plantListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
@SuppressWarnings("unused")
public void injectDependencies(PlantListModel plantListModel, MainFXMLController mainController) {
this.plantListModel = plantListModel;
this.mainController = mainController;
init();
}
@FXML @FXML
private VBox seasons; private VBox seasons;
@ -63,12 +62,11 @@ public class PlantsController {
/** /**
* open new window to select sow or harvest day to save the crop * open new window to select sow or harvest day to save the crop
* @param event event
*/ */
@FXML @FXML
void selectSowDate(ActionEvent event) throws IOException { void selectSowDate() throws IOException {
Stage stage = new Stage(); Stage stage = new Stage();
if (mainController.loadSceneToStage("SelectSowDay.fxml", stage) instanceof SelectSowDayController controller) { if (appLoader.loadSceneToStage("SelectSowDay.fxml", stage) instanceof SelectSowDayController controller) {
controller.setSelectedPlant(selectedPlant); controller.setSelectedPlant(selectedPlant);
} }
@ -84,6 +82,8 @@ public class PlantsController {
* create event listener for selected list entry and search by query * create event listener for selected list entry and search by query
* {@inheritDoc} * {@inheritDoc}
*/ */
@AfterInject
@SuppressWarnings("unused")
public void init() { public void init() {
setListCellFactory(); setListCellFactory();
fillPlantListWithHardinessZone(); fillPlantListWithHardinessZone();
@ -95,6 +95,7 @@ public class PlantsController {
createFilterSeasons(); createFilterSeasons();
createFilterHardinessZone(); createFilterHardinessZone();
lookForSelectedListEntry(); lookForSelectedListEntry();
try { try {
viewFilteredListBySearch(); viewFilteredListBySearch();
} catch (HardinessZoneNotSetException e) { } catch (HardinessZoneNotSetException e) {

View File

@ -1,11 +1,11 @@
package ch.zhaw.gartenverwaltung; package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import ch.zhaw.gartenverwaltung.models.Garden; import ch.zhaw.gartenverwaltung.models.Garden;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
import ch.zhaw.gartenverwaltung.models.PlantNotFoundException; import ch.zhaw.gartenverwaltung.models.PlantNotFoundException;
import ch.zhaw.gartenverwaltung.types.GrowthPhaseType; import ch.zhaw.gartenverwaltung.types.GrowthPhaseType;
import ch.zhaw.gartenverwaltung.types.Plant; import ch.zhaw.gartenverwaltung.types.Plant;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.*; import javafx.scene.control.*;
@ -20,12 +20,9 @@ import java.util.ResourceBundle;
public class SelectSowDayController implements Initializable { public class SelectSowDayController implements Initializable {
private Plant selectedPlant = null; private Plant selectedPlant = null;
private Garden garden;
@SuppressWarnings("unused") @Inject
public void injectDependencies(Garden garden) { private Garden garden;
this.garden = garden;
}
@FXML @FXML
private DatePicker datepicker; private DatePicker datepicker;
@ -41,20 +38,18 @@ public class SelectSowDayController implements Initializable {
/** /**
* close the date selector window * close the date selector window
* @param event event
*/ */
@FXML @FXML
void cancel(ActionEvent event) { void cancel() {
closeWindow(); closeWindow();
} }
/** /**
* get sow date from datePicker or calculate sow date from harvest date * get sow date from datePicker or calculate sow date from harvest date
* save selected plant and sow date * save selected plant and sow date
* @param event event
*/ */
@FXML @FXML
void save(ActionEvent event) throws HardinessZoneNotSetException, IOException, PlantNotFoundException { void save() throws HardinessZoneNotSetException, IOException, PlantNotFoundException {
LocalDate sowDate = datepicker.getValue(); LocalDate sowDate = datepicker.getValue();
if (harvest_radio.isSelected()) { if (harvest_radio.isSelected()) {
//ToDo method to get current lifecycle group in plant //ToDo method to get current lifecycle group in plant

View File

@ -0,0 +1,10 @@
package ch.zhaw.gartenverwaltung.bootstrap;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterInject { }

View File

@ -0,0 +1,112 @@
package ch.zhaw.gartenverwaltung.bootstrap;
import ch.zhaw.gartenverwaltung.HelloApplication;
import ch.zhaw.gartenverwaltung.MainFXMLController;
import ch.zhaw.gartenverwaltung.io.CropList;
import ch.zhaw.gartenverwaltung.io.JsonCropList;
import ch.zhaw.gartenverwaltung.io.JsonPlantList;
import ch.zhaw.gartenverwaltung.io.JsonTaskList;
import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.io.TaskList;
import ch.zhaw.gartenverwaltung.models.Garden;
import ch.zhaw.gartenverwaltung.models.GardenSchedule;
import ch.zhaw.gartenverwaltung.models.PlantListModel;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class AppLoader {
/**
* Caching the panes
*/
private final Map<String, Pane> panes = new HashMap<>();
/**
* Application-wide dependencies
*/
private final PlantList plantList = new JsonPlantList();
private final CropList cropList = new JsonCropList();
private final TaskList taskList = new JsonTaskList();
private final GardenSchedule gardenSchedule = new GardenSchedule(taskList, plantList);
private final Garden garden = new Garden(gardenSchedule, cropList);
private final MainFXMLController mainController;
public AppLoader(MainFXMLController mainController) throws IOException {
this.mainController = mainController;
}
public Pane loadPane(String fxmlFile) throws IOException {
Pane pane = panes.get(fxmlFile);
if (pane == null) {
loadAndCacheFxml(fxmlFile);
pane = panes.get(fxmlFile);
}
return pane;
}
public Object loadSceneToStage(String fxmlFile, Stage appendee) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
Pane root = loader.load();
appendee.setScene(new Scene(root));
Object controller = loader.getController();
annotationInject(controller);
return controller;
}
public void loadAndCacheFxml(String fxmlFile) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
AnchorPane anchorPane = loader.load();
panes.put(fxmlFile, anchorPane);
annotationInject(loader.getController());
}
public void annotationInject(Object controller) {
Arrays.stream(controller.getClass().getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Inject.class))
.forEach(field -> {
field.setAccessible(true);
try {
field.set(controller, getAppDependency(field.getType()));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
field.setAccessible(false);
});
Arrays.stream(controller.getClass().getMethods())
.filter(method -> method.isAnnotationPresent(AfterInject.class))
.forEach(afterInjectMethod -> {
if (afterInjectMethod.getParameterCount() == 0) {
try {
afterInjectMethod.invoke(controller);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
private Object getAppDependency(Class<?> type) {
return switch (type.getSimpleName()) {
case "Garden" -> garden;
case "PlantList" -> plantList;
case "PlantListModel" -> new PlantListModel(plantList);
case "GardenSchedule" -> gardenSchedule;
case "MainFXMLController" -> mainController;
case "AppLoader" -> this;
default -> null;
};
}
}

View File

@ -0,0 +1,10 @@
package ch.zhaw.gartenverwaltung.bootstrap;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Inject { }