How to autowired profile configuration class on startup? - java

I'd like to create different database profile classes, each for a purpose of development, production and testing.
I tried the following with the help of http://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile/, but it won't wire correctly. Why?
interface DataConfig {
DataSource dataSource();
}
#Configuration
#Profile("dev")
public class StandaloneDataConfig implements DataConfig {
#Bean
#Override
public dataSource dataSource() {
//return the ds
}
}
#Configuration
#Profile("prod")
public class JndiDataConfig implements DataConfig { ... }
#Configuration
#PropertySource({"classpath:config.properties"})
class AppConfig {
#Autowired
private DataConfig cfg;
}
#Configuration
#ComponentScan
#Import(AppConfig.class)
#EnableTransactionManagement
public class SpringBootConfig extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
config.properties:
spring.profiles.active=dev
Result: Exception on startup
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private DataConfig dataConfig; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [DataConfig] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:508)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
... 34 common frames omitted
Spring 4.0.3.RELEASE
My setup though seem to work in general: if I remove the #Profile annotation on one of the databases, everything wires up correctly.

You need to rename your config.properties file to application.properties for Spring Boot to pick it up automatically.

I haven't tested your configuration, but my best guess (following some investigation I've done for this post) is that #PropertySources are not available for a #Conditional annotated #Configuration class when autowiring occurs.
According to the source code, #Profile is a #Conditional flavor of annotation with a Condition implementation.
For #PropertySource to be available when they are needed I think you would need your own custom #Conditional implementation just like in the SO post I mentioned above, where you define not a Condition but a ConfigurationCondition making sure to be used at ConfigurationPhase.REGISTER_BEAN phase.

Related

spring IOC qualifier and autowired strange behavior

I've specified Qualifier on both autowired et bean method.
So What am i missing ?
#Configuration
#EnableWebSecurity
public class CustomSecurityCOnfig {
#Bean
#Qualifier("entryPoint")
AuthenticationEntryPoint loginUrlAuthenticationEntryPoint() {
return new LoginUrlAuthenticationEntryPoint("/login");
}
}
I autowire the field this way
#Autowired
#Qualifier("entryPoint")
private AuthenticationEntryPoint loginUrlAuthenticationEntryPoint;
Stacktrace of the error :
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.env.Environment' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1716) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1272) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
EDIT
I Have another implementation of AuthenticationEntryPoint interface :
#Component
public class CustomAuthenticationEntryPoint extends BasicAuthenticationEntryPoint
But in my opinion it doesn't explain the error (as long as i specify the qualifiers)
You're mixing bean name and Qualifier
#Bean(name="someFancyBean")
public ClassXyx fooBar(){
return new ClassXyz()
}
In this example, method fooBar creates a bean of type ClassXyx and it's named someFancyBean. If you want to auto-wire this bean then you have to use
#Service
class SomeFancyService{
#Autowired #Qualifier("someFancyBean") ClassXyx xyz;
}
A configuration class can create multiple beans of the same type but their names are derived from function name. There's no point in using Bean annotation with name="XYZ" unless you want to rename that bean.
The Qualifier annotation is used for referring one of the beans of the same type.
Now coming back to your code
#Configuration
#EnableWebSecurity
public class CustomSecurityCOnfig {
#Bean
public AuthenticationEntryPoint entryPoint() {
return new LoginUrlAuthenticationEntryPoint("/login");
}
}
In your service you have to Autowired as.
#Autowired
#Qualifier("entryPoint")
private AuthenticationEntryPoint loginUrlAuthenticationEntryPoint;
Also, I would like to point one more thing bean accessibility across package/class.
Generally, all beans created by Spring IOC are public, but it has the same access modifier like Java classes. If you're creating a bean with package scope then you can't auto-wire in another package. Similarly, if a bean is created using private then that bean can be only auto-wired in that class.

#ContextConfiguration not bringing in only the config classes specified

