This approach is generally not recommended for new projects as it deviates from Spring's best practices for dependency injection.
It is more suited for legacy projects undergoing a migration to a Spring-based architecture.
This can be a practical workaround when refactoring is cost-prohibitive, such as when maintaining compliance with security requirements under existing support contracts for outdated frameworks, or when migrating from less commonly used frameworks like the Karaf.
In Spring Boot applications, we may encounter situations where we need to create an instance of a bean with dynamic arguments while still leveraging Spring's dependency injection.
This is a common requirement when we want to initialize an object with parameters determined at runtime.
Standard methods like @Autowired
or applicationContext.getBean
don't support passing dynamic arguments directly.
Using new
to instantiate the bean will bypass Spring's dependency injection, which can result in missing dependencies.
To address this, we can use a factory method within a Spring-managed class.
This approach allows us to pass dynamic arguments while ensuring that all necessary dependencies are injected by Spring.
Here's a simple example:
class Foo {
private final String name;
private final BarService barService;
/**
* Private constructor to prevent direct instantiation,
* ensuring that Foo can only be created via the factory.
*/
private Foo(String name, BarService barService) {
this.name = name;
this.barService = barService;
}
@Component
public static class FooFactory {
@Autowired
private BarService barService;
public Foo create(String name) {
return new Foo(name, barService);
}
}
}
In this example, Foo
instances are not Spring Beans themselves, but by passing the Spring-managed barService
through FooFactory
(also a Spring Bean), we ensure proper dependency injection.
If there are many dependencies, we can pass the entire factory to the constructor, like this:
class Foo {
private final String name;
private final FooFactory factory;
private Foo(String name, FooFactory factory) {
this.name = name;
this.factory = factory;
}
public void doSomethingWithService() {
// The factory carries all the dependencies
factory.barService.doSomething();
}
@Component
public static class FooFactory {
@Autowired
private BarService barService;
// Other dependencies...
public Foo create(String name) {
return new Foo(name, this);
}
}
}
This approach is especially useful when dealing with a large number of dependencies, allowing for centralized management and ensuring all necessary components are available when creating instances of the class.
After defining the class with its factory, we can easily obtain the FooFactory
bean using @Autowired
in any Spring-managed component. Here's an example:
@Service
public class SomeService {
@Autowired
private Foo.FooFactory factory;
public void doSomethingWithService(String name) {
Foo foo = factory.create(name);
foo.doSomethingWithService();
}
}
In this example, SomeService
uses Foo.FooFactory
to create instances of Foo
with the dynamic argument name. This allows Foo
to perform operations using the injected BarService
dependency, demonstrating how to pass and manage dynamic parameters while maintaining Spring's dependency injection benefits.