Spring Session - SessionDestroyedEvent is not called - spring

I have a Spring application where sessions are stored in redis with a short timeout (1m). I want to call a function after my sessions timeout, however SessionDestroyedEvent #EventListener does not get called.
SessionListener.java:
import org.springframework.context.event.EventListener;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.stereotype.Component;
#Component
public class SessionListener {
#EventListener
public void sessionCreated(SessionCreatedEvent event) {
System.out.println("created"); // gets called
}
#EventListener
public void sessionDestroyed(SessionDestroyedEvent event) {
System.out.println("destroyed"); // never gets called
}
}
application.properties:
spring.session.store-type=redis
server.servlet.session.timeout=1m
notes:
eventListener on SessionCreatedEvent gets called
sessions from redis disappear after the timeout

Section SessionDeletedEvent and SessionExpiredEvent in Spring Session reference describes how sessions are cleaned up.
From the documentation:
Firing SessionDeletedEvent or SessionExpiredEvent is made available through the SessionMessageListener which listens to Redis Keyspace events. In order for this to work, Redis Keyspace events for Generic commands and Expired events needs to be enabled

Related

Spring Boot WebSockets #EventListener doesn't detect SessionConnectEvent but detect SessionDisconnectEvent at the same class

I try to create simple chat via WebSockets. I have configuration class which register appropriate endpoints and I try to create service which will save user id which is generated in handshake service. The problem is that I cannot inject any services to the handshake service so I decided to create class as below:
#Slf4j
#RequiredArgsConstructor
public class WebSocketEventListener {
#EventListener
public void handleWebSocketConnectListener(SessionConnectEvent event) {
log.debug("Handled connection event");
}
#EventListener
public void handleWebSocketConnectedListener(SessionConnectedEvent event) {
log.debug("Handled connected event");
}
#EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
log.debug("Handled disconnection event");
}
}
Register it as a Bean in configuration class:
#Bean
public WebsocketEventListener websocketEventListener() {
return new WebsocketEventListener();
}
And there try to save user id into database.
Unfortunately when I connect to the webSocket via android app, Spring doesn't detect any event connected with Connection session but when I close the connection between android app and spring server I'm getting log from handleWebSocketDisconnectListener method.
I also tried to add annotation #Component to the class WebSocketEventListener instead of registering it as a Bean but I got the same situation.
I tried to implement ApplicationListener and register it in META-INF folder but also without any results.
P.S. I use Spring Boot version: 2.6.4 and Java 17
I don't know what I'm doing wrong. Any suggestions?

Is there a way to have a function run when a session is created or expired?

I am currently planning an application that requires a function to run whenever a session is created and expires. I'm planning on using something like redis but I am open to other ideas. What i am looking for is a n annotation such as #whenexpires and #whencreated. I know that most of the annotations for sessions are at the class, and notthemethod Thanks in regards.
As of Servlet specification 2.3, Java Servlet containers like Apache Tomcat provide the HttpSessionListener interface in order to execute custom logic in the event of created or destroyed sessions. Basic usage:
package com.example;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class MySessionListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent event) {
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
}
}
Add MySessionListener to your web.xml or - in case of Spring - declare a Spring bean for it that is detected by Spring. However, Spring is not required as HttpSessionListener is part of the Java Servlet spec.
If you go for Spring Session with Redis, you can continue using your HttpSessionListener by adding it to the Spring configuration as described in the official docs.
#EnableRedisHttpSession
public class Config {
#Bean
public MySessionListener mySessionListener() {
return new MySessionListener();
}
// more Redis configuration comes here...
}
Moreover, Spring Session comes with support for the "Spring-native" way of event subscription and publishing: ApplicationEvent. Depending on the session persistence approach, there are currently up to three events that can be catched by your application: SessionExpiredEvent, SessionCreatedEvent, SessionDestroyedEvent.
Implement an EventListener in order to subscribe to Spring Session events, for example:
package com.example;
import org.springframework.context.event.EventListener;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import org.springframework.stereotype.Component;
#Component
public class MySessionEventListener {
#EventListener
public void sessionDestroyed(SessionDestroyedEvent event) {
}
#EventListener
public void sessionCreated(SessionCreatedEvent event) {
}
#EventListener
public void sessionExired(SessionExpiredEvent event) {
}
}

Spring Boot: Capture event on context load time and on property change

