Spring Boot Kafka Multiple Consumers with different properties configuration using appication.yml/properties - spring

I have seen examples where we have a java configuration class and we define multiple KafkaListenerContainer and pass the required containerType to #kafkaListener. But i am exploring if there are any ways to achieve the same using Spring Boot auto Kafka configuration via appication.yml/properties.

No; Boot will only auto-configure one set of infrastructure; if you need multiple, you need to define them as beans.
However, with recent versions (since 2.3.4), you can add a listener container customizer to the factory so you can customize each listener container, even though they are created by the same factory; some properties can also be overridden on the #KafkaListener annotation itself.
Example:
#Component
class Customizer {
public Customizer(ConcurrentKafkaListenerContainerFactory<?, ?> factory) {
factory.setContainerCustomizer(container -> {
if (container.getContainerProperties().getGroupId().equals("slowGroup")) {
container.getContainerProperties().setIdleBetweenPolls(60_000);
}
});
}
}

Related

Spring boot and AWS Secret Manager - how to feature flaged the listener to set the secrets only on prod but not on test env

I need to integrate my spring boot app with the AWS Secret manager to retrieve the DB credentials from it. It has to work only on prod environment.
This is what I did until now
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-secretsmanager</artifactId>
<version>1.12.131</version>
</dependency>
...
SpringApplication application = new SpringApplication(MyApplication.class);
application.addListeners(new DatabasePropertiesListener());
application.run(args);
And my listener implements the
ApplicationListener<ApplicationPreparedEvent>
Is there any way to run my method inside that listener only on specific environment ( kind of feature flag) ? I need to say that this is to early to use variables from properties files.
Update:
When we have to decide before context creation (.i.e. ApplicationListener is no Component/Bean), then we (just mimic profiles) set:
# env: SET MY_FLAG=foo -> System.getenv("MY_FLAG")
# system: java -jar -Dmy.flag=foo myApp.jar -> System.getProperty("my.flag")
# cmd arg:
java -jar myApp.jar aws
..and issue it in our (spring boot) main method like:
if("aws".equalsIgnoreCase(args[0])) // alternatively: System.get...
application.addListeners(new DatabasePropertiesListener());
Misunderstood answer
Sure we can: With spring (boot) core features!
Assuming our ApplicationListener is a "spring managed bean", we annotate it with:
#Component
#Profile("aws") // custom profile (name)
class MyApplicationListener implements ...
{ ... }
...this will not load this "bean" into our context, unless we define:
spring.profiles.acitve=aws,... # a comma separated list of profiles to activate
Profiles
Spring Profiles provide a way to segregate parts of your application configuration and make it be available only in certain environments. Any #Component (Or #Bean,#Service,#Repository...descendants), #Configuration or #ConfigurationProperties can be marked with #Profile to limit when it is loaded, as shown in the ...
... above example.
An advanced application of #Profile annotation:
with multiple profiles: "or"/"and" semantics
"not" (!) operator
#Profile("!local","aws") // (String[]: OR semantics) the annotated component/configuration/property will
// be loaded, when active profiles NOT contains "local" OR contains "aws"
// for AND semantics, we'd use (1 string): "!local & aws"
Activating Profile(s!)
spring.profiles.acitve can be set/added (like any spring property source) through several (14^^, precisely priorized) locations.
E.g. setting environment variable (5th lowest priority, but higher than application.properties (3rd lowest)):
SPRING_PROFILES_ACTIVE=aws
Or as a command line flag (when starting the application, 11th lowest/3rd highest priority):
java -jar myApp.jar --spring.profiles.active=aws,...
#comma separated list
For (spring) tests additionally exists an #ActiveProfiles annotation.
Remarks/Note
Deciding for profiles, we should ensure to "make it consistently" (not raising Nullpointer/BeaninitalizationExceptions ... with dependencies!;). If needed: Creating replacement/local/test (#Profile("!aws")) beans.
Activating a profile "xyz", automatically tries to load application-xyz.properties (with higher priority than application.properties (prio 3.1 - 3.4))...also in (spring-)cloud-config.
Not to forget: The default profile (activated by spring automatically, only when no explicit profile is activated).
Reference
For detailed documentation, please refer to:
Spring Boot - Externalized Configuration
Spring Boot - Profiles
Spring Environment Abstraction chapter
#Profile Javadoc
#ActiveProfiles Javadoc
Profile sample from Configuration Javadoc:
#Configuration classes may be marked with the #Profile annotation to indicate they should be processed only if a given profile or profiles are active:
#Profile("development")
#Configuration
public class EmbeddedDatabaseConfig {
#Bean
public DataSource dataSource() {
// instantiate, configure and return embedded DataSource
}
}
#Profile("production")
#Configuration
public class ProductionDatabaseConfig {
#Bean
public DataSource dataSource() {
// instantiate, configure and return production DataSource
}
}
Alternatively, you may also declare profile conditions at the #Bean method level — for example, for alternative bean variants within the same configuration class:
#Configuration
public class ProfileDatabaseConfig {
#Bean("dataSource")
#Profile("development")
public DataSource embeddedDatabase() { ... }
#Bean("dataSource")
#Profile("production")
public DataSource productionDatabase() { ... }
}
See the #Profile and Environment javadocs for further details.

Creating a custom FactoryBean in Sprint Boot 2.3/Spring 5

I've got a spring-boot web application that's mostly working; my DataSource is properly configured by an external application.properties file.
Now I want to add properties to that file to help me instantiate and configure two instances of a class in my app. I have a APNsFactory that I currently instantiate manually and configure using JNDI, but I want to get away from JNDI calls:
#Bean
public
APNsFactory
apnsFactory()
throws
javax.naming.NamingException
{
sLogger.info("Configuring APNsFactory");
InitialContext ctx = new InitialContext();
APNsFactory f = new APNsFactory();
f.setProductionKeystorePath((String) ctx.lookup("java:comp/env/apns/prod/keystorePath"));
f.setProductionKeystorePassword((String) ctx.lookup("java:comp/env/apns/prod/keystorePassword"));
f.setDevelopmentKeystorePath((String) ctx.lookup("java:comp/env/apns/dev/keystorePath"));
f.setDevelopmentKeystorePassword((String) ctx.lookup("java:comp/env/apns/dev/keystorePassword"));
return f;
}
When running before in a standalone webapp container, Spring properly called that method and the JNDI context from the container’s <env-entry> tags was available.
I'm trying to update my APNsFactory to be a proper Spring FactoryBean<>, and I’ve given it a couple of #Autowire String variables that I want to be set by Spring Boot from the application.properties file.
For bonus points, I want this to be usable both in Spring Boot and in a standalone container like Tomcat or Resin.
For the life of me, I can't figure out how to get Spring to do this. There are dozens of examples for DataSources and other Beans already implemented by Spring, but none for a completely custom one, using application.properties, in a Spring Boot web environment.
I've seen some examples that use an XML config file, but I'm not sure how to do that with Spring Boot.
I don't think you need a factory bean here.
You already have spring boot that can read application.properties out-of-the-box:
So try the following:
Create key/values in the application.properties file:
myapp.keystore.path=...
myapp.keystore.passwd=...
// the same for other properties
Create ConfigurationProperties class
#ConfigurationProperties(prefix="myapp.keystore")
public class MyAppKeyStoreConfigProperties {
private String path; // the names must match to those defined in the properties file
private String passwd;
... getters, setters
}
In the class marked with #Configuration (the one where you create #Bean public APNsFactory apnsFactory()) do the following:
#Configuration
// Note the following annotation:
#EnableConfigurationProperties(MyAppKeyStoreConfigProperties.class)
public class MyConfiguration {
// Note the injected configuration parameter
#Bean public APNsFactory apnsFactory(MyAppKeyStoreConfigProperties config) {
APNsFactory f = new APNsFactory();
f.setProductionKeystorePath(config.getKeyPath());
and so on
}
}
I've intentionally didn't show the separation between production/dev stuff.
In spring boot you have profiles so that the same artifact (WAR, JAR whatever) can be configured to run with different profile and depending on that the corresponding properties will be read.
Example:
If you're running with prod profile, then in addition to application.properties that will be loaded anyway, you can put these keystore related definitions to application-prod.properties (the suffix matches the profile name) - spring boot will load those automatically. The same goes for dev profile of course.
Now I haven't totally understand the "bonus points" task :) This mechanism is spring boot proprietary way of dealing with configuration. In "standalone" server it should still have a WAR with spring boot inside so it will use this mechanism anyway. Maybe you can clarify more, so that I / our colleagues could provide a better answer

