Home > Enterprise >  How can I promote an instance of a Java class to a Spring Bean when launching Spring Boot?
How can I promote an instance of a Java class to a Spring Bean when launching Spring Boot?

Time:12-08

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
}
  • Related