I noticed that with #EnableWebMvc I'm getting the following error when running my tests: A ServletContext is required to configure default servlet handling. This issue is temporarily resolved by commenting out #EnableWebMvc then my tests all pass, however I want this in my web app.
I read in this post that I could put the #EnableWebMvc in another config class that isn't included in the tests(?). So I've tried this:
AppConfig.java
#Configuration
#ComponentScan(basePackages = "biz.martyn.budget")
#PropertySource("classpath:prod.properties")
#EnableTransactionManagement
public class AppConfig {
#Autowired
private Environment env;
#Bean(name = "dataSource", destroyMethod = "shutdown")
#Profile("prod")
public DataSource dataSourceForProd() {...
WebMvcConfig.java
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Bean
public ViewResolver viewResolver() {...
Then in my tests I'm attempting:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = AppConfig.class)
#Transactional
public class FundRepositoryTest {...
However, I'm still seeing the same error in my tests. I know it's the #EnableWebMvc as they all pass when I remove this. Have I misunderstood something with how #ContextConfiguration annotation works? By the way, I'm using Spring version 4.2.2.RELEASE for all my spring-* dependencies if that helps.
Below is also the error I'm seeing in my test run:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultServletHandlerMapping' defined in class path resource [org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'defaultServletHandlerMapping' threw exception; nested exception is java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1123)
I'm still not sure why the #ContextConfiguration annotation isn't only accepting the class(es) I provide but I have found that #WebAppConfiguration added to each test class provides the context required:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = AppConfig.class)
#Transactional
#WebAppConfiguration // <-- added this
public class FundRepositoryTest {...
It's an additional annotation I need to add though but my tests run now.

Why are some #Enable* annotations picked up without #Configuration?

I have the following classes in different packages:
#EnableWebMvc
public class ActuatorConfig
{
// empty
// only needed to get REST so I can hit Actuator endpoints
}
#Component // REMOVE THIS LINE?
#EnableBatchProcessing
public class BatchConfig
{
// empty
}
#Component
#Configuration
public class ScheduleJob
{
#Autowired
private JobBuilderFactory jobBuilder;
#Bean
protected JobExecutionListener scheduleJobListener()
{
return new ScheduleJobListener();
}
}
(EDIT: Added Application class)
#SpringBootApplication
public class AfxApplication
{
public static void main(String[] args)
{
SpringApplication.run(AfxApplication.class, args);
}
}
The first class turns on Actuator features. I don't need any other annotations.
However, if I remove #Component from BatchConfig as noted above, Boot won't start because it can't find the dependency on JobBuilderFactory that it needs in order to populate the #Autowired ScheduleJob.jobBuilder.
The exception:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.batch.core.configuration.annotation.JobBuilderFactory] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1373) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1119) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:545) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
... 25 common frames omitted
When is #Component needed and when isn't it needed?
(I pulled out the #Enable* annotations because I'm trying to run unit tests that don't need all features enabled, and this way I can specify the features I want to enable by using #ComponentScan(basePackageClasses).)
Thanks to M. Deinum, I finally have the answer.
By having #Enable*, I was disabling #EnableAutoConfiguration. The only exception is #EnableBatchProcessing, which is explicitly required. (I understand that it is so, but I am not sure why it cannot be handled by #EnableAutoConfiguration.)
The answer was a combination of the Application class listed above, and this class:
#Configuration
#EnableBatchProcessing
public class BatchConfig
{
}
Once this was in place, everything else worked.
How this will affect my test code will remain a task for another day...

Scanning #Configuration beans in Spring 4

Hy
I am upgrading my web app from Spring 3.1 to 4.1.8 but having issues. My code has not changed (only my pom.xml)
I have a configuration bean in my main context that looks like:
#Configuration
public class StorableServiceConfiguration {
...
#Bean
public StorableService<Template, Long> templateService(ITemplateJpaDao dao) {
return new DaoService<Template, Long>(Template.class, dao);
}
}
And obviously somewhere else in my web app, I have this statement:
#Autowired
#Qualifier("templateService")
private StorableService<Template, String> templateService;
Now this all worked fine with Spring 3.1.1 but after updating the version to 4.1.8, I am getting this error:
Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type [w.wexpense.service.StorableService] found for
dependency: expected at least 1 bean which qualifies as autowire
candidate for this dependency. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true),
#org.springframework.beans.factory.annotation.Qualifier(value=templateService)}
Anybody got a clue?
I read somewhere that there was a change in Spring 4 on how the context:component-scan behave regarding the #Configuration annotation but can't remember what. Is anybody aware of that?
Thanks
Spring 4 autowire beans using Java generics as form of #Qualifier.
So you have a Bean #Autowired with StorableService<Template, String> but in your #Configuration class your #Bean declares StorableService<Template, Long>.
If you want a StorableService<Template, String> instance you should create another #Bean at your #Configuration class, for example:
#Bean
public StorableService<Template, String> templateService(ITemplateJpaDao dao) {
return new DaoService<Template, String>(Template.class, dao);
}
and autowire it without the #Qualifier annotation:
#Autowired
private StorableService<Template, String> templateService;
Spring 4 will inject it perfectly. Look at this blog post to see this new feature of Spring 4.

How to change the property signUpUrl in ProviderSignInController in Spring Boot

In short
I need to change the property value of a bean defined in the auto configuration of the spring boot, which is not available to configure from application.properties
Descriptive
I want to change the signUpUrl of the bean ProviderSignInController. This is not available to change in the properties file according to the documentation.
http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
So I did somthing like this.
#Configuration
public class SomeConfig {
#Autowired
public void configureProviderSignInController(ProviderSignInController signInController){
signInController.setSignUpUrl("/register");
}
and ended up with the following error
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.social.connect.web.ProviderSignInController] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1301)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1047)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
But according to the autoconfiguration this bean should be available
https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/social/SocialWebAutoConfiguration.java
Please help and guild me if I'm doing this incorrectly.
The error describes that the problem is that you have not instantiated a bean with the type of ProviderSignInController in your method signature for configureProviderSignInController. Instead, you need to instantiate and configure the controller in one of your configurations with the proper constructor signatures required by the controller:
#Configuration
#EnableSocial
public class SocialConfig implements SocialConfigurer {
...
#Bean
public ProviderSignInController providerSignInController(
ConnectionFactoryLocator connectionFactoryLocator,
UsersConnectionRepository usersConnectionRepository) {
ProviderSignInController controller = new ProviderSignInController(
connectionFactoryLocator, usersConnectionRepository,
new SimpleSignInAdapter());
controller.setSignUpUrl("/register");
return controller;
}
}
Alternatively, if you are using XML configurations:
<bean class="org.springframework.social.connect.signin.web.ProviderSignInController">
<!-- relies on by-type autowiring for the constructor-args -->
<property name="signUpUrl" value="/register" />
</bean>
For more information, consult the Spring Social docs on enabling provider sign in.

Categories

Resources