Can I get another #Configuration only through #ComponentScan

I am using spring-boot 2.0.4; I have a bunch of services and they have a common configuration class marked with #Configuration.
I want to move this to a common dependency which will have this #Configuration, and based on the need, any micro-service can use #ComponentScan to activate this configuration from dependency.
I have done this for #Component classes, and it's working fine. I activate any particular component I need by adding it into #ComponentScan. How can I activate the configuration in a similar manner(based on need).
Here are the code examples:
Common Configuration:
package abc.department.common.configs.mongo
#Component
public class AbcMongo {
#Bean
public MongoTemplate mongoTemplate() {
// ... create MongoTemplate.
return createdMongoTemplate;
}
}
Here is a class which uses the above dependency:
#Configuration
#ComponentScan("abc.department.common.configs.mongo")
public class MyServiceConfigs {
}
Similarly, I want to do something like this:
package abc.department.common.configs.security.web
#Configuration
#EnableWebSecurity
public class AbcWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// ... do common configs;
}
}
and now, if a service would need web-security config, it could get like:
#Configuration
#ComponentScan({"abc.department.common.configs.mongo","abc.department.common.configs.security.web"})
public class MyServiceConfigs {
}
#Configuration is meant to specify the beans, for example:
#Configuration
public class MyMongoConfiguration {
#Bean
public MongoTemplate mongoTemplate() {
return new ...
}
#Bean
public MySampleBean mySampleBean(MongoTemplate tpl) {
return new MySampleBean(tpl);
}
}
But if so why do you need to work with #Component at all (at least for the beans you create)?
Configuration is a special bean used by Spring framework to load other beans and it can be viewed as a "substitution"/alternative technique to component scanning.
I believe that, if you have some infrastructure configuration that loads a bunch of "infrastructure beans" (shared jar if I get you right), then the services that use this jar should only say "Hey, I want to load this configuration" and not to scan inside the packaging structure of that jar. Why do I think so?
What if you decide to add new beans into a new package in the infra, should external services change their code and define an additional folder to scan? - Probably no.
What if you decide to move the infra to another package?
Now in Spring there are two simple ways to do this that come to mind:
Way 1: Use #Import Annotation
#Configuration // this is from "shared artifact"
class MyInfraConfiguration {
}
#Configuration // this is from an "applicative service" that uses the infra jar in dependencies
#Import(MyInfraConfiguration.class)
class ServiceAConfiguration {
}
Way 2: Use Spring Factories mechanism
The first way has a drawback: You need to know in a Service what infra configuration exactly is. If you see it as a drawback, consider using spring factories.
Spring factories allow registering the infra configuration in some file so that spring boot will load it in service one automatically, you won't even need to mention MyInfraConfiguration in the Service Configuration, just add a dependency to the infra jar and it will work.
In the infra component create:
META-INF/spring.factories
And add there:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycompany.myinfra.whatever.InfraConfiguration
That's it.
Now if you want to customize the loading of beans in the infra configuration, like, a creation of Mongo related templates only if some properties are available, you might want to use #Conditional. Now, although this is kind of out of scope for this question, I mention this because in conjunction with spring factories this can create a very flexible way to manage your configurations

