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

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.

Related

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 Session - SessionDestroyedEvent is not called

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

Catch application stop event for Spring-boot application

Is there a clean way to detect when a spring-boot application is stopped and perform some action before? Kind of CommandLineRunner for stopping a service
Thanks in advance
Similar to ApplicationReadyEvent you can use ContextClosedEvent:
#Component
public class ContextClosedEventListener {
#EventListener(ContextClosedEvent.class)
public void onContextClosedEvent(ContextClosedEvent contextClosedEvent) {
System.out.println("ContextClosedEvent occurred at millis: " + contextClosedEvent.getTimestamp());
}
}
I've come up with this solution. If you have better one, feel free to share
#Component
public class PortalServiceLifeCycle implements CommandLineRunner {
static final Logger LOGGER = LoggerFactory.getLogger(PortalServiceLifeCycle.class);
#Override
public void run(String... arg0) throws Exception {
LOGGER.info("###START FROM THE LIFECYCLE###");
}
#PreDestroy
public void onExit() {
LOGGER.info("###STOP FROM THE LIFECYCLE###");
}
}
Don't know if you have resolve this problem perfectly. I meet this issue recently, and have got a solution that a little different.
Firstly, my Spring boot Application is a Tomcat embedded one. (The second method of this issue doesn't depends on the web structure. don't mad, my friend.) In this case, it's naturally to get the idea of catch the stop event by register a listener. I do it like this,
#WebListener
public class HelloListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("HelloListener contextInitialized");
}
#Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("HelloListener contextDestroyed");
}
}
and, at the same time, add the annotation #ServletComponentScan on your Application class.
Surely, there are some other ways to register a ServletContextListener, and once you registered it, you can get the stop event in the contextDestroyed function.
BUT, that don't match my issue very much. I must catch the stop event BEFORE the Spring Beans being destroyed. And here comes the second solution.
modify your application main method like the follow:
SpringApplication application = new SpringApplication(DemoApplication.class);
application.addListeners(new MyListener());
application.run(args);
and provide the defination of class MyListener:
class MyListener implements ApplicationListener<ContextClosedEvent>{
#Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
// your code here
}
}
NOTE: the second solution has nothing to do with Tomcat or other web container. The ContextClosedEvent isn't introduced in the Spring document, but I found it in the source, it's very useful i think.
I will be very glad if this can help some one.
It depends what you want to do but one thing you could do is have a bean that implements SmartLifecycle and implement the stop method. Whenever the context is being stopped, you'd get a callback. Note that it does not necessarily means that the process is shutting down. If you want to invoke some code when that happens, I'd register a shutdown hook as Sven wrote in a comment.

Sending a message to a specific client with Spring Websockets

So I've got a Runnable class that should invoke the method notifyUser(String username, String content) once a certain criteria is met. I've been trying to get this to work but it always fails with NullPointerExceptions. This has most likely to do with an Autowiring failure (since the Runnable class is not managed by Spring). Autowiring SimpMessagingTemplate in a Spring-managed context works just fine and the methods do what they're supposed to.
What I want to do is to invoke the method (or a similar method) convertAndSendToUser of the SimpMessagingTemplate, but I cannot autowire it in this context. Everything I've tried failed so far, which is why I assume I got some of the basic concepts wrong.
My Configuration looks like this:
#Configuration
#EnableScheduling
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/test");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/test").withSockJS();
}
}
Update: I've managed to get rid of the NullPointerException by using the following code .. but messages (convertAndSendToUser() as well as convertAndSend()) don't get picked up by the client. The developer console doesn't show any incoming messages.
AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(Application.class);
ctx.getAutowireCapableBeanFactory().autowireBean(myService);
That's true because you do this:
new AnnotationConfigApplicationContext(Application.class);
in that your class, meaning starting a new full appicationContext. But your user is registered in the another context.
It isn't clear why you can't make your component managed by Spring, but there is no other way to use SimpMessagingTemplate, if you can't reach applicationContext.
It would be better to share that your code to investigate from our side and decide how can we help there.
Maybe you can use there WebApplicationContextUtils...

How can I do relational database-based HTTP Session Persistence in Spring 4?

I need to be able to store the HTTP Session in a relational database in order to do stateless load balancing of my front-end users across multiple front-end servers. How can I achieve this in Spring 4?
I see how one can do this with Redis, however there does not appear to be documentation on how to do this with a relational database e.g. Postgres.
With Spring Session (it transparently will override HttpSessions from Java EE) you can just take SessionRepository interface and implement it with your custom ex. JdbcSessionRepository. It is kind of easy to do. When you have your implementation, then just add manually (you don't need #EnableRedisHttpSession annotation) created filter to filter chain, like bellow:
#Configuration
#EnableWebMvcSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//other stuff...
#Autowired
private SessionRepository<ExpiringSession> sessionRepository;
private HttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy(); // or HeaderHttpSessionStrategy
#Bean
public SessionRepository<ExpiringSession> sessionRepository() {
return new JdbcSessionRepository();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
SessionRepositoryFilter<ExpiringSession> sessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository);
sessionRepositoryFilter.setHttpSessionStrategy(httpSessionStrategy);
http
.addFilterBefore(sessionRepositoryFilter, ChannelProcessingFilter.class);
}
}
Here you have how SessionRepository interface looks like. It has only 4 methods to implement. For how to create Session object, you can look in MapSessionRepository and MapSession implementation (or RedisOperationsSessionRepository and RedisSession).
public interface SessionRepository<S extends Session> {
S createSession();
void save(S session);
S getSession(String id);
void delete(String id);
}
Example solution https://github.com/Mati20041/spring-session-jpa-repository
Now spring boot supports by 'spring-session-jdbc'. You can save session into db with less code. For more example you can look at https://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot-jdbc.html#httpsession-jdbc-boot-sample
Just slap Spring Session on it, and you're done. Adding a Redis client bean and annotating a configuration class with #EnableRedisHttpSession is all you need.

Resources