Das mit @Autowired
annotierte Feld ist null
, weil Spring nichts über die Kopie von MileageFeeCalculator
weiß, die Sie mit new
erstellt haben und nicht wusste, wie es verkabelt werden soll.
Der Spring Inversion of Control (IoC) Container hat drei Hauptkomponenten: ein Register (genannt der ApplicationContext
) von Komponenten (Beans), die von der Anwendung verwendet werden können, ein Konfigurationssystem, das Objektabhängigkeiten in sie injiziert, indem es die Abhängigkeiten mit Beans im Kontext abgleicht, und ein Abhängigkeitslöser, der eine Konfiguration vieler verschiedener Beans betrachten kann und bestimmen kann, wie sie in der erforderlichen Reihenfolge instanziiert und konfiguriert werden sollen.
Der IoC-Container ist keine Magie und hat keine Möglichkeit, über Java-Objekte Bescheid zu wissen, es sei denn, Sie informieren ihn somehow. Wenn Sie new
aufrufen, instanziiert die JVM eine Kopie des neuen Objekts und übergibt es direkt an Sie - es durchläuft niemals den Konfigurationsprozess. Es gibt drei Möglichkeiten, wie Sie Ihre Beans konfigurieren können.
Ich habe den gesamten Code mit Spring Boot zur Ausführung in diesem GitHub-Projekt veröffentlicht: dieses GitHub-Projekt; Sie können sich ein komplettes laufendes Projekt für jeden Ansatz ansehen, um zu sehen, was alles benötigt wird, um es zum Laufen zu bringen. Tag mit der NullPointerException
: nonworking
Inject your beans
Die bevorzugte Option besteht darin, Spring all Ihre Beans verkabeln zu lassen; dies erfordert den geringsten Codeaufwand und ist am einfachsten zu warten. Um das Verkabeln so zu machen, wie Sie es möchten, verkabeln Sie auch den MileageFeeCalculator
wie folgt:
@Controller
public class MileageFeeController {
@Autowired
private MileageFeeCalculator calc;
@RequestMapping("/mileage/{miles}")
@ResponseBody
public float mileageFee(@PathVariable int miles) {
return calc.mileageCharge(miles);
}
}
Wenn Sie eine neue Instanz Ihres Service-Objekts für unterschiedliche Anfragen erstellen müssen, können Sie immer noch Injektion verwenden, indem Sie die Spring Bean Scopes verwenden.
Tag, der funktioniert, indem das @MileageFeeCalculator
Service-Objekt injiziert wird: working-inject-bean
Verwenden Sie @Configurable
Wenn Sie wirklich möchten, dass Objekte, die mit new
erstellt wurden, verkabelt werden, können Sie die Spring @Configurable
Annotation zusammen mit dem AspectJ-Kompilierzeit-Weaving verwenden, um Ihre Objekte einzufügen. Dieser Ansatz fügt Code in den Konstruktor Ihres Objekts ein, der Spring signalisiert, dass es erstellt wird, so dass Spring die neue Instanz konfigurieren kann. Dies erfordert etwas Konfiguration in Ihrem Build (wie das Kompilieren mit ajc
) und das Aktivieren der Laufzeitkonfigurationshandler von Spring (@EnableSpringConfigured
mit der JavaConfig-Syntax). Dieser Ansatz wird vom Roo Active Record-System verwendet, um new
Instanzen Ihrer Entitäten die notwendigen Persistenzinformationen einzuspeisen.
@Service
@Configurable
public class MileageFeeCalculator {
@Autowired
private MileageRateService rateService;
public float mileageCharge(final int miles) {
return (miles * rateService.ratePerMile());
}
}
Tag, der funktioniert, indem @Configurable
auf das Service-Objekt angewendet wird: working-configurable
Manuelle Bean-Suche: nicht empfohlen
Dieser Ansatz ist nur geeignet, um in speziellen Situationen mit Legacy-Code zu interagieren. Es ist fast immer vorzuziehen, eine Singleton-Adapterklasse zu erstellen, die Spring verkabeln kann und die der Legacy-Code aufrufen kann, aber es ist möglich, den Spring-Anwendungscontext direkt nach einem Bean zu fragen.
Dazu benötigen Sie eine Klasse, der Spring einen Verweis auf das ApplicationContext
-Objekt geben kann:
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}
Dann kann Ihr Legacy-Code getContext()
aufrufen und die Beans abrufen, die er benötigt:
@Controller
public class MileageFeeController {
@RequestMapping("/mileage/{miles}")
@ResponseBody
public float mileageFee(@PathVariable int miles) {
MileageFeeCalculator calc = ApplicationContextHolder.getContext().getBean(MileageFeeCalculator.class);
return calc.mileageCharge(miles);
}
}
Tag, der funktioniert, indem das Service-Objekt im Spring-Kontext manuell gesucht wird: working-manual-lookup