What Is the Correct Way To Use AbstractReactiveWebInitializer

I've got a Spring WebFlux application running successfully as a standalone spring boot application.
I am attempting to run the same application in a Tomcat container, and following the documentation, I've created a class that extends AbstractReactiveWebInitializer. The class requires that I implement a method getConfigClasses that would return classes normally annotated with #Configuration. If the working spring boot app started with a class called ApplicationInitializer, then the resulting implementations would look like this:
#SpringBootApplication(scanBasePackages = "my.pkg")
#EnableDiscoveryClient
#EnableCaching
public class ApplicationInitializer {
public static void main(String... args) {
SpringApplication.run(ApplicationInitializer.class, args);
}
}
and
public class ServletInitializer extends AbstractReactiveWebInitializer {
#Override
protected Class<?>[] getConfigClasses() {
return new Class[] {ApplicationInitializer.class};
}
}
When deployed, the only thing that starts is ApplicationInitializer, none of the autoconfigured Spring Boot classes (Cloud Config, DataSource, etc) ever kick off.
The documenation states this is the class I need to implement, I just expected the remainder of the spring environment to "just work".
How should I be using this class to deploy a Reactive WebFlux Spring Boot application to a Tomcat container ?
Edit:
After some additional research, I've narrowed it down to likely just Cloud Config. During bean post processing on startup, the ConfigurationPropertiesBindingPostProcessor should be enriched with additional property sources (from cloud config), but it appears to be the default Spring properties instead, with no additional sources.
The misisng properties is causing downstream beans to fail.
Spring Boot does not support WAR packaging for Spring WebFlux applications.
The documentation you're referring to is the Spring Framework doc; Spring Framework does support that use case, but without Spring Boot.
you can extend SpringBootServletInitializer, add add reactive servlet on onStartup method

Spring cloud stream kafka binder to create consumer with on demand configuration

I am using Spring boot 1.5.9.RELEASE and Spring cloud Edgware.RELEASE across the Microservices.
I've bound a consumer using #EnableBinding annotation. The annotation will do rest of the part for me to consume events.
Some requirements came up to configure the topic name and some other configuration properties manually for which I want to override some of the properties of a consumer defined in an application.properties at application boot time.
Is there any direct way to do such?
You can use an initialization bean, it can do the work:
#SpringBootApplication
public class SpringDataDemoApplication {
#Bean
InitializingBean populateDatabase() {
return () -> {
// doWhatYouWantHere...
};
}

Resources