Conditional spring bean creation - java

I have a question about Spring annotation configurations. I have a bean:
#Bean
public ObservationWebSocketClient observationWebSocketClient(){
log.info("creating web socket connection...");
return new ObservationWebSocketClient();
}
and I have a property file:
#Autowired
Environment env;
In the property file I want to have a special boolean property
createWebsocket=true/false
which signs whether a bean ObservationWebSocketClient should be created. If property value is false I don't want to establish web socket connection at all.
Is there any technical possibility to realize this?

Though I've not used this functionality, it appears that you can do this with spring 4's #Conditional annotation.
First, create a Condition class, in which the ConditionContext has access to the Environment:
public class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return null != env
&& "true".equals(env.getProperty("createWebSocket"));
}
}
Then annotate your bean:
#Bean
#Conditional(MyCondition.class)
public ObservationWebSocketClient observationWebSocketClient(){
log.info("creating web socket connection...");
return new ObservationWebSocketClient();
}
edit The spring-boot annotation #ConditionalOnProperty has implemented this generically; the source code for the Condition used to evaluate it is available on github here for those interested. If you find yourself often needing this funcitonality, using a similar implementation would be advisable rather than making lots of custom Condition implementations.

Annotate your bean method with #ConditionalOnProperty("createWebSocket").
Note that Spring Boot offers a number of useful conditions prepackaged.

For Spring Boot 2+ you can simply use:
#Profile("prod")
or
#Profile({"prod","stg"})
That will allow you to filter the desired profile/profiles, for production or staging and for the underlying Bean using that annotation it only will be loaded by Springboot when you set the variable spring.profiles.active is equals to "prod" and ("prod" or "stg"). That variable can be set on O.S. environment variables or using command line, such as -Dspring.profiles.active=prod.

As for me, this problem can be solved by using Spring 3.1 #Profiles, because #Conditional annotation give you opportunity for define some strategy for conditional bean registration (user-defined strategies for conditional checking), when #Profiles can based logic only on Environment variables only.

Related

Spring Boot 2 - Wire Two LDAP Templates

