Spring Autowire configuration in flink - spring

i am trying to use the comination of flink and springboot and im having some problems.
Lets say i am having this flow.
Getting json string that have one field date that contains date string.
using map function and ObjectMapper to parse it into object of LocalDateTime
print
This is simple usecase that will describe my probem.
So, i have Word Class represnting Word that contains LocalDateTime field.
#Data
public class Word {
#JsonDeserialize(using = LocalDateTimeSerde.class)
LocalDateTime date;
}
The LocalDateTimeDeserlization is looking like that(I want to autowire the app configuration):
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
#JsonComponent
public class LocalDateTimeSerde extends JsonDeserializer<LocalDateTime> {
private final AppConf conf;
#Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(this.conf.getDateFormatter());
return LocalDateTime.parse(jsonParser.getText(), formatter);
}
}
AppConf.java represneting the configuration of the application is:
#Data
#Configuration
#ConfigurationProperties(value = "app")
public class AppConf {
private String dateFormatter;
}
DemoApplication.java:
final StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(1);
String example = "{\"date\":\"2019-01-29 00:00\"}";
var stream = env
.fromElements(example)
.map(x->new ObjectMapper().readValue(x,Word.class))
.returns(Word.class);
stream.print();
env.execute("Demo App");
The exception im getting is :
Caused by: java.lang.IllegalArgumentException: Class com.example.demo.LocalDateTimeSerde has no default (no arg) constructor
The main problem here is that the code of the deserialization is running on the TaskManager and over there springboot doesnt take a part, so it doesn`t inject AppConf into the class.
Adding #NoArgsConstructor will not solve the problem
I think i know why it is hapenning (because flink master serialize the classes to the workers and then springboot doesn`t "ScanComponents" and takes control.
Is there any solution for that? I really want to combine spring with flink also in the worker`s function.
Thanks.

In general, I personally don't think it's a good idea to mix those concepts. The easiest solution is to use AutoWired only on the job manager and use explicit dependency injection when you go into Flink-land.
For example, you could extract the date pattern in the DemoApplication and set it on the ObjectMapper. (Don't forget to initialize ObjectMapper only once in your real code!)
If you really want to use AutoWiring. I guess you need to manually trigger the autowiring on taskmanager. There is a related post specifically for ObjectMapper.

Related

Spring #Value not working in Spring Boot 2.5.5, getting null values

I am trying to inject some property values into variables by means of Spring #Value annotation but I get null values. I tried different configurations and triks but it doesn't work. Think is that before today everythink was working properly. I do not know what I changed in order to get things broken.
Here is my java class:
#Component
#ConditionalOnProperty(prefix = "studioghibli", name = "get")
public class StudioGhibliRestService {
#Value("${studioghibli.basepath}")
private static String BASE_PATH;
#Value("${studioghibli.path}")
private static String PATH;
#Value("${studioghibli.protocol:http}")
private static String PROTOCOL;
#Value("${studioghibli.host}")
private static String HOST;
private static String BASE_URI = PROTOCOL.concat("://").concat(HOST).concat(BASE_PATH).concat(PATH);
#Autowired
StudioGhibliRestConnector connector;
public List<StudioGhibliFilmDTO> findAllFilms() throws SipadContenziosoInternalException {
var response = connector.doGet(BASE_URI, null, null);
if (!response.getStatusCode().is2xxSuccessful() || !response.hasBody()) {
throw new SipadContenziosoInternalException(Errore.INTERNAL_REST_ERROR, "FindAll(), microservizio ".concat(BASE_URI), null);
}
return (List<StudioGhibliFilmDTO>) response.getBody();
}
}
As you can see, the class is annotated with #Component, that because I will need to use it as #Service layer in order to make a rest call in my business logic.
The class is also annotaded with conditional on property...
Here is a screenshot of the debug window at startup:
Since the PROTOCOL value is null, i get a null pointer exception immediately at start up.
Here is part of the application-dev.properties file:
studioghibli.get
studioghibli.protocol=https
studioghibli.host=ghibliapi.herokuapp.com
studioghibli.basepath=/
studioghibli.path=/films
First of all, #Value annotation does not work with static fields.
Secondly, fields with #Value annotation is processed when the instance of the class (a bean) is created by Spring, but static fields exist for a class (for any instance), so when the compiler is trying to define your static BASE_URI field other fields are not defined yet, so you get the NPE on startup.
So you might need a refactoring, try to inject values with the constructor like this:
#Component
#ConditionalOnProperty(prefix = "studioghibli", name = "get")
public class StudioGhibliRestService {
private final StudioGhibliRestConnector connector;
private final String baseUri;
public StudioGhibliRestService(StudioGhibliRestConnector connector,
#Value("${studioghibli.basepath}") String basePath,
#Value("${studioghibli.path}") String path,
#Value("${studioghibli.protocol:http}") String protocol,
#Value("${studioghibli.host}") String host) {
this.connector = connector;
this.baseUri = protocol.concat("://").concat(host).concat(basePath).concat(path);
}
// other code
}
Thanks, It works for me, I have to add some codes to my project. Then I check the spring core document in "#Value" section. Besides
When configuring a PropertySourcesPlaceholderConfigurer using
JavaConfig, the #Bean method must be static.
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}

What is best way to read properties from application.properties?

So, I have created configuration class with #Component and #ConfigurationProperties(prefix = "properties"), set default values for some of my application properties and changed some of them in application.yaml/properties
Now, I know I can access it using #Value("properties.*") but it can lead to having many variables which will be repetitive in another classes too
#Value("${properties.user-id-length}")
private int userIdLength;
I also can access my configuration class (as it is Spring Bean) through #Autowire it to variable in every single class I need make use of it. The cons for that is that more complex configuration class containing inner classes, which contain inner classes etc. will not look too great in code
#Autowired // Not recommended, but for simplicity
private MyConfigurationClass myConfigurationClass;
// some method
int userIdLength = myConfigurationClass.getUserIdLength();
String serverLocation = myConfigurationClass.getAmazon().getSes().getSenderEmailAddress()
Another way is to create additional helper class like Constant and set needed static fields with #Value but it can be time consuming and I'm not sure it is THAT different from first solution
public static int USER_ID_LENGTH;
#Value("${properties.user-id-length}")
private void setUserIdLength(int length){
Constant.USER_ID_LENGTH = length;
}
So, which aproach is the best? Or are there another ways to do that?
Well, not much of the feedback but in the meantime I figured out that using both #Value and #ConfigurationProperties leads to some problems.
MyProp.class
#Getter
#Setter
#ConfigurationProperties(prefix = "prop")
public class MyProp{
private String default = "Default String"
}
SomeClass.class
#Component
public class SomeClass.class{
#Value("${prop.default}")
public String message;
}
Above example causes Exception BeanCreationException
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'someClass': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'prop.default' in value "${prop.default}"
Explanation to this can be found here in #davidxxx's post and occurs unless we set all fields in application.properties. Because of that my first proposal cannot be done in the terms I thought it can be done and explanation was not easy to find, so I hope it will help someone one day.

How to spring inject configuration value for Joda Period

How can I use the #Value annotation to configure a Joda-Time Period field in my spring bean?
E.g. Given the following component class:
#Component
public class MyService {
#Value("${myapp.period:P1D}")
private Period periodField;
...
}
I want to use standard ISO8601 format to define the period in a properties file.
I get this error:
Caused by: java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.joda.time.Period]: no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:302)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:125)
at org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:61)
... 35 more
A simple solution that does not require any java code is to use Spring Expression Language (SpEL).
(My example uses java.time.Duration and not Joda stuff but I think you get it anyway.)
#Value("#{T(java.time.Duration).parse('${notifications.maxJobAge}')}")
private Duration maxJobAge;
What you can do is register a Spring ConversionService bean and implement a proper converter.
#Bean
public ConversionServiceFactoryBean conversionService() {
ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
Set<Converter<?, ?>> myConverters = new HashSet<>();
myConverters.add(new StringToPeriodConverter());
conversionServiceFactoryBean.setConverters(myConverters);
return conversionServiceFactoryBean;
}
public class StringToPeriodConverter implements Converter<String, Period> {
#Override
public Period convert(String source) {
return Period.parse(source);
}
}
Another, not elegant, option, is to use a String setter who invokes the parse method.
#Value("${myapp.period:P1D}")
public void setPeriodField(String periodField)
{
if (isBlank(periodField))
this.periodField= null;
this.periodField= Duration.parse(periodField);
}
For joda-time:2.10.13 and spring-boot:2.3.2.RELEASE next example (like in question) is worked:
#Value("${myapp.period:P1D}")
private Period periodField;
If you use java.time.Period, in addition, worked a simple period property format (org.springframework.boot.convert.PeriodStyle):
#Value("${myapp.period:1d}")
private Period periodField;

Several beans implementating the same interface

The exact usage is like this:
#Slf4j
public class Client<E, Key> {
#Getter #NonNull private final UpdateListener<E, Key> updateListener;
#NonNull private final SubscriptionFactory subscriptionFactory;
#NonNull private final Map<Key, Instant> updatedRegistry = new ConcurrentHashMap<>();
public Client(UpdateListener<E, Key> updateListener,
SubscriptionFactory subscriptionFactory) {
this.updateListener = updateListener;
this.subscriptionFactory = subscriptionFactory;
this.subscriptionFactory.registerSnapshotClient(updateListener);
log.info("Created new snapshot client for entity key [{}], update type [{}] and component qualifier [{}]",
updateListener.getEntityKey(),
updateListener.getOptionalChangeType(),
updateListener.getComponentQualifier());
}
#RabbitListener(queues = {"#{#queueNameCreator.createUpdateQueueName(snapshotClient.getUpdateListener())}",
"#{#queueNameCreator.createSnapshotQueueName(snapshotClient.getUpdateListener())}"})
public void handleMessage(Message<E> rawUpdate, #Header("last_updated") Instant newUpdatedTime) {
...//more code
}
}
Each 'Client' instance has its own bean id to not clash with each other.
How can I call get the exact updateListener of this object using SpEl?
Update
After using programattical approach and registering method I get the following exception:
Apr 28, 2015 3:22:47 PM org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler handleError
WARNING: Execution of Rabbit message listener failed.
org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener method 'public void com.everymatrix.om2020.messaging.model.SnapshotClient.handleMessage(org.springframework.messaging.Message<E>,java.time.Instant)' threw exception
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:126)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:93)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:756)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:679)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$001(SimpleMessageListenerContainer.java:82)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$1.invokeListener(SimpleMessageListenerContainer.java:167)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.java:1241)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:660)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:1005)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:989)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$700(SimpleMessageListenerContainer.java:82)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1103)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: No suitable resolver for argument [0] [type=org.springframework.messaging.Message]
Update
Done, you need to do the following to achieve the desired behaviour.
#Configuration
#EnableRabbit
public static class OmbeRabbitListenerConfigurer implements RabbitListenerConfigurer {
#Autowired ApplicationContext applicationContext;
#Autowired SnapshotClientQueueNamesCreator snapshotClientQueueNamesCreator;
#Autowired RabbitListenerContainerFactory rabbitListenerContainerFactory;
#Autowired MessageConverter messageConverter;
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
final Collection<SnapshotClient> snapshotClients = applicationContext.getBeansOfType(SnapshotClient.class).values();
System.out.println(snapshotClients);
snapshotClients.stream().forEach(bean -> {
final String snapshotQueueName = snapshotClientQueueNamesCreator.createSnapshotQueueName(bean.getUpdateListener());
final String updateQueueName = snapshotClientQueueNamesCreator.createUpdateQueueName(bean.getUpdateListener());
Method method = Stream.of(bean.getClass().getMethods()).filter(x -> x.getName().equals("handleMessage")).findAny().get();
MethodRabbitListenerEndpoint endpoint = new MethodRabbitListenerEndpoint();
final DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
messageHandlerMethodFactory.afterPropertiesSet();
endpoint.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
endpoint.setBean(bean);
endpoint.setMethod(method);
endpoint.setId(snapshotQueueName + ":" + updateQueueName + UUID.randomUUID());
endpoint.setQueueNames(snapshotQueueName, updateQueueName);
endpoint.setExclusive(false);
registrar.registerEndpoint(endpoint, rabbitListenerContainerFactory);
});
}
}
Your question is not clear - you seem to be mixing runtime and initialization time concepts.
For example, "#{#queueNameCreator.createUpdateQueueName(e.c.doSomething())}" is evaluated once during initialization - it's not clear from this expression what e is, or where it comes from.
But, you seem to be passing in an E in the payload of message: Message<E> rawUpdate. This message came from the queue and therefore can't influence the queue name.
Perhaps if you can explain what you are trying to do rather than how you have attempted to do it, I can update this "answer" with possible solutions.
EDIT:
If you mean you want to reference some field in the current (listener) bean in your SpEL then it can't be done directly.
EDIT2:
I can't think of any way to get a reference to the current bean in the SpEL expression - it has to be a constant; that's just the way annotations work in Java; they are tied to the class, not the instance.
I think to do what you want, you would need to revert to using programmatic endpoint registration. However, you'd need to wire in a MethodRabbitListenerEndpoint (rather than the SimpleRabbitListenerEndpoint) to get the benefits of the annotation you are looking for (#Header etc).
We don't really cover it in the documentation; it's a little advanced, but essentially, you need to inject the bean and Method (for the listener), and a DefaultMessageHandlerMethodFactory.

Spring Boot equivalent to XML multi-database configuration

I would like to port two projects to Spring Boot 1.1.6. The are each part of a larger project. They both need to make SQL connections to 1 of 7 production databases per web request based region. One of them persists configuration setting to a Mongo database. They are both functional at the moment but the SQL configuration is XML based and the Mongo is application.properties based. I'd like to move to either xml or annotation before release to simplify maintenance.
This is my first try at this forum, I may need some guidance in that arena as well. I put the multi-database tag on there. Most of those deal with two connections open at a time. Only one here and only the URL changes. Schema and the rest are the same.
In XML Fashion ...
#Controller
public class CommonController {
private CommonService CommonService_i;
#RequestMapping(value = "/rest/Practice/{enterprise_id}", method = RequestMethod.GET)
public #ResponseBody List<Map<String, Object>> getPracticeList(#PathVariable("enterprise_id") String enterprise_id){
CommonService_i = new CommonService(enterprise_id);
return CommonService_i.getPracticeList();
}
#Service
public class CommonService {
private ApplicationContext ctx = null;
private JdbcTemplate template = null;
private DataSource datasource = null;
private SimpleJdbcCall jdbcCall = null;
public CommonService(String enterprise_id) {
ctx = new ClassPathXmlApplicationContext("database-beans.xml");
datasource = ctx.getBean(enterprise_id, DataSource.class);
template = new JdbcTemplate(datasource);
}
Each time a request is made, a new instance of the required service is created with the appropriate database connection.
In the spring boot world, I've come across one article that extended TomcatDataSourceConfiguration.
http://xantorohara.blogspot.com/2013/11/spring-boot-jdbc-with-multiple.html That at least allowed me to create a java configuration class however, I cannot come up with a way to change the prefix for the ConfigurationProperties per request like I am doing with the XML above. I can set up multiple configuration classes but the #Qualifier("00002") in the DAO has to be a static value. //The value for annotation attribute Qualifier.value must be a constant expression
#Configuration
#ConfigurationProperties(prefix = "Region1")
public class DbConfigR1 extends TomcatDataSourceConfiguration {
#Bean(name = "dsRegion1")
public DataSource dataSource() {
return super.dataSource();
}
#Bean(name = "00001")
public JdbcTemplate jdbcTemplate(DataSource dsRegion1) {
return new JdbcTemplate(dsRegion1);
}
}
On the Mongo side, I am able to define variables in the configurationProperties class and, if there is a matching entry in the appropriate application.properties file, it overwrites it with the value in the file. If not, it uses the value in the code. That does not work for the JDBC side. If you define a variable in your config classes, that value is what is used. (yeah.. I know it says mondoUrl)
#ConfigurationProperties(prefix = "spring.mongo")
public class MongoConnectionProperties {
private String mondoURL = "localhost";
public String getMondoURL() {
return mondoURL;
}
public void setMondoURL(String mondoURL) {
this.mondoURL = mondoURL;
}
There was a question anwsered today that got me a little closer. Spring Boot application.properties value not populating The answer showed me how to at least get #Value to function. With that, I can set up a dbConfigProperties class that grabs the #Value. The only issue is that the value grabbed by #Value is only available in when the program first starts. I'm not certain how to use that other than seeing it in the console log when the program starts. What I do know now is that, at some point, in the #Autowired of the dbConfigProperties class, it does return the appropriate value. By the time I want to use it though, it is returning ${spring.datasource.url} instead of the value.
Ok... someone please tell me that #Value is not my only choice. I put the following code in my controller. I'm able to reliably retrieve one value, Yay. I suppose I could hard code each possible property name from my properties file in an argument for this function and populate a class. I'm clearly doing something wrong.
private String url;
//private String propname = "${spring.datasource.url}"; //can't use this
#Value("${spring.datasource.url}")
public void setUrl( String val) {
this.url = val;
System.out.println("==== value ==== " + url);
}
This was awesome... finally some progress. I believe I am giving up on changing ConfigurationProperties and using #Value for that matter. With this guy's answer, I can access the beans created at startup. Y'all were probably wondering why I didn't in the first place... still learning. I'm bumping him up. That saved my bacon. https://stackoverflow.com/a/24595685/4028704
The plan now is to create a JdbcTemplate producing bean for each of the regions like this:
#Configuration
#ConfigurationProperties(prefix = "Region1")
public class DbConfigR1 extends TomcatDataSourceConfiguration {
#Bean(name = "dsRegion1")
public DataSource dataSource() {
return super.dataSource();
}
#Bean(name = "00001")
public JdbcTemplate jdbcTemplate(DataSource dsRegion1) {
return new JdbcTemplate(dsRegion1);
}
}
When I call my service, I'll use something like this:
public AccessBeans(ServletRequest request, String enterprise_id) {
ctx = RequestContextUtils.getWebApplicationContext(request);
template = ctx.getBean(enterprise_id, JdbcTemplate.class);
}
Still open to better ways or insight into foreseeable issues, etc but this way seems to be about equivalent to my current XML based ways. Thoughts?

Resources