Spring: Injecting an external properties into java Configurations - java

I've got a spring boot application (1.3.1.RELEASE) and recently experienced a strange behavior when trying to inject a #ConfigurationProperties bean into my configuration. I'm not an expert in Spring, so my question is how can this behavior be explained?
So the setup is like that:
MyApplication.java:
package me.developer;
#SpringBootApplication
public class MyApplication {
public static void main(final String... args) {
SpringApplication.run(<..>, args);
}
}
SecurityProperties.java:
package me.developer.document.security;
#Setter
#Getter
#Component
#ConfigurationProperties(prefix = "security")
public class SecurityProperties {
private List<String> apiKeys = new ArrayList<>();
}
SecurityConfiguration.java
package me.developer.document;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private SecurityProperties securityProperties;
#Override
protected void configure(final HttpSecurity http) throws Exception {
<..>
}
}
When the application is started I get an error that Spring can't inject SecurityConfiguration.securityProperties because it knows nothing about me.developer.document.security.SecurityProperties bean.
But if I rename SecurityProperties to SicurityProperties (second letter "e" --> "i") - it works! Don't ask me how did I get to that, but I'm just curious how this behavior can be explained? I.e. from my perspective it should either work or not, but not depending of the bean name, etc...
Update:
I also works if I don't touch the class name but explicitly specify the bean name like that
package me.developer.document.security;
#Setter
#Getter
#Component("securityProperties.my")
#ConfigurationProperties(prefix = "security")
public class SecurityProperties {
private List<String> apiKeys = new ArrayList<>();
}
And I don't provide any additional qualifiers during autowiring... Why is it happening?

Assuming that you have a dependency on spring-boot-starter-security, Spring Boot will be auto-configuring its own SecurityProperties bean that's also annotated with #ConfigurationProperties. Unfortunately this bean will have the same name as your bean as the class names (ignoring the package) are the same. This name clash means that Spring Boot's been will be overriding your security properties bean. You should see a warning message to that effect logged during startup.
As you have observed, changing the name of your bean so that there's no longer a name clash will solve the problem.

Related

Repository Injection not been recognized as bean

Hy everybody
I have this simplified version of my trouble. I´m trying to run my application but it fails as Repository is not beign recognized to be injected. I already tried to annotate Repository as Service and to add the repository package to be scanned, but none works. Can someone help me?
I get an exception
Description:
Field topicRepository in br.com.alura.controller.TopicController required a bean of type 'br.com.alura.repository.TopicRepository' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'br.com.alura.repository.TopicRepository' in your configuration.
Controller/Service
#Autowired
private TopicRepository topicRepository;
#GetMapping(value = "/api/topics", produces = MediaType.APPLICATION_JSON_VALUE)
public Page<TopicBriefOutputDto> listTopics(TopicSearchInputDto topicSearch, #PageableDefault(sort="creationInstant", direction=Sort.Direction.DESC) Pageable pageRequest) {
Specification<Topic> topicSearchSpecification = topicSearch.build();
Page<Topic> topics = this.topicRepository.findAll(topicSearchSpecification, pageRequest);
return TopicBriefOutputDto.listFromTopics(topics);
}
Start
#SpringBootApplication
#Configuration
#ComponentScan(basePackages ={"br.com.alura.controller"})
#EntityScan
#EnableAutoConfiguration
#EnableJpaRepositories
#EnableSpringDataWebSupport
public class ForumApplication {
public static void main(String[] args) {
SpringApplication.run(ForumApplication.class, args);
}
}
Repository
public interface TopicRepository extends Repository<Topic, Long>, JpaSpecificationExecutor<Topic> {
}
Probably You set incorrect base package. The package scope is too narrow.
Try this:
#EnableJpaRepositories(basePackages = {"br.com.alura.repository"})
or change br.com.alura.controller to br.com.alura
#ComponentScan(basePackages ={"br.com.alura"})

Getting null for Autowired bean created using Java config

