N Ways to Dynamically Switch Spring Boot Implementations and Ditch if‑else
The article demonstrates four practical techniques—@Qualifier, manual ApplicationContext lookup, Map injection, and a FactoryBean with dynamic proxy—to dynamically select among multiple Spring Boot beans, eliminating static if‑else wiring and improving code flexibility and maintainability.
In Spring Boot projects a single interface often has several implementations (e.g., PDF and CSV exporters). Traditional dependency injection binds a fixed bean, leading to redundant code and poor extensibility. The article explores flexible injection strategies that allow runtime selection of the appropriate implementation.
Environment: Spring Boot 3.5.0. The example defines a DataExporter interface and two beans: @Component("pdf") class PdfExporter implements DataExporter returning "Pdf格式导出" and @Component("csv") class CsvExporter implements DataExporter returning "Csv格式导出".
2.2 Qualifier annotation – The most common static approach. By annotating a constructor parameter with @Qualifier("pdf") (or "csv") the desired bean is fixed at compile time. This cannot switch dynamically based on request data.
private final DataExporter exporter;</code>
<code>public ExportController(@Qualifier("pdf") DataExporter exporter) {</code>
<code> this.exporter = exporter;</code>
<code>}2.3 Manual bean retrieval – Inject ApplicationContext and obtain the bean whose name matches a request parameter.
@RestController</code>
<code>@RequestMapping("/export")</code>
<code>public class ExportController {</code>
<code> private final ApplicationContext context;</code>
<code> public ExportController(ApplicationContext context) {</code>
<code> this.context = context;</code>
<code> }</code>
<code> @GetMapping</code>
<code> public String export(String format) {</code>
<code> DataExporter exporter = this.context.getBean(format, DataExporter.class);</code>
<code> return exporter.export();</code>
<code> }</code>
<code>}The format query parameter (e.g., "pdf" or "csv") determines which exporter runs, as shown in the accompanying result screenshots.
2.4 Map injection – Inject a Map<String, DataExporter> containing all exporter beans. The map key is the bean name, enabling dynamic lookup.
private final Map<String, DataExporter> exporters;</code>
<code>public ExportController(Map<String, DataExporter> exporters) {</code>
<code> this.exporters = exporters;</code>
<code>}</code>
<code>@GetMapping</code>
<code>public String export(String format) {</code>
<code> DataExporter exporter = this.exporters.get(format);</code>
<code> return exporter.export();</code>
<code>}2.5 FactoryBean with dynamic proxy – Create a DataExporterFactoryBean that implements FactoryBean<DataExporter>. It receives ApplicationContext and HttpServletRequest, builds a JDK proxy, and delegates each method call to the bean whose name matches the request parameter format. Marked @Primary, the proxy can be injected wherever DataExporter is required.
@Component</code>
<code>@Primary</code>
<code>public class DataExporterFactoryBean implements FactoryBean<DataExporter> {</code>
<code> private final ApplicationContext context;</code>
<code> private final HttpServletRequest request;</code>
<code> public DataExporterFactoryBean(ApplicationContext context, HttpServletRequest request) {</code>
<code> this.context = context;</code>
<code> this.request = request;</code>
<code> }</code>
<code> @Override</code>
<code> public DataExporter getObject() throws Exception {</code>
<code> return (DataExporter) Proxy.newProxyInstance(</code>
<code> DataExporterFactoryBean.class.getClassLoader(),</code>
<code> new Class<?>[] { DataExporter.class },</code>
<code> (proxy, method, args) -> {</code>
<code> DataExporter instance = context.getBean(request.getParameter("format"), DataExporter.class);</code>
<code> return method.invoke(instance, args);</code>
<code> });</code>
<code> }</code>
<code> @Override</code>
<code> public Class<?> getObjectType() {</code>
<code> return DataExporter.class;</code>
<code> }</code>
<code>}Controller can now inject the proxy as a regular DataExporter and call export() without knowing the concrete implementation.
private final DataExporter exporter;</code>
<code>public ExportController(DataExporter exporter) {</code>
<code> this.exporter = exporter;</code>
<code>}</code>
<code>@GetMapping</code>
<code>public String export() {</code>
<code> return this.exporter.export();</code>
<code>}All four approaches achieve dynamic bean selection, with the FactoryBean solution offering the most transparent usage while keeping the concrete implementations hidden.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
