My class is an sftp poller. It implements DirectoryListener and implements the fileAdded method to monitor when new events are added to an sftp directory. Code looks like
#SpringBootApplication
#Slf4j
public class SftpBridge implements DirectoryListener, IoErrorListener, InitialContentListener {
#Autowired
private SftpBridgeConfig config;
#Autowired
public SftpDirectory sftpDirectory;
public static void main(final String[] args) throws Exception {
SpringApplication.run(SftpBridge.class, args);
}
#PostConstruct
public void postConstruct() {
LOG.info("Initializing...");
initialize();
LOG.info("Initialized!");
}
private void initialize() {
pollSftp();
}
public void pollSftp() {
try {
while (true) {
LOG.info("monitoring directory: " + "/");
PolledDirectory polledDirectory = sftpDirectory;
DirectoryPoller dp = DirectoryPoller.newBuilder()
.addPolledDirectory(polledDirectory)
.addListener(new SftpBridge())
// other settings
//remove this later
.enableFileAddedEventsForInitialContent() // optional (disabled by default). FileAddedEvents fired for directories initial content.
//TODO: enable later for subdirectory polling
//.enableParallelPollingOfDirectories() // optional (disabled by default).
.setDefaultFileFilter(new RegexFileFilter(".*csv")) // optional. Only consider files ending with "xml".
.setThreadName("sftp-poller") // sets the name of the the polling thread
.setPollingInterval(10, TimeUnit.SECONDS)
.start();
TimeUnit.HOURS.sleep(2);
dp.stop();
}
} catch (final Exception e) {
LOG.error("Error monitoring ftp host", e);
}
}
Since pollSftp() is called by initialize() during Spring boot application init, it is able to see the #Autowired component SftpBridgeConfig config.
My problem is that my class implements DirectoryListener, I have to override the fileAdded event to take some action when a new ftp file is added.
#Override
public void fileAdded(FileAddedEvent event) {
LOG.info("Added: " + event.getFileElement());
//implementing DirectoryListener
//#Autowired component config is null here as it is called from a polling thread
}
in the fileAdded(FileAddedEvent event) method, my #Autowired component config is null, because this method is not called during Spring boot init. What is the best way to structure the code so that the #Autowired component config is available when fileAdded() is called by an sftp directory polling thread?
Thanks for any advice.
Edit: #Andreas - I've filled out my pollSftp() method which adds the class as a DirectoryListener. Thanks
Is there an annotaion in SftpBridgeConfig?
cf. #Service #Component
if configuration class, need to register #Bean in the spring context.
cf. #Bean(name="") or #Bean
ex) message source configuration
#Configuration
public class MessageSourceConfiguration {
#Bean
public MessageSource messageSource() {
// statements
}
}
Related
I have my application set up to use Spring cloud config for providing configuration and have the monitor enabled so that the config server publishes change events to my application. The configuration gets updated correctly, but I want to be notified of when the configuration changes so I can execute some custom logic based on the new config.
I have this configuration object
#Configuration
#RefreshScope
#ConfigurationProperties(prefix = "my.prefix")
public class MyConfig {
private Map<String, MyObject> configs;
private String someValue;
public Map<String, MyObject> getConfigs(){...}
public void setConfigs(){...}
public String getSomeValue(){...}
public void setSomeValue(){...}
}
...
public class MyObject {
private String field1;
public String getField1() {...}
public void setField1() {...}
}
And this in my config servers application.yml
my:
prefix:
configs:
TEST:
field1: "testValue"
someValue: "test"
Now when I change someValue in the configuration, and the config server publishes a refresh, it calls setSomeValue() and updates the value to the new value. I can add my custom logic to setSomeValue() and it will work fine. However it does not seem to call setConfigs() or setField1() when updating or adding/removing entries from configs.
I tried registering a listener for EnviornmentChangeEvents, RefreshEvents, or RefreshScopeRefreshedEvents but those are either triggered before Spring updates the values or aren't triggered at all. I also tried adding logic to #PreDestroy and #PostConstruct methods but only the PreDestroy ends up being called and it's called before the configuration is updated. I also tried implementing InitializingBean and putting my logic in afterPropertiesSet() but it never get's called either.
How can I get notified when this configuration get's updated?
With a RefreshScopeRefreshedEvent Listener you can get notified when the configuration is updated.
The following example works for me:
The configuration:
#Configuration
public class Config {
#Bean
#RefreshScope
public A aBean() {
return new A();
}
#Bean
public RefreshScopeRefreshedListener remoteApplicationEventListener(A aBean) {
return new RefreshScopeRefreshedListener(aBean);
}
}
And the listener:
public class RefreshScopeRefreshedListener implements ApplicationListener<RefreshScopeRefreshedEvent> {
private A aBean;
public RefreshScopeRefreshedListener(A abean) {
this.aBean = abean;
}
#Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
System.out.println(aBean.getValue());
}
}
It always print the new value of the configuration.
If you already tried this listener, are you sure that is has been registered well? The bean has been created correctly?
What I've done is to get hook of EnvironmentChangeEvent listener, and then get the updated properties from the Environment itself, not from the #Autowired Bean bean
#Autowired
private Environment env;
#EventListener(EnvironmentChangeEvent.class)
public void onApplicationEvent(EnvironmentChangeEvent environmentChangeEvent) {
log.info("Received an environment changed event for keys {}", environmentChangeEvent.getKeys());
if(environmentChangeEvent.getKeys().contains("key.i.wanted.to.recalculate")) {
String newValue = env.getProperty("key.i.wanted.to.recalculate");
System.out.println("New Value: " + newValue);
}
}
Alternatively you can add #PostConstruct with #ConfigurationProperties so that you can write your custom logic
#ConfigurationProperties(prefix = "dummy.just.to.trigger.actuator.refresh")
Class AnyPropLoader {
#PostConstruct
private void customLogicMethod(){
...
}
}
Create a Bean (dto or an object)
update it, in any method
Autowire that dto/object in another class
That class is updating the values of that DTO/Object
This is how I solved this updating of data and using #Autowired.
I'm having an issue where trying to gracefully shutdown Tomcat (8) never finishes, due to what appears to be DefaultMessageListenerContainer being blocked (or looping) indefinitely.
I've been googling around for solutions, but anything similar I've found hasn't worked. This includes (but is not limited to):
Using configureListenerContainer() to set the taskExecutor of the container
Using Messages.queue() instead of Messages.direct()
Wrapping the ActiveMQConnectionFactory in a CachingConnectionFactory
A simple Servlet 3.0 example:
compile 'org.springframework.integration:spring-integration-core:4.3.6.RELEASE'
compile 'org.springframework.integration:spring-integration-jms:4.3.6.RELEASE'
compile 'org.springframework.integration:spring-integration-java-dsl:1.2.1.RELEASE'
Initializer:
public class ExampleWebApp implements WebApplicationInitializer {
#Override
public void onStartup(final ServletContext servletContext) throws ServletException {
final AnnotationConfigWebApplicationContext springContext = new AnnotationConfigWebApplicationContext();
springContext.register(ExampleConfig.class);
servletContext.addListener(new ContextLoaderListener(springContext));
final ServletRegistration.Dynamic registration = servletContext.addServlet("example", new HttpRequestHandlerServlet());
registration.setLoadOnStartup(1);
registration.addMapping("/status");
}
}
Configuration:
#Configuration
#EnableIntegration
public class ExampleConfig {
#Bean
public ConnectionFactory connectionFactory() {
final ActiveMQConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory();
mqConnectionFactory.setBrokerURL("tcp://host:port");
mqConnectionFactory.setUserName("----");
mqConnectionFactory.setPassword("----");
return mqConnectionFactory;
}
#Bean
public Queue testQueue() {
return new ActiveMQQueue("test.queue");
}
#Bean
public MessageChannel testReceiveChannel() {
return MessageChannels.direct().get();
}
#Bean
public IntegrationFlow pushMessageInboundFlow() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(connectionFactory())
.destination(testQueue()))
.log()
.transform(new JsonToObjectTransformer(TestMessageObject.class))
.channel(testReceiveChannel())
.get();
}
/** Example message object */
public static class TestMessageObject {
private String text;
public String getText() {
return text;
}
public void setText(final String text) {
this.text = text;
}
}
}
If I try and stop this via the catalina.sh script (for example, pressing "stop" in Intellij", it never finishes existing. So far the only way I've been able to get shutdown to finish is by "manually" destroying the JmsMessageAdapters on shutdown, via a little helper class:
public class JmsMessageListenerContainerLifecycleManager {
private static final Logger LOG = LoggerFactory.getLogger(JmsMessageListenerContainerLifecycleManager.class);
#Autowired
private List<IntegrationFlow> mIntegrationFlows;
#PreDestroy
public void shutdownJmsAdapters() throws Exception {
LOG.info("Checking {} integration flows for JMS message adapters", mIntegrationFlows.size());
for (IntegrationFlow flow : mIntegrationFlows) {
if (flow instanceof StandardIntegrationFlow) {
final StandardIntegrationFlow standardFlow = (StandardIntegrationFlow) flow;
for (Object component : standardFlow.getIntegrationComponents()) {
if (component instanceof JmsMessageDrivenChannelAdapter) {
final JmsMessageDrivenChannelAdapter adapter = (JmsMessageDrivenChannelAdapter) component;
LOG.info("Destroying JMS adapter {}", adapter.getComponentName());
adapter.destroy();
}
}
}
}
}
}
And while that works, it definitely feels like the wrong solution.
Previously I was using XML configuration of spring-integration, and I did not have this problem. What am I missing?
Ugh! This is definitely a bug. And looks like you workaround it properly.
Although consider to destroy any DisposableBean there.
I'm adding the fix to the Spring Integration Java DSL. We are going to release the next 1.2.2 just after Spring Integration 4.3.9.
The Spring Integration 5.0 will have a fix in its M3 release tomorrow.
In my Spring Boot Application i'm trying to do some tasks in the background.
Getting data from one db, and store it in another, every 30min ish.
Would it be correct to make a CommandLineRunner class that takes care of this with #Async ?
#Component
public class UpdateDB implements CommandLineRunner {
#Autowired
private WagerBoardMarksRepo loadRepo;
#Autowired
private StoreDbEntRepo storeRepo;
#Async
private static void update() {
while (true) {
// get data from loadRepo.
// save data to storeRepo
try {
Thread.sleep("sleep for 30min"); //
} catch (Exception e) {
e.printStackTrace();
}
}
}
#Override
public void run(String... args) throws Exception {
update();
}
}
Scheduler is made for such operation, see the code below
#Component
public class ScheduledTasks {
#Scheduled(cron = "0 0,30 * * * * ?")
public void update() {
// get data from loadRepo.
// save data to storeRepo
}
}
And dont forget to use #EnableScheduling in your Startup class
#SpringBootApplication
#EnableScheduling
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class);
}
}
See the Scheduling Tasks docs of spring for more details.
This is very similar to the other question here: Spring Boot #Async method in controller is executing synchronously. However my #Service method annotated with #Async is still executing synchronously. I've tried all methods from different forums to no use. Hopefully someone could help me figure out why. A simple spring boot project as below doesn't work.
AsyncConfiguration.java
#Configuration
#EnableAsync
public class AsyncConfiguration(){}
SomeService.java
#Service
public class SomeService() {
#Async
public void doSomething() {
try {
Thread.sleep(5000L);
} catch (Exception ignore){}
}
}
SomeController.java
#Controller
public class SomeController() {
#Inject SomeService someService;
#RequestMapping(value="/", method=RequestMethod.POST)
public String doStuff() {
someService.doSomething();
return "mytemplate";
}
}
Here is a simple example with #Async. Follow these steps to get #Async to work in your Spring Boot application:
Step 1: Add #EnableAsync annotation and Add TaskExecutor Bean to Application Class.
Example:
#SpringBootApplication
#EnableAsync
public class AsynchronousSpringBootApplication {
private static final Logger logger = LoggerFactory.getLogger(AsynchronousSpringBootApplication.class);
#Bean(name="processExecutor")
public TaskExecutor workExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setThreadNamePrefix("Async-");
threadPoolTaskExecutor.setCorePoolSize(3);
threadPoolTaskExecutor.setMaxPoolSize(3);
threadPoolTaskExecutor.setQueueCapacity(600);
threadPoolTaskExecutor.afterPropertiesSet();
logger.info("ThreadPoolTaskExecutor set");
return threadPoolTaskExecutor;
}
public static void main(String[] args) throws Exception {
SpringApplication.run(AsynchronousSpringBootApplication.class,args);
}
}
Step 2: Add Method which executes an Asynchronous Process
#Service
public class ProcessServiceImpl implements ProcessService {
private static final Logger logger = LoggerFactory.getLogger(ProcessServiceImpl.class);
#Async("processExecutor")
#Override
public void process() {
logger.info("Received request to process in ProcessServiceImpl.process()");
try {
Thread.sleep(15 * 1000);
logger.info("Processing complete");
}
catch (InterruptedException ie) {
logger.error("Error in ProcessServiceImpl.process(): {}", ie.getMessage());
}
}
}
Step 3: Add an API in the Controller to execute the asynchronous processing
#Autowired
private ProcessService processService;
#RequestMapping(value = "ping/async", method = RequestMethod.GET)
public ResponseEntity<Map<String, String>> async() {
processService.process();
Map<String, String> response = new HashMap<>();
response.put("message", "Request is under process");
return new ResponseEntity<>(response, HttpStatus.OK);
}
I have also written a blog and a working application on GitHub with these steps. Please check:
http://softwaredevelopercentral.blogspot.com/2017/07/asynchronous-processing-async-in-spring.html
Sorry for my English.
The same problem happened to me.
The solution was to add
#Autowired
private SomeService someService;
In the Controller class, in this way it allows the class to contain the Beam Configurations associated with "SomeService", thus being able to execute the asynchronous method perfectly.
Here is a project with a functional asynchronous method:
https://github.com/JColmenares/async-method-api-rest.git
The FailedMessageAspect.afterMethod() below gets called successfully during RabbitConsumerMain.main() below. However, it doesn't get called when it's used in the context of listening for a RabbitMQ message - when MessageHandlerImpl.handleMesasge() receives a message from a RabbitMQ queue. Any idea why?
FailedMessageAspect.java
#Aspect
#Component
public class FailedMessageAspect {
#AfterReturning("execution(* com..MessageHandlerImpl.testAspect(..))")
private void afterMethod() {
System.out.println("aspect foo");
}
}
MessageHandlerImpl.java
#Component
public class MessageHandlerImpl implements MessageHandler {
#Override
public void testAspect() {
System.out.println("handler foo");
}
#Override
public void handleMessage(String message) {
// handleMessage is called successfully when message is received
testAspect();
// FailedMessageAspect.afterMethod() does not get called
}
}
RabbitConsumerMain.java
#Controller
#SpringBootApplication
public class RabbitConsumerMain implements CommandLineRunner {
#Autowired
private MessageHandler messageHandler;
public static void main(String[] args) throws Exception {
SpringApplication.run(RabbitConsumerMain.class, args);
}
#Override
public void run(String... args) {
messageHandler.testAspect();
//FailedMessageSpect.afterMethod() gets called right here
}
}
ConsumerConfiguration.java
#Configuration
public class ConsumerConfiguration {
#Autowired #Lazy
private MessageHandler messageHandler;
//other standard AMQP configs
#Bean
public MessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setQueues(workQueue());
MessageListenerAdapter adapter = new MessageListenerAdapter(messageHandler, new Jackson2JsonMessageConverter());
container.setMessageListener(adapter);
return container;
}
}
You don't show all your configuration but, just to be clear, Spring AOP does not advise internal method calls such as handleMessage calling testAspect() within the same class instance.
You need to use AspectJ for that; otherwise, all methods you advise must be public methods invoked via bean definitions, so Spring can invoke the method via a proxy. Internal calls within a bean are never advised.
See the reference manual for a complete explanation.