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

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();
}
}
}

Related

How to execute a runnable instead of bean creation in Spring configuration?

There is a usual configuration creating beans and performing an action using some of them.
The intention of initialAction is just execute print("Hello...") if certain conditions are in place (MyCondition.class). Obviously this return null is only required to keep the ceremony of beans creation and can be misleading for developers reading the code.
Is there a more elegant and convenient way to express the intention "run code after certain beans created if a condition is true"?
#Configuration
class App{
#Bean
#Conditional(MyCondition.class)
Object initialAction() {
var a = firstBean();
var b = secondBean();
print("Hello beans: $s, %s".formatted(a, b))
return null;
}
MyEntity firstBean(){
...
}
MyEntity secondBean(){
...
}
// other beans
}
There is an annotation #PostConstruct from javax or jakarta packages that are invoked once the dependency injection is done. Sadly, this cannot be combined with the #Conditional annotation.
You can, however, create a dedicated #Configuration + #Conditional class using #PostConstruct:
#Configuration
#Conditional(MyCondition.class)
public InitialActionConfiguration {
#Autowired
#Qualifier("firstBean")
private MyEntity firstBean;
#Autowired
#Qualifier("secondBean")
private MyEntity secondBean;
#PostConstruct
public void postConstruct() {
var a = firstBean;
var b = secondBean;
print("Hello beans: $s, %s".formatted(a, b));
return null;
}
// initializations in other #Configuration class
}
Once you do this, both MyEntity beans got initialized (if properly defined as beans) and this configuration class autowires them and prints them out only if the condition defined by MyCondition is satisfied.

Spring: Get the list of beans injected into a bean

I'm looking for a way to list the beans that are injected into a particular Spring bean at runtime. For example, given these two classes:
#Controller
public class TestController {
#Autowired
private TestComponent testComponent;
private final TestService testService;
public TestController(TestService testService) {
this.testService = testService;
}
}
and
#Service
public class TestService {
}
and
#Component
public class TestComponent {
}
The list of beans for the TestController class should return:
TestService (injected via constructor)
TestComponent (injected via #Autowired annotation)
Is there an existing Spring helper/utility that can return this information for me?
You can query names of dependent beans from the ConfigurableBeanFactory for a given bean name with the method getDependenciesForBean(). So in your example the code could look like
try (ConfigurableApplicationContext app = SpringApplication.run(MySpringApplication.class)) {
ConfigurableListableBeanFactory beanFactory = app.getBeanFactory();
String[] dependencies = beanFactory.getDependenciesForBean("testController");
System.out.println(Arrays.toString(dependencies)); // [testService, testComponent]
}
The problem hereby is that you only work on names of beans. So to make the code generic for a given bean instance you would have to find out the name of the bean (which can be non-unique) and also when getting the actual injected beans for these names it can be possible that you don't get the same instances (because of #Scope(SCOPE_PROTOTYPE) on the bean definition).

Spring Boot read values from application properties

I'm not sure if I understand it correctly, but from what I got, is that I can use #Value annotations to read values from my application.properties.
As I figured out this works only for Beans.
I defined such a bean like this
#Service
public class DBConfigBean {
#Value("${spring.datasource.username}")
private String userName;
#Bean
public String getName() {
return this.userName;
}
}
When the application starts I'm able to retrieve the username, however - how can I access this value at runtime?
Whenever I do
DBConfigBean conf = new DBConfigBean()
conf.getName();
* EDIT *
Due to the comments I'm able to use this config DBConfigBean - but my initial problem still remains, when I want to use it in another class
#Configurable
public SomeOtherClass {
#Autowired
private DBConfigBean dbConfig; // IS NULL
public void DoStuff() {
// read the config value from dbConfig
}
}
How can I read the DBConfig in a some helper class which I can define as a bean
Thanks
As Eirini already mentioned you must inject your beans.
The #Value annotation only works on Spring beans.
There is another way of accessing configuration with #ConfigurationProperties.
There you define a class that holds the configuration.
The main advantage is, that this is typesafe and the configuration is in one place.
Read more about this:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-vs-value
You shouldn't instantiate your service with the new operator. You should inject it, for example
#Autowired
private DBConfigBean dbConfig;
and then dbConfig.getName();
Also you don't need any #Bean decorator in your getName() method
You just need to tell spring where to search for your annotated beans. So in your configuration you could add the following:
#ComponentScan(basePackages = {"a.package.containing.the.service",
"another.package.containing.the.service"})
EDIT
The #Value, #Autowired etc annotations can only work with beans, that spring is aware of.
Declare your SomeOtherClass as a bean and add the package config in your #Configuration class
#Bean
private SomeOtherClass someOtherClass;
and then
#Configuration
#ComponentScan(basePackages = {"a.package.containing.the.service"
"some.other.class.package"})
public class AppConfiguration {
//By the way you can also define beans like:
#Bean
public AwesomeService service() {
return new AwesomeService();
}
}
Wrap your DBConfig with #Component annotation and inject it using #Autowired :
#Autowired
private DBConfig dbConfig;
Just add below annotation to your DBConfigBean class:
#PropertySource(value = {"classpath:application.properties"})

Spring: Injecting an external properties into java Configurations

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.

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