How can I pass an instance of a Java class dynamically to a Spring configuration when I launch a Spring Boot application?
I have a ThirdPartyService
that requires SomeConfig
when it is instantiated. Now, I would like to wrap this service using a custom ServiceWrapper
and then use Spring Boot to launch it programmatically without that the user of the ServiceWrapper
is aware of that Spring Boot is used behind the scene, ServiceWrapperTest
gives you an idea about how I intend to use it:
@Service
public class ThirdPartyService {
private final SomeConfig someConfig;
@Autowired
public ThirdPartyService(final SomeConfig someConfig) {
this.someConfig = someConfig;
}
void start() {
// reads values from someConfig and starts the service
}
void stop() {
// stops the service
}
}
public class SomeConfig {
// getters and setters omitted
}
@SpringBootApplication
public class ServiceWrapper {
private ThirdPartyService thirdPartyService;
public ServiceWrapper() {
}
public void start(SomeConfig someConfig) {
// how can I pass someConfig to Spring Boot?
thirdPartyService = SpringApplication.run(ThirdPartyService.class).getBean(ThirdPartyService.class);
}
public void stop() {
if (thirdPartyService != null) {
thirdPartyService.stop();
}
}
}
class ServiceWrapperTest {
@Test
void testStartAndStop() {
SomeConfig someConfig = new SomeConfig();
ServiceWrapper serviceWrapper = new ServiceWrapper(someConfig);
serviceWrapper.start();
serviceWrapper.stop();
}
}
Unsurprisingly, the test fail with following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.test.ThirdPartyService required a bean of type 'com.example.test.SomeConfig' that could not be found.
Action:
Consider defining a bean of type 'com.example.test.SomeConfig' in your configuration.
How can I pass the SomeConfig
instance to Spring Beot so that it becomes eligible for auto-wiring? Is it possible to use @Bean
, @Configuration
or similar (I don't think so)?
Spring Boot version 2.6.1
CodePudding user response:
You could annotate it with @Component if it is just a third-party class and do the standard constructor or field injection in the ServiceWrapper. Also since Spring 5.2, you don't have to explicitly annotate the dependency injection with @Autowired.
@Service
public class ThirdPartyService {
private final SomeConfig someConfig;
public ThirdPartyService(final SomeConfig someConfig) {
this.someConfig = someConfig;
}
void start() {
// reads values from someConfig and starts the service
}
void stop() {
// stops the service
}
}
@Component
public class SomeConfig {
// getters and setters omitted
}
That is if I understood your question correctly.
CodePudding user response:
Is this what you mean:
@SpringBootApplication
public class ServiceWrapper {
@Bean
SomeConfig getSomeConfig() {
return someConfig;
}
private static SomeConfig someConfig; // really not thread safe
private ApplicationContext context;
public void start(SomeConfig someConfig) {
this.someConfig = someConfig;
this.context = SpringApplication.run(ServiceWrapper.class);
// do whatever you want with ThirdPartyService
ThirdPartyService service = this.context.getBean(ThirdPartyService.class);
}
public void stop() {
this.context.close();
}
}
This way you make SomeConfig a bean for Spring and use that to wire your context, but you don't need to change any code in SomeConfig. You have then ThirdPartyService populated, and you can return it with a getter.
CodePudding user response:
You can use an ApplicationContextInitializer
to make the existing instance of SomeConfig
available as a bean:
public void start(SomeConfig someConfig) {
SpringApplication application = new SpringApplication(ServiceWrapper.class);
application.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.getBeanFactory().registerSingleton("someConfig", someConfig);
}
});
ThirdPartyService service = application.run(ServiceWrapper.class).getBean(ThirdPartyService.class);
// Use service as needed
}