How to create #Controller and #Service instance conditionally on Spring - java

I am using Spring Boot 1.5.9.
Is there a way to turn on/off #Controller and #Services?
Something such as #ConditionalOnProperty, #Conditional for beans.
#ConditionalController // <--- something like this
#RestController
public class PingController {
#Value("${version}")
private String version;
#RequestMapping(value = CoreHttpPathStore.PING, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Map<String, Object>> ping() throws Exception {
HashMap<String, Object> map = new HashMap<>();
map.put("message", "Welcome to our API");
map.put("date", new Date());
map.put("version", version);
map.put("status", HttpStatus.OK);
return new ResponseEntity<>(map, HttpStatus.OK);
}
}
Then use some configuration bean to load it up.

#ConditionalOnProperty should work for Controller (or Service) as well, since it is also a Spring bean.
Add to your PingController
#ConditionalOnProperty(prefix="ping.controller",
name="enabled",
havingValue="true")
#RestController
public class PingController {...}
and to the application.properties to turn it on/off
ping.controller.enabled=false

do you want to try and load the bean programmatically instead?
you could access the application context using one of the two mechanisms
#Autowired private ApplicationContext appContext;
or creating something like a bean factory by extending ApplicationAware
public class ApplicationContextProvider implements
ApplicationContextAware{
once you have a handle to the application context you can add a bean to the context programmatically.

By default in Spring, all the defined beans, and their dependencies, are created when the application context is created.
We can turn it off by configuring a bean with lazy initialization, the bean will only be created, and its dependencies injected, once they're needed.
You can enable lazy initialization by configuring application.properties.
spring.main.lazy-initialization=true
Setting the property value to true means that all the beans in the application will use lazy initialization.
All the defined beans will use lazy initialization, except for those that we explicitly configure with #Lazy(false).
Or you can do it through the #Lazy approach. When we put #Lazy annotation over the #Configuration class, it indicates that all the methods with #Bean annotation should be loaded lazily.
#Lazy
#Configuration
#ComponentScan(basePackages = "com.app.lazy")
public class AppConfig {
#Bean
public Region getRegion(){
return new Region();
}
#Bean
public Country getCountry(){
return new Country();
}
}

Related

No adapter for handler with spring-boot custom starter

I am developing a spring-boot custom starter, and I want my components/beans to be initialized only if some conditions are met. These beans include a REST controller. I have made a #Configuration class for that purpose:
#Configuration
#ConditionalOnWebApplication
#Conditional(MyConditions.class)
public class MyConfiguration {
#Bean
public HandlerMapping registerController() {
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
urlMap.put("/custom", myController());
handlerMapping.setUrlMap(urlMap);
return handlerMapping;
}
#Bean
public MyController myController() {
return new MyController();
}
MyController class is:
public class MyController {
#GetMapping
public String hello() {
return "I'm here";
}
}
What I am trying to achieve is that localhost:8080/custom answers I'm here, but instead of that what I get is:
No adapter for handler [MyController#355b46cb]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler","path":"/custom"
I don't want any of my beans to be initialized if conditions are not met, that is why MyController is not annotated with #Controller or #RestController.
Any ideas on how I can achieve this?
Note: to test this, I have created a minimalistic SpringBoot project with a controller and I have added my custom starter as a dependency. MyConfiguration conditions are matched and beans are registered.

What is given priority in below scenario #Bean or #Component?

I am learning concepts of Spring & I came across #Bean & #Component annotations. I want to know what will happen in below scenario:
#Configuration
class ConfigClass {
#Bean
public ComponentClass ComponentClass() {
return new ComponentClass(someDependency1, someDependency2, someDependency3);
}
}
#Component
class ComponentClass{
private SomeDependency1 sd1;
private SomeDependency2 sd2;
private SomeDependency3 sd3;
public ComponentClass(SomeDependency1 sd1, SomeDependency2 sd2, SomeDependency3 sd3) {
/* initialize here */
}
}
I have declared ComponentClass as #Component which means it is a spring bean now. But I have also defined a #Bean for it in config class separately.
Which of these beans will be actually used as by default Spring is singleton?
What happens when I remove #Component?
Spring will notice a mistake and throw NoUniqueBeanDefinitionException during application startup.
If you remove #Component annotation it will work as expected, #Bean will be used for initialization.

#ConditionalOnBean not working with JdbcTemplate.class

I have some spring component:
#Component
#ConditionalOnBean(value={JdbcTemplate.class})
public class DictionaryHandler implements Handler {
private final JdbcTemplate jdbcTemplate;
#Autowired
public DictionaryHandler(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//some methods
}
In debug, I see, that JdbcTemplate bean was created:
But, my bean not created.It doesn't go into the constructor. But why? Bean JdbcTemplate is exist, but my bean not created.
Does the condition work wrong? I don't want create DictionaryHandler if JdbcTemplate is missing. Without this I get an error.
You should use #ConditionalOnBean on auto configuration classes only (otherwise the order is unspecified)
The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.
Since your JdbcTemplate bean is defined inside JdbcTemplateAutoConfiguration class so I assume that JdbcTemplateAutoConfiguration is marked with #Configuration.
In that case, you can ensure the instantiate of your bean by config:
#Configuration
#AutoConfigureAfter(JdbcTemplateAutoConfiguration.class)
public class DicationaryHandlerConfiguration {
#Bean
#ConditionalOnBean(JdbcTemplate.class)
public DictionaryHandler handler(JdbcTemplate jdbcTemplate) {
return new DictionaryHandler(jdbcTemplate)
}
}
public class DictionaryHandler implements Handler {
private final JdbcTemplate jdbcTemplate;
public DictionaryHandler(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//some methods
}
Check the documentation for ConditionalOnBean. It runs once and if the bean it requires not yet created - it will not trigger. The order of bean creation matters. You can try to lower priority of your component with #Order annotation or increase priority of you configuration/component class which holds JdbcTemplate bean.
From Annotation Type ConditionalOnBean:
The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.
In your case, the problem is DictionaryHandler bean is attempted to be created before processing the configuration class and given that JdbcTemplate bean is not yet in DI container, your DictionaryHandler is not getting instantiated.
One workaround could be to initialize DictionaryHandler in the same configuration class as JdbcTemplate.
Also, you can use different configuration class, but you'll have to use #DependsOn annotation.
An example:
#Component
#ConditionalOnBean(name = "bean1")
class AnotherBean {
}
#Configuration
class Config {
#Bean
public Object bean1() {
return new Object();
}
}
In the previous example AnotherBean will not be created, but it will be in this way:
#Configuration
class Config {
#Bean
public Object bean1() {
return new Object();
}
#Bean
#ConditionalOnBean(name = "bean1")
public AnotherBean anotherBean() {
return new AnotherBean();
}
}

Why when use #Autowired in #Configuration in Spring failed sometimes?

I'm using Spring Boot with a thrift server, and I have two #Configuration class with two bean generation method, and the code is as following:
#Configuration
public class EagleBeanCreator {
#Bean(destroyMethod = "destroy")
public EagleRestClient build() {
EagleRestClient client = new EagleRestClient();
// some set values code
return client;
}
}
And another one:
#Configuration
public class EagleServiceBuilder {
#Autowired
private EagleRestClient eagleProxy;
#Bean
public EagleService eagleService() {
EagleService service = new EagleService();
System.out.println(eagleProxy);
service.setEagleProxy(eagleProxy);
return service;
}
}
But when I run spring-boot:run, it print out null for "System.out.println(eagleProxy);"
Why?
=========================UPDATE=============================
I know setter injection or constructor injection works.
You may want to try this out.
#Configuration
public class EagleServiceBuilder {
#Bean
public EagleService eagleService(EagleRestClient eagleProxy) {
EagleService service = new EagleService();
System.out.println(eagleProxy);
service.setEagleProxy(eagleProxy);
return service;
}
}
My guess is that the way you currently implement doesn't indicate a dependency between the EagleService and EagleRestClient. So your current implementation leads to random initialization order between the two beans. The modified version tells Spring "Hey, my EagleService depends on EagleRestClient. Please initialize EagleRestClient before EagleService.
Because the order to load EagleBeanCreator and EagleServiceBuilder is not definite. You can use #Order or #ConditionalOnClass to make sure EagleBeanCreator initialize first.
Because the #Configuration bean are initialized in the same phase of bean lifecycle. I don't remember them clearly but something like:
Configurations -> Components -> Services
With the beans in the same phase, if they depend on each other, you should declare the load order by some #Conditional or #Order
Add #DependsOn("eagleRestClient") annotation on the definition of EagleService.
#DependsOn("eagleRestClient")
#Bean
public EagleService eagleService() {
EagleService service = new
EagleService();
System.out.println(eagleProxy);
service.setEagleProxy(eagleProxy);
return service;
}
Spring will then first create rest client then the eagle service.
First, you need to get the spring container through ApplicationContextAware try
ApplicationContext.getBean(EagleRestClient.class)

Injecting library class as dependencies in spring project

I have multiple library classes in my project which need to be injected into a service class. This is the error statement for IntegrationFactory class:
Consider defining a bean of type 'com.ignitionone.service.programmanager.integration.IntegrationFactory' in your configuration.
This error is coming on almost every injection where this library class is injected.
I have already added the Library package in #ComponentScan, but, as it is read-only file, I can not annotate the library class. I came to know from some answer here that Spring can not inject classes which it does not manage. This library is not built on spring.
I have tried to create a #Bean method which returns the IntegrationFactory(class in question) in the class where #Inject is used, but this too does not seem to work.
How can this be done, preferably without creating a stub/copy class?
This is EngagementServiceImpl class snippet:
#Inject
public EngagementServiceImpl(EngagementRepository engagementRepository,
#Lazy IntegrationFactory integrationFactory, TokenRepository tokenRepository,
EngagementPartnerRepository engagementPartnerRepository, MetricsService metricsService) {
this.engagementRepository = engagementRepository;
this.integrationFactory = integrationFactory;
this.tokenRepository = tokenRepository;
this.engagementPartnerRepository = engagementPartnerRepository;
this.metricsService = metricsService;
}
This is injection part:
#Autowired
private EngagementService engagementService;
This is ConfigClass:
#Configuration
public class ConfigClass {
#Bean
public IntegrationFactory getIntegrationFactory(){
Map<String, Object> globalConfig = new HashMap<>();
return new IntegrationFactory(globalConfig);
}
#Bean
#Primary
public EntityDataStore getEntityDataStore(){
EntityModel entityModel = Models.ENTITY;
return new EntityDataStore(this.dataSource(), entityModel );
}
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
}
You need to add your bean definitions in a configuration class.
#Configuration
public class ServiceConfig {
#Bean
public IntegrationFactory getIntegrationFactory(){
// return an IntegrationFactory instance
}
}
Then you have to make sure your #Configuration class gets detected by Spring, either by having it within your scanned path or by manually importing it via #Import from somewhere withing you scanned path. An example of #Import, considering you are using Spring Boot.
#Import(ServiceConfig.class)
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Hope this helps!
Your Bean IntegrationFactory can't be found, as it is not annotated with any Spring stereotype and therefore not recognized by the component scan.
As you have multiple options to provide an instance of your class to the application context, read the Spring documentation (which also includes samples) to find out which one fits you the most:
https://docs.spring.io/spring/docs/5.1.0.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts
One Option would be to create a factory which provides an instance of your class to the application context, like it is stated in the documentation:
#Configuration
public class AppConfig {
#Bean
public IntegrationFactory myIntegrationFactory() {
return new IntegrationFactory();
}
}
Do not forget to add the Configuration to the application context.

Categories