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.
Related
I have two separate beans configured with two custom conditional annotations that should be exact opposites of each other and never both autowired at the same time, yet both are being autowired. These annotations will be used on many classes, so I want to be able to reuse them throughout the application without duplicating the #ConditionalOnProperty configuration. Are my conditionals wrong, or is there a better way to accomplish this in code so I don't have to rely on #Conditional annotations for optionally autowiring beans?
Conditional annotations:
#Inherited
#Documented
#ConditionalOnProperty(prefix = "my-prefix", value = "enable-this-thing", havingValue = "false", matchIfMissing = false)
public #interface ConditionalOnNotEnablingThisThing {
}
#Inherited
#Documented
#ConditionalOnProperty(prefix = "my-prefix", value = "enable-this-thing", havingValue = "true", matchIfMissing = true)
public #interface ConditionalOnEnablingThisThing {
}
Shared interface:
public interface SharedInterface {
void doSomething();
}
Implementing classes:
#Component
#ConditionalOnEnablingThisThing
class EnabledThingComponent implements SharedInterface {
//code
}
#Component
#ConditionalOnNotEnablingThisThing
class NotEnabledThingComponent implements SharedInterface {
//code
}
Class attempting to autowire that throws a multiple available bean exception:
#Component
public class MyConsumer {
private final SharedInterface sharedInterface;
public MyConsumer(SharedInterface sharedInterface) {
this.sharedInterface = sharedInterface;
}
//code
}
I would like to first start off with saying this is not an ideal solution, but it worked for my specific usecase without too much heavy lifting.
The configuration class code:
#Configuration(MYCONFIG_NAME_CONSTANT)
public class MyConfig {
private final GenericApplicationContext genericApplicationContext;
public MyConfig(GenericApplicationContext genericApplicationContext) {
this.genericApplicationContext = genericApplicationContext;
configureConditionalBeans();
}
private void configureConditionalBeans() {
if(//my conditional code) {
genericApplicationContext.registerBean(
//registration details for conditional bean
);
}
}
}
Components that need to autowire in these conditional beans:
#Component
#DependsOn(MYCONFIG_NAME_CONSTANT) //optional will always be empty without this
public class MyConsumingComponent {
private final Optional<[MyConditionalBean]> conditionalBeanOptional;
public MyConsumingComponent(Optional<[MyConditionalBean]> conditionalBeanOptional) {
this.conditionalBeanOptional = conditionalBeanOptional;
}
//more code here
}
The biggest caveat I found is that the beans autowired in this way are never found by #ConditionalOnBean annotations, even when the bean has the #DependsOn specified. This means that any additional conditional beans relying on these underlying beans being configured will need to be conditionally set up in the same way. This caused a large pain for me since the beans I was attempting to conditionally autowire were ones a Spring Boot library was looking for. This meant the Spring Boot dependent beans had to be manually configured as well. I would still be interested in a better solution than this, but this solved the immediate problem.
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();
}
}
}
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"})
Consider this code:
public class Bean1 {}
public class Bean2 {
private final Bean1 bean1;
public Bean2(Bean1 bean1){
this.bean1 = bean1;
}
}
#Configuration
public class MyConfiguration {
#Bean
public Bean1 bean1(){
return new AImpl();
}
#Bean
public Bean2 bean2() {
return new BImpl(bean1());
}
#Bean
public Bean3 bean3() {
return new BImpl(bean1());
}
}
My knowledge of Java dicates, that two references of bean1 in bean2 and bean3 should be different, that, since I call the bean1() method twice, two different objects should be created.
However, under Spring, in the same ApplciationContext, etc. etc., both bean2 and bean3 will have the same reference to the same object of class Bean1.
How is that possible in Java? What mechanism does Spring use that allows it to somehow intercept method calls and put beans as result of those calls?
Class with the #Configurable annotation are treated in a special way. They are parsed using ASM and from the scanning special bean definitions are created. Basically each #Bean annotation is a special kind of factory bean.
Because the methods are treated as factory beans they are only invoked once (unless the scope isn't singleton of course).
Your configuration class is not executed as it.
Your class is first read by org.springframework.asm.ClassReader
The class org.springframework.context.annotation.ConfigurationClassParser parses your configuration class. Each method annoted by #Bean is associated to a org.springframework.context.annotation.BeanMethod.
I am having in Class A the following beans:
#Bean
public AsyncItemProcessor OneUploadAsyncItemProcessor() {
// ...
asyncItemProcessor.setDelegate(processor(OVERRIDDEN_BY_EXPRESSION, OVERRIDDEN_BY_EXPRESSION));
// ...
return asyncItemProcessor;
}
#Scope(value = "step", proxyMode = ScopedProxyMode.INTERFACES)
#Bean
public ItemProcessor<MyItem, MyItem> processor(#Value("#{jobParameters[pushMessage]}") String pushMessage, #Value("#{jobParameters[jobId]}") String jobId) {
return new PushItemProcessor(pushMessage, jobId);
}
Now I have in a class B the following:
#Bean
public AsyncItemProcessor TwpUploadAsyncItemProcessor() {
// ...
asyncItemProcessor.setDelegate(processor(OVERRIDDEN_BY_EXPRESSION, OVERRIDDEN_BY_EXPRESSION));
return asyncItemProcessor;
}
How I can Inject into class B the bean processor (which defined on class A) without duplicate it.
You just need to "autowire" it in class B. Something like:
class B {
#Autowire
//#Qualifier(value = "OneUploadAsyncItemProcessor")
[modifier] AsyncItemProcessor OneUploadAsyncItemProcessor;
}
I'm assuming you do not want –and hence going to delete- the bean TwpUploadAsyncItemProcessor from class B and include/autowire the (already defined) bean OneUploadAsyncItemProcessor from class A. If so, the #Qualifier annotation is not needed.
On the other hand if you want to autowire OneUploadAsyncItemProcessor without deleting TwpUploadAsyncItemProcessor and/or define (in the future) another bean of type AsyncItemProcessor you are going to need the #Qualifier annotation.