I wanted to execute a custom logic as soon as the application started and also whenever properties change in Spring Cloud config repo/server. So I have written something like this:
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AppConfiguration implements ApplicationListener<EnvironmentChangeEvent> {
#Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
// Custom logic goes here. It should be executed on both app context load time
// and on any property change time
}
}
This above code was working few months back during application load time and when property change. But this code stopped working recently with, I guess, Spring boot / cloud version updates.
Currently I am using Sprig boot 1.5.10 and Cloud Edgware.SR3
Found a way to run the custom logic to run it on both load time and property change time.
Basically changed to above code to be called upon any event and then inside the overridden methood onApplicationEvent checking only for below event
ContextRefreshedEvent - Event raised when an application context gets initialized or refreshed.
EnvironmentChangeEvent - Event published to signal a change in the environment such as properties in config repo.
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
public class AppConfiguration implements ApplicationListener<ApplicationEvent> {
#Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof EnvironmentChangeEvent || event instanceof ContextRefreshedEvent) {
// Custom logic goes here. It should be executed on both app context load time
// and on any property change time
}
}
}
Updated
We can also use #EventListener annotation to do the similar thing, which is very simple and easy use. Refer below example:
#Configuration
public class AppConfiguration {
#EventListener({EnvironmentChangeEvent.class, ContextRefreshedEvent.class})
public void onRefresh() {
// Your code goes here...
}
}

How to get notified when session is about to expire? Spring-boot

I've tried using
public void onApplicationEvent(ApplicationEvent event)
{
if(event instanceof SessionDestroyedEvent){
and
#WebListener
public class SessionListener implements HttpSessionListener {
#Override
public void sessionDestroyed(HttpSessionEvent se) {
First one, I didn't get SessionDestoryedEvent event at all.
It seems spring might notify us after session is expired.
Is there a reliable way to get notified before session is expired?
Preferably I want solution without spring-session package.
I'm not getting sessionDestroyed nor sessionCreated with the following code..
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import javax.servlet.annotation.WebListener;
import org.springframework.stereotype.Component;
#WebListener
public class MySessionListener implements HttpSessionListener {
private static int totalActiveSessions;
public static int getTotalActiveSession(){
return totalActiveSessions;
}
public MySessionListener()
{
System.out.println("MySessionListener -------------");
}
#Override
public void sessionCreated(HttpSessionEvent arg0) {
totalActiveSessions++;
System.out.println("sessionCreated - add one session into counter");
}
#Override
public void sessionDestroyed(HttpSessionEvent arg0) {
totalActiveSessions--;
System.out.println("sessionDestroyed - deduct one session from counter");
}
}
Spring Session JDBC does not support publishing of session events due to the obvious limitations of an underlying data store in that regard. A relational database, by itself, has no pub-sub like mechanism that could be used to propagate events to all nodes in the cluster.
This is documented both in the reference manual and the JdbcOperationsSessionRepository javadoc.
Regarding the second part of your question, with session stores that support event publishing (such as Redis and Hazelcast) Spring Session translates all the events it publishes to standard Servlet API's HttpSessionEvent instances. While you could listen to Spring Session's event hierarchy is recommended to keep all session related interactions through standard Servlet API mechanisms.
Session events related to expiration/deletion are published when session is to be invalidated, as per HttpSession and HttpSessionListener#sessionDestroyed. I'm not sure what exactly do you mean by getting notified before session is expired, as it a vague term that depends on your expectations of how much before.

JMS: how can I enqueue a task that have failed?

I'm using spring boot and I have a task which consists on invoking an external API to create a resource. In other words, it's just an API call which take a simple parameter.
Since that call is asynchronous, i need to ENSURE that the resource is created. So if the first call to the api fails, it has to be enqueued in order to retry after X seconds. Once the api call completes successfuly, i have to remove that api call from the queue.
How can i achieve this behaviour? I was looking for using ActiveMQ. Is there any other proposal which could work better with spring boot?
You can use "browse" and "get".
The steps as follows:
Browse data and run your api to create a resource. (data is not
removed just browse it)
Check resource has been created and get a
data from queue using selectors.
you can use schedulerSupport of ActiveMQ, It is enabled by setting the broker schedulerSupport attribute to true :
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true">
http://activemq.apache.org/delay-and-schedule-message-delivery.html
package com.example.amq;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ScheduledMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;
#Component
public class ProducerScheduledMessage implements CommandLineRunner {
#Autowired
private JmsTemplate jmsTemplate;
#Override
public void run(String... args) throws Exception {
send("LOCAL_Q", "send informations of first api call to do", 0);
boolean stop = false;
do {
try {
final String msg = (String) this.jmsTemplate.receiveAndConvert("LOCAL_Q");
// execute call or verify resources creation
stop = true;
} catch (Exception e) {
// if api call fails, send again to the same destination
// to be
// treated after 5 seconds
send("LOCAL_Q", "resend call api or resources creation verification to do after 5s", 5000);
}
} while (!stop);
}
public void send(String dest, final String msg, final long delay) {
this.jmsTemplate.send(dest, new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
TextMessage tm = session.createTextMessage(msg);
tm.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delay);
return tm;
}
});
}
}

Resources