A Day in the Life of an Engineer: Me and Spring Dependency Injection
We have a REST API backend system that's relatively old and is using Spring Framework (version 4.x with Tomcat as container) and one of the things my team is improving on is to add more observability via Prometheus.
One of the changes I had to implement is to track the number of entities that are loaded into in-memory cache (from database) upon startup/bootstrap and report them via the Prometheus endpoint it already has, and for this I didn't want to change too much code in the Component/Repository/Service (Spring) Beans that already exist, but I do want to have them report their in-memory entities respectively.
For the Bean that will do the reporting itself is the trivial part: create a Bean that implements ApplicationListener<ContextRefereshEvent>:
public class EntityStatisticsReporter implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// create the Gauge metrics
}
}
The key thing is how would the other beans report their entity count without having to change too much. For that I define an interface:
public interface EntityCounterReporter {
int getEntityCount();
}
Each Bean that has entities loaded into memory will just need to implement that interface to report number of entities without having to know nor care who will actually do the reporting.
领英推荐
Going back to the EntityStatisticsReporter, the way it will "know" about the entities is through the magic of Spring Dependency Injection. And the cool, not-so-frequently used feature is that Spring can autowire a list of beans that define the interface and so we now have the EntityStatisticsReporter like this:
public class EntityStatisticsReporter implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
List<EntityCounterReporter> entityCounterReporters;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// create the Gauge metrics
}
}
In the Autowired field above, the reporter doesn't care what objects got wired through DI, it just cares about that it implements the interface. and if we define more beans that implement EntityCounterReporter, it just magically wires it to that List!
And the final piece of the puzzle is to actually create the (Micrometer) Gauge objects:
public class EntityStatisticsReporter implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
MeterRegistry meterRegistry;
@Autowired
List<EntityCounterReporter> entityCounterReporters;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// create the Gauge metrics
entityCounterReporters.forEach(r -> {
Gauge.builder("entities", r, EntityCounterReporter::getEntityCount,
strongReference(true),
tags("type", r.getClass().getSimpleName(),
register(meterRegistry);
}
}
}
The original code has a bit more stuff; i just stripped it to bare essentials to get the point across, but the above code snippet is essentially what is needed to make this all happen.
Hopefully this showcases that with proper Spring DI in place, refactoring code to add more capabilities should be painless and involve little risk.