I am creating a REST service in Spring boot. I am creating a bean in config class and trying to use in service class by auto wiring, but I am always getting null, I have tried in constructor injection as well but not working. Below is the code,
Main app
#SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
REST controller
#RestController
#RequestMapping("/v1")
public class RestController {
#Autowired
private Service service;
Service
#Service
public class ServiceImpl implements Service {
//This is the bean
#Autowired
private Record record;
public ServiceImpl() { //-----------------> tried injecting in constructor as well
System.out.println(record); //-------------------> null
}
Config class
#Configuration
public class AppConfig {
#Bean
public Record record() {
return new Record("test");
}
}
I noted whenever I remove the record() from config class I get below error
required a bean of type 'com.ns.service.Record' that could not be found
And after adding the method the error is not reported but null is returned, which indirectly means record() is considered as returning the required bean.
I can't find what I am doing wrong please advise.
Project folder structure
I think you're doing everything right conceptually
Spring creates an object first and only after that injects the values (technically done in bean post processors):
So try this:
#Service
public class ServiceImpl implements Service {
//This is the bean
#Autowired
private Record record;
public ServiceImpl() {
// here the record is null - not injected yet
System.out.println(record);
}
#PostConstruct
public void checkThisOut() {
// here print the record
}
You say you've tried constructor injection as well - it should work because spring has to inject something into the constructor of a bean (ServiceImpl) or fail. Please show the code snippet
One this that might be wrong in some level (although it doesn't sound like this from your description) is that you have to put all the #Configuration/#Service annotated classes in the package that is the same or underneath the package where you've created the main class annotated with #SpringBootApplication annotation. It instructs spring boot where to look for the beans.
So make sure your classes obey this rule...

What's the difference between #ComponentScan and #Bean in a context configuration?

There are at least 2 approaches to put Spring beans into a context configutation:
Declare a method with #Bean inside the configuration class.
Put #ComponentScan on the configuration class.
I was expecting that there are no difference between the 2 approaches in terms of the resulting Spring beans.
However, I found an example to demonstrate the difference:
// UserInfoService.java
public interface UserInfoService
{
#PreAuthorize("isAuthenticated()")
String getUserInfo ();
}
// UserInfoServiceTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#ContextHierarchy({
#ContextConfiguration(classes = TestSecurityContext.class),
#ContextConfiguration(classes = UserInfoServiceTest.Config.class)
})
public class UserInfoServiceTest
{
#Configuration
public static class Config
{
#Bean
public UserInfoService userInfoService ()
{
return new UserInfoServiceImpl();
}
}
#Autowired
private UserInfoService userInfoService;
#Test
public void testGetUserInfoWithoutUser ()
{
assertThatThrownBy(() -> userInfoService.getUserInfo())
.isInstanceOf(AuthenticationCredentialsNotFoundException.class);
}
#Test
#WithMockUser
public void testGetUserInfoWithUser ()
{
String userInfo = userInfoService.getUserInfo();
assertThat(userInfo).isEqualTo("info about user");
}
The above code is to test the security annotation in the service UserInfoService. However, it will fail on testGetUserInfoWithoutUser(). The reason is that the bean userInfoService didn't get proxied by Spring Security. Therefore, the call userInfoService.getUserInfo() didn't get blocked by the annotation #PreAuthorize("isAuthenticated()").
However, if I replace the #Bean annotation with #ComponentScan, everything will start to work. That is, the bean userInfoService will get proxied and the call userInfoService.getUserInfo() will be blocked by the #PreAuthorize annotation.
Why are the approaches between the #Bean and #ComponentScan different? Did I miss something?
P.S. The full example is here and the above mentioned fix is here.
It is because #ContextHierarchy will create multiple spring contexts with parent children hierarchy. In your case, TestSecurityContext defines the bean configuration for the parent context while UserInfoServiceTest.Config defines for the children context.
If there is no #ComponentScan on the UserInfoServiceTest.Config , the security related beans are defined in the parent context which are invisible to the UserInfoService bean in the children context , and hence it does not get proxied by Spring Security.
On the other hand , if you define #ComponentScan on the UserInfoServiceTest.Config , it will also scan all the #Configuration beans from the package that containing UserInfoService (and all of its sub package) . Because TestSecurityContext is also inside this package and hence it get scanned and the security related beans are also configured for the children context. UserInfoService in children context will then get proxied by Spring Security.(Note : In this case ,both the parent and children context has their own set of security related beans)
By the way , if you just need a single context , you can simply use #ContextConfiguration :
#ContextConfiguration(classes= {TestSecurityContext.class,UserInfoServiceTest.Config.class})
public class UserInfoServiceTest {
public static class Config {
#Bean
public UserInfoService userInfoService() {
return new UserInfoServiceImpl();
}
}
}

Spring boot, error defining bean from same interface and class

I have a class that define a list of zipcodes. In my Application class I define two objects of the same class and interface but with different information from application.yml
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
#ConfigurationProperties(prefix = "catalog.city1")
public CatalogConfiguration city1() {
return new Catalog();
}
#Bean
#ConfigurationProperties(prefix = "catalog.city2")
public CatalogConfiguration city2() {
return new Catalog();
}
}
When I want to use one like that...
#Autowired
#Qualifier("city1")
CatalogConfiguration myCity;
...I get this error:
No qualifying bean of type [] is defined: expected single matching
bean but found 2
I checked all documentation that I could, but I didn't found where it's my mistake.
Thank you in advance
You have defined 2 beans of same type. Spring boot will simply override second bean over the first. And the way you are referencing the beans is by name. So I guess what you need to do is #Qualifier at the bean definition too with the same alias as city1 and city2. Or do it like #Bean('')

Using different java based spring context configurations with cucumber

I am trying to get Cucumber working with Spring. In our code, we are already using java based Spring configuration. I am having trouble getting it to work in the following scenario. Can someone please help?
Today , in our integration test classes we use #ContextConfiguration for each class and provide the config class that is declared with in that integration test class for loading the beans. Config class is annotated with #Configuration. Same bean could be instantiated differently in 2 different classes Config classes used in 2 different integration test classes.
So when I use Cucumber, since the Contextconfiguration differs on different classes, it looks for 'Cucumber.xml' . In the xml file, I am using component-scan to scan the cucumber step definition classes by giving the package name that these classes use (both classes have same package name) . Since all beans gets loaded in same context, Cucumber is failing to load the beans when it finds the same bean defined in these different config classes .
How do I get over this problem of creating same bean but in different ways and use them in different classes?
Please note that I am not looking for a solution that creates lot of churn from our existing coding practices, so having per-test-xml file is not an option for me.
Here is how our code looks:
Class NameAndAddressProviderIntegrationTestSteps :-
#ContextConfiguration(locations="classpath:cucumber.xml")
public class NameAndAddressProviderIntegrationTestSteps {
#Configuration
#Import({
xyz.class,
abc.class,
NameAndAddressProvider.class
})
#ImportResource({
"file:configuration/spring-configuration/abc.xml",
"file:configuration/spring-configuration/xyz.xml"
})
public static class Config {
#Bean
AccountHolderDataMap dataMap() {
AccountHolderDataMap data = new AccountHolderDataMap();
data.put(ID,
new AccountHolderData(customerID));
data.get(customerID).setCustomerplaceID(testCustomerplaceID);
return data;
}
}
#Inject
private NameAndAddressProvider provider;
#When("^I call nameandAddress provider with a 'customerId'$")
public void i_call_nameandAddress_provider_with_a_customerId() throws DependencyException {
System.out.println("Entering when method");
names = provider.getNames(customerID);
System.out.println(provider.toString());
}
......
}
Class AddressProviderIntegrationTestSteps:-
#ContextConfiguration(locations="classpath:cucumber.xml")
public class AddressProviderIntegrationTestSteps {
#Configuration
#Import({
abc.class,
xyz.class,
AddressesProvider.class
})
#ImportResource({
"file:configuration/spring-configuration/test-environment.xml",
"file:configuration/spring-configuration/test-logging-config.xml"
})
public static class Config {
#Bean
#DependsOn("Environment")
AccountHolderDataMap data() {
AccountHolderDataMap data = new AccountHolderDataMap();
data.put(testCustomerID,
new AccountHolderData(testCustomerID, testCustomerplaceID,businessType));
return data;
}
}
private static final String testCustomerID = "1234";
private static final String testMarketplaceID = "abc";
#Inject
private AddressesProvider provider;
#When("^I call AddressesProvider provider with a 'CustomerID'$")
public void i_call_AddressesProvider_provider_with_a_CustomerID() throws Throwable {
List<Address> addresses = provider.getAddresses(testCustomerID);
Log.info(addresses.get(0).toString());
assertTrue(addresses.size()==1);
}
}
And here is the nested exception I am getting:-
"nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [....AccountHolderDataMap] is defined: expected single matching bean but found 2: dataMap,data"
Appreciate your help!
I've managed multiple sources for bean-definitions. You can use this at a starting point (or others in the internet as your question is quite old)
I am using spring4, see my other cucumer post for the pom
At the stepdefs use a config.class
#ContextConfiguration(classes = { CucumberConfiguration.class })
public class StepdefsTest123 {
#Autowired bean; // from cucumberBeanContext.xml
#When("^A$")
public void a() throws Throwable {
System.out.println(bean.getFoo());
}
}
in the config-class add aditional beandefinitions
#Configuration
#ComponentScan(basePackages = "package.here.cucumber")
#ImportResource("classpath:cucumberBeanContext.xml")
public class CucumberConfiguration {
// nothing to do here
}

Categories

Resources