I need to configure multiple LDAP data sources / LdapTemplates in my Spring Boot 2 application. The first LdapTemplate will be used for most of the work, while the second will be used for a once-in-a-while subset of data (housed elsewhere).
I have read these StackOverflow questions regarding doing that, but they seem to be for Spring Boot 1.
Can a spring ldap repository project access two different ldap directories?
Multiple LDAP repositories with Spring LDAP Repository
From what I can gather, much of that configuration/setup had to be done anyway, even for just one LDAP data source, back in Spring Boot 1. With Spring Boot 2, I just put the properties in my config file like so
ldap.url=ldap://server.domain.com:389
ldap.base:DC=domain,DC=com
ldap.username:domain\ldap.svc.acct
ldap.password:secret
and autowire the template in my repository like so
#Autowired
private final LdapTemplate ldapTemplate;
and I'm good to go. (See: https://stackoverflow.com/a/53474188/3669288)
For a second LDAP data source, can I just add the properties and configuration elements for "ldap2" and be done (see linked questions)? Or does adding this configuration cause Spring Boot 2's auto configuration to think I'm overriding it and so now I lose my first LdapTemplate, meaning I now need to go explicitly configure that as well?
If so, do I need to configure everything, or will only a partial configuration work? For example, if I add the context source configuration and mark it as #Primary (does that work for LDAP data sources?), can I skip explicitly assigning it to the first LdapTemplate? On a related note, do I still need to add the #EnableLdapRepositories annotation, which is otherwise autoconfigured by Spring Boot 2?
TLDR: What's the minimum configuration I need to add in Spring Boot 2 to wire in a second LdapTemplate?
This takes what I've learned over the weekend and applies it as an answer to my own question. I'm still not an expert in this so I welcome more experienced answers or comments.
The Explanation
First, I still don't know for certain if I need the #EnableLdapRepositories annotation. I don't yet make use of those features, so I can't say if not having it matters, or if Spring Boot 2 is still taking care of that automatically. I suspect Spring Boot 2 is, but I'm not certain.
Second, Spring Boot's autoconfigurations all happen after any user configurations, such as my code configuring a second LDAP data source. The autoconfiguration is using a couple of conditional annotations for whether or not it runs, based on the existence of a context source or an LdapTemplate.
This means that it sees my "second" LDAP context source (the condition is just that a context source bean exists, regardless of what its name is or what properties it is using) and skips creating one itself, meaning that I no longer have that piece of my primary data source configured.
It will also see my "second" LdapTemplate (again, the condition is just that an LdapTemplate bean exists, regardless of what its name is or what context source or properties it is using) and skip creating one itself, so I again no longer have that piece of my primary data source configured.
Unfortunately, those conditions mean that in this case there is no in-between either (where I can manually configure the context source, for example, and then allow the autoconfiguration of the LdapTemplate to still happen). So the solution is to either make my configuration run after the autoconfiguration, or to not leverage the autoconfiguration at all and set them both up myself.
As for making my configuration run after the autoconfiguration: the only way to do that is to make my configuration an autoconfiguration itself and specify its order to be after Spring's built-in autoconfiguration (see: https://stackoverflow.com/a/53474188/3669288). That's not appropriate for my use case, so for my situation (because Spring Boot's setup does make sense for a standard single-source situation) I'm stuck forgoing the autoconfiguration and setting them both up myself.
The Code
Setting up two data sources is pretty well covered in the following two answers (though partly for other reasons), as linked in my question, but I'll also detail my setup here.
Can a spring ldap repository project access two different ldap directories?
Multiple LDAP repositories with Spring LDAP Repository
First up, the configuration class needs to be created, as one was not previously needed at all with Spring Boot 2. Again, I left out the #EnableLdapRepositories annotation partly because I don't use it yet, and partly because I think Spring Boot 2 will still cover that for me. (Note: All of this code was typed up in the Stack Overflow answer box as I don't have a development environment where I'm writing this, so imports are skipped and the code may not be perfectly compilable and function correctly, though I hope it's good.)
#Configuration
public class LdapConfiguration {
}
Second is manually configuring the primary data source; the one that used to be autoconfigured but no longer will be. There is one piece of Spring Boot's autoconfiguration that can be leveraged here, and that is its reading in of the standard spring.ldap.* properties (into a properties object), but since it wasn't given a name, you have to reference it by its fully qualified class name. This means you can skip straight to setting up the context source for the primary data source. This code is not quite as full featured as the actual autoconfiguration code (See: Spring Code)
I marked this LdapTemplate as #Primary because for my use, this is the primary data source and so it's what all other autowired calls should default to. This also means you don't need a #Qualifier where you autowire this source up (as seen later).
#Configuration
public class LdapConfiguration {
#Bean(name="contextSource")
public LdapContextSource ldapContextSource(#Qualifier("spring.ldap-org.springframework.boot.autoconfigure.ldap.LdapProperties") LdapProperties properties) {
LdapContextSource source = new LdapContextSource();
source.setUrls(properties.getUrls());
source.setUserDn(properties.getUsername());
source.setPassword(properties.getPassword());
source.setBaseEnvironmentProperties(Collections.unmodifiableMap(properties.getBaseEnvironment()));
return source;
}
#Bean(name="ldapTemplate")
#Primary
public LdapTemplate ldapTemplate(#Qualifier("contextSource") LdapContextSource source) {
return new LdapTemplate(source);
}
}
Third is to manually configure the secondary data source, the one that caused all of this to begin with. For this one, you do need to configure the reading of your properties into an LdapProperties object. This code builds on the previous code, so you can see the complete class for context.
#Configuration
public class LdapConfiguration {
#Bean(name="contextSource")
public LdapContextSource ldapContextSource(#Qualifier("spring.ldap-org.springframework.boot.autoconfigure.ldap.LdapProperties") LdapProperties properties) {
LdapContextSource source = new LdapContextSource();
source.setUrls(properties.getUrls());
source.setUserDn(properties.getUsername());
source.setPassword(properties.getPassword());
source.setBaseEnvironmentProperties(Collections.unmodifiableMap(properties.getBaseEnvironment()));
return source;
}
#Bean(name="ldapTemplate")
#Primary
public LdapTemplate ldapTemplate(#Qualifier("contextSource") LdapContextSource source) {
return new LdapTemplate(source);
}
#Bean(name="ldapProperties2")
#ConfigurationProperties("app.ldap2")
public LdapProperties ldapProperties2() {
return new LdapProperties();
}
#Bean(name="contextSource2")
public LdapContextSource ldapContextSource2(#Qualifier("ldapProperties2") LdapProperties properties) {
LdapContextSource source = new LdapContextSource();
source.setUrls(properties.getUrls());
source.setUserDn(properties.getUsername());
source.setPassword(properties.getPassword());
source.setBaseEnvironmentProperties(Collections.unmodifiableMap(properties.getBaseEnvironment()));
return source;
}
#Bean(name="ldapTemplate2")
public LdapTemplate ldapTemplate2(#Qualifier("contextSource2") LdapContextSource source) {
return new LdapTemplate(source);
}
}
Finally, in your class that uses these LdapTemplates, you can autowire them as normal. This uses constructor autowiring instead of the field autowiring the other two answers used. Either is technically valid though constructor autowiring is recommended.
#Component
public class LdapProcessing {
protected LdapTemplate ldapTemplate;
protected LdapTemplate ldapTemplate2;
#Autowired
public LdapProcessing(LdapTemplate ldapTemplate, #Qualifier("ldapTemplate2") LdapTemplate ldapTemplate2) {
this.ldapTemplate = ldapTemplate;
this.ldapTemplate2 = ldapTemplate2;
}
}
TLDR: Defining a "second" LDAP data source stops the autoconfiguration of the first LDAP data source, so both must be (nearly fully) manually configured if using more than one; Spring's autoconfiguration can not be leveraged even for the first LDAP data source.

How to Lazy load all the Spring beans whether it is defined by #Bean or #Component in Springboot 2.2

I am writing a spring application which is interactive and basically handles lots of commands like create, list, update, delete various types of resources.
For now, just assume a single run of the application handles only a single command and the program exits.
For all the classes to validate command, execute the command, required factory classes for each resource there is a separate class and each class is annotated with #Component annotation for spring to manage all the components.
There are also some of the manually defined beans by #Bean method.
Now that my application first identifies what kind of command is executed (create, delete, list, update, etc), I want the only beans of that command to be created and Autowired wherever required (after taking command from user) and I want to avoid the creation of dozens of beans related to other commands.
On searching, I came to know about Lazy instantiation of Beans that spring provides.
However, I am not sure if it is the weapon I am searching for.
What I tried
Very first I found #Lazy annotation, but since I want to lazily load all the Beans, I don't want to write #Lazy everywhere in each class.
Then I found setting below property in application.yml does the work.
spring:
main:
lazy-initialization: true
I tried that but still, it is not lazily creating the beans.
My application.yml files looks like this
spring:
main:
lazy-initialization: true
My main SpringBootApplication file looks like this:
#Slf4j
#SpringBootApplication
public class SpringBootApplication {
public static void main(String[] args) {
System.out.println("Loading Application...");
ApplicationContext context = SpringApplication.run(SpringBootApplication.class, args);
final AtomicInteger counter = new AtomicInteger(0);
log.info("**************** START: Total Bean Objects: {} ******************", context.getBeanDefinitionCount());
Arrays.asList(context.getBeanDefinitionNames())
.forEach(beanName -> {
log.info("{}) Bean Name: {} ", counter.incrementAndGet(), beanName);
});
log.info("**************** END: Total Bean: {} ******************", context.getBeanDefinitionCount());
}
}
My other classes looks like this:
#Component
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class MyClass1 implements ResourceCreator<MyClass2, MyClass3> {
private final RequestValidatorImpl requestValidator;
private final ResourceCreator resourceCreator;
#Override
public MyClass2 toImplementFunction(MyClass3 myclass3) {
//logic
}
On running the application, It prints all the classes where I annotated #Component as well as beans created by #Bean method.
I have also tried using below in Application.properties but still no use.
spring.main.lazy-initialization=true
Also, if you wish, please comment on whether I should use #Component for each Class like I am using or not and what is better practice instead.
I think you misunderstood the meaning of the lazy flag you are passing,
It means that the object will be created only when it is invoked but it does not say that it will not scan that package. Spring will scan all packages and store bean definition names but it will create the objects only when it is invoked, if you have passed the lazy flag to it.
You can verify this behaviour by checking the number of beans created when you pass the lazy flag as true and false.
you can check it as given below
ApplicationContext context = SpringApplication.run(SpringBootApplication.class, args);
System.out.println("count:"+context.getBeanDefinitionCount());
Edit on Apr/7th 2020 start
Another way to do that is create a constructor and use that to inject the autowired properties and print out a log when they enter the constructor.
I did the same in a sample project and below is he result, first one is for eager initialization and next one for lazy.
spring.main.lazy-initialization=false
Application logs
Inside Constructor
calling bean
inside bean method
spring.main.lazy-initialization=true
Application logs
calling bean
Inside Constructor
inside bean method
Edit on Apr/7th 2020 end
Please mark this as answered if I answered your question.
Thank you
Long story short:
For anyone wanting to lazily initialize their whole Spring Boot context, setting this property to true is the way to go:
spring.main.lazy-initialization=true
Pro tip:
It can be used in combination with the #Lazy annotation, set to false; so all the defined beans will use lazy initialization, except for those that we explicitly configure with #Lazy(false).
In such a way, the lazy initialization becomes opt-out instead of the default opt-in.

What's the Spring way of accessing properties from an entity?

I'm new to Spring and I'm building an application where some entities (JPA/Hibernate) need access to a property from application.properties. I do have a configuration class in which this is trivial:
#Configuration
public class FactoryBeanAppConfig {
#Value("${aws.accessKeyId}")
private String awsAccessKeyId;
#Value("${aws.secretKey}")
private String awsSecretKey;
}
but since entities do not have and I think they should not have the annotations such as #Configuration or #Component, what's the Spring way for them to access the property?
Now, I know I can create my own class, my own bean, and make it as a simple wrapper around the properties; but is that the Spring way to do it or is there another way?
specify Property file location using #PropertySource
Something like below
#PropertySource("classpath:/application.proerties")
You also need to add below bean in your config
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigIn() {
return new PropertySourcesPlaceholderConfigurer();
}
There is no "Spring way", since JPA entities and Spring have nothing to do with each other. Most importantly, JPA entities are not Spring beans, so Spring doesn't know or care about them as they're not managed by Spring.
You can try to hack around, trying in vain to access Spring's configuration from code that should not be trying to access it, or you can accept the truth that your design is broken and you're trying to do something that's not meant to be done.
As was proposed several times, use a service class for this. It's managed by Spring, so it can access the Spring config, and it can handle entities, so there's no crossing boundaries.
First create a public static variable in some bean managed by Spring, then make the following use of the #Value annotation.
public static String variable;
#Value("${variable}")
private void setVariable(String value) {
variable = value;
}
It will be set at runtime on startup, now you can access it from entities and everywhere else because it is just a public static var.
You can use #PropertySource to load the properties file as follows
#Configuration
#PropertySource("classpath:/com/organization/config/application.proerties")
public class FactoryBeanAppConfig {
...
}
Entities should not acces environment properties. If you are using your entity through a service, then the service can access the properties to act on the entity.

How to exclude a class from Spring using new Condition interface?

Often we have tests and production environment different from each other. Depending on some configuration parameters, certain classes are not required to be registered as beans by container (while using spring). Is there a way to dynamically skip such classes from application context ?
If the class that should been used in the test is only available the classpath while testing, and your problem is about replacing injected classes, then you could annotate the class that is used in TESTs with #Primary annotation.
#Primary is a feature of spring that is much older then conditions and less powerful but it is really easy to use. Is says: when an injection point
could been fulfilled with two Beans then use the bean that is annotated with #Primary, instead throwing an exception that complains about ambiguous beans.
So when you add a bean in test scope with #Primary annotation, then this bean replaces the original bean in its injection points.
Using Spring 4 #Conditional annotation we can.
Check here for details of Spring Condition interface
First create a Class say- "ComponentScanCondition which implements Spring's "Condition" interface. And the only method "matches" returns false if the system property is not null or it is a "test" environment.
public class ComponentScanCondition implements Condition{
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metaData) {
return System.getProperty("environment")!=null && System.getProperty("environment").equals("test")? false:true;
}
}
Now with annotation- #Conditional(ComponentScanCondition.class) you can control the component scan on the classes you do not need in test environment.
In jUnit test class, set up a system property as below:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class testMyClass{
#BeforeClass
public static void setUpBeforeClass() throws Exception{
System.setProperty("environment", "test");
}
#Test
public void testSomeMethod(){
}
}
And in the classes, not required in test, use #Conditional annotation.For example if you do not need UserProfile class in test environment, skip it as below:
#service("userProfile")
#Conditional(ComponentScanCondition.class)
public class UserProfile{
}
As in test class the system property "environment" is set to "test", matches method will return false and Spring will skip the class UserProfile to be scanned.
In prod environment this property will not be set and will be null so matches will return true and hence UserProfile (and those class with #Conditional) will be scanned by Spring to register as beans in the container.

Using placeholders in Annotations on Spring Java Configurations

I'm a little bit lost in Spring's Property Replacement mechanism. Lets say I have this Java Config
#Configuration
#ComponentScan(basePackageClasses = Application.class)
#PropertySources({
#PropertySource("classpath:/config/default.properties")
})
public class ApplicationConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
return pspc;
}
Now I want to add a Spring-Data Annotation #EnableMongoRepositories and define a custom basepackage for scanning, using a custom placeholder like follows
#EnableMongoRepositories("${my.custom.repobasepackage}"). The Placeholder is defined in my default.properties.
However, this property cannot be resolved here. When diving deeper into Spring's property replacement, I can see that it tries to resolve the property, so it is possible to do so.
However, the underlying Environment class which is used to replace the placeholder does not know about my PropertyPlaceholderConfigurer, but only know about my SystemProperties and my VM-Props. :-(
I can see that in org.springframework.context.annotation.ClassPathBeanDefinitionScanner#getOrCreateEnvironment.java#339 (I'm using Spring 4.0.1) my "PropertyPlaceholder" is already in place, so its not a question of ordering in initialization, but is not used, because the used BeanDefinitionRegistry does not implement the Interface EnvironmentCapable. Here, my understanding of the Spring App-Context Bootstrapping is at the end.
Can anybody help me out here? Is there a BeanDefinitionRegistry out there which is capable of providing the Environment Instance which uses my Property Placeholder?
Any help is highly appreciated!! I got cookies for you! :-))
Cheers, Stefan
I imagine that the #PropertySources (you only have one by the way so you don't need the wrapper) are added to the Environment after the config classes are processed, so it will be too late to resolve on one of the annotations of those classes themselves. You can verify this by setting my.custom.repobasepackage as a System property.
As an alternative I encourage you to try out Spring Boot (where application.properties is added to the Environment before any configuration is processed).

Categories