Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Redis Serialization Configuration and Session Event Handling in Spring Session

Tech Oct 14 14

Spring Session offers extensive customization options for Redis-backed HTTP sessions. This article explores key configuration aspects including serialization strategies, namespace management, repository selection, and event handling mechanisms.

JSON Serialization Configuration

By default, Spring Session employs Java's native object serialization to persist session attribute values. While functional, this approach introduces several complications in distributed environments.

The Serializable Contract

Java serialization requires objects to implement the Serializable interface, converting them into binary byte arrays. Consider this example entity that will participate in session storage:

package com.example.demo.model.entity;

import java.io.Serializable;

public class UserProfile implements Serializable {
    private static final long serialVersionUID = -8837526910472198746L;
    private Integer userId;
    private String displayName;
    private Integer accessLevel;

    public UserProfile() {}

    public UserProfile(Integer userId, String displayName, Integer accessLevel) {
        this.userId = userId;
        this.displayName = displayName;
        this.accessLevel = accessLevel;
    }

    // Getters and setters omitted for brevity

    @Override
    public String toString() {
        return "UserProfile{userId=" + userId + ", displayName='" + displayName + "'}";
    }
}

When a Spring Boot controller returns an object with @ResponseBody, the framework automatically leverages Jackson for JSON serialization. However, Java serialization operates differently—it produces binary output that can only be read back by compatible class definitions.

Limitations of Java Serialization

The default serialization mechanism becomes problematic in these scenarios:

  • Version Incompatibility: When class definitions change between application deployments, deserialization fails with InvalidClassException
  • Cross-Application Conflicts: Multiple applications sharing the same Redis instance may define classes with identical names but different structures
  • Debugging Difficulty: Binary serialized data cannot be inspected directly in Redis CLI tools

Customizing JSON Serialization

Spring Data Redis provides GenericJackson2JsonRedisSerializer, which uses Jackson's ObjectMapper for transparent JSON conversion. This approach aligns with Spring Boot's default JSON handling and produces human-readable Redis values.

package com.example.demo.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisSessionConfiguration {

    /**
     * Replaces default Java serialization with JSON-based serialization
     * for all session attributes stored in Redis.
     */
    @Bean
    public RedisSerializer<Object> sessionAttributeSerializer(ObjectMapper mapper) {
        return new GenericJackson2JsonRedisSerializer(mapper);
    }
}

Namespace Configuration

When multiple applications share a Redis infrastructure, session data isolation becomes essential. Spring Session prefixes all session-related keys with a namespace string, preventing collisions between applications.

Default Namespace Behavior

The framework defaults to spring:session as the key prefix. All session data—including the session map, indexes, and expiration metadata—appears under this prefix in Redis.

Configuration Approaches

Application Properties Method:

spring.session.redis.namespace=production:user-service:sessions

Annotation-Based Method:

@SpringBootApplication
@EnableRedisHttpSession(redisNamespace = "production:user-service:sessions")
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

The @EnableRedisHttpSession annotation imports RedisHttpSessionConfiguration, which handles namespace application automatically.

SessionRepository Implementation Selection

The SessionRepository interface defines CRUD operations for session data. Spring Session provides multiple implementations with varying capabilities and performance characteristics.

RedisSessionRepository

This basic implementation stores session data as simple key-value pairs. Each session maps to a single Redis key containing all attributes as a serialized blob.

Characteristics:

  • Minimal Redis overhead with simple key structure
  • No secondary indexes for attribute-based queries
  • Efficient for straightforward session storage needs
  • Session retrieval requires the exact session identifier

RedisIndexedSessionRepository

This enhanced implementation maintains additional data structures enabling fast attribute-based lookups.

Additional Capabilities:

  • Maintains indexes for principal names, custom attributes, and timestamps
  • Supports querying sessions by user identity or other indexed properties
  • Enables session expiration scheduling and cleanup
  • Facilitates user session enumeration across devices

Query Methods:

@Autowired
private FindByIndexNameSessionRepository<? extends Session> sessionRepository;

public Collection<Session> findAllSessionsForUser(Principal user) {
    return sessionRepository.findByPrincipalName(user.getName()).values();
}

public void terminateSpecificSession(Principal user, String sessionIdToRemove) {
    Map<String, ? extends Session> userSessions = 
        sessionRepository.findByPrincipalName(user.getName());
    if (userSessions.containsKey(sessionIdToRemove)) {
        sessionRepository.deleteById(sessionIdToRemove);
    }
}

Default Injection Behavior

Using @EnableRedisHttpSession automatically registers RedisIndexedSessionRepository as the session repository bean. This occurs through the imported RedisHttpSessionConfiguration class, which declares the indexed repository as the default bean.

Custom Repository Selection

To use the basic repository instead:

@Configuration
public class RedisSessionConfiguration {

    @Bean
    public SessionRepository sessionRepository(RedisTemplate<String, Object> template) {
        return new RedisSessionRepository(template);
    }
}

Session Event Handling

Many scenarios require responding to session lifecycle events—auditing user login activity, clearing cached resources when sessions terminate, or enforcing single-session-per-user policies. The RedisIndexedSessionRepository emits events throughout the session lifecycle.

Java Event Model Fundamentals

The event model involves three participants:

Event Source: The object triggering events, typically passed to event constructors Event Object: Extends EventObject, carrying event-specific information Event Listener: Extends EventListener, containing callback logic for event handling

class NotificationEvent extends EventObject {
    private boolean processed;

    public NotificationEvent(Object source) {
        super(source);
    }

    public void setProcessed(boolean processed) {
        this.processed = processed;
    }

    public boolean isProcessed() {
        return processed;
    }
}

@FunctionalInterface
interface NotificationListener extends EventListener {
    void handleNotification(NotificationEvent event);
}

class NotificationDispatcher {
    private static final List<NotificationListener> listeners = new ArrayList<>();

    public static void registerListener(NotificationListener listener) {
        listeners.add(listener);
    }

    public static void dispatch(NotificationEvent event) {
        listeners.forEach(listener -> listener.handleNotification(event));
    }
}

Spring Application Events

Spring Framework extends the event model with ApplicationEvent, which extends EventObject. The framework provides numerous event subtypes including context lifecycle events and custom application events.

ApplicationListener Interface:

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);
}

Implementation Example:

package com.example.demo.listener;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import java.time.Instant;

@Slf4j
@Component
public class GlobalEventTracker implements ApplicationListener<ApplicationEvent> {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        log.info("Event occurred at {}: {}", 
            Instant.ofEpochMilli(event.getTimestamp()),
            event.getClass().getSimpleName());
    }
}

Spring Session Event Types

Spring Session defines a hierarchy of session-specific events under AbstractSessionEvent:

  • SessionCreatedEvent: Triggered when a new session is established
  • SessionDestroyedEvent: Base type for session termination; subtypes include:
    • SessionExpiredEvent: Session reached its timeout threshold
    • SessionDeletedEvent: Session explicitly invalidated or removed

Session Event Listener Implementation

Using @EventListener annotation for concise listener registration:

package com.example.demo.listener;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.session.events.*;

@Slf4j
@Configuration
public class SessionLifecycleHandler {

    @EventListener(SessionCreatedEvent.class)
    public void onSessionCreated(SessionCreatedEvent event) {
        log.info("Session created: {}", event.getSessionId());
    }

    @EventListener(SessionDeletedEvent.class)
    public void onSessionDeleted(SessionDeletedEvent event) {
        log.info("Session deleted: {}", event.getSessionId());
    }

    @EventListener(SessionExpiredEvent.class)
    public void onSessionExpired(SessionExpiredEvent event) {
        log.info("Session expired: {}", event.getSessionId());
    }

    @EventListener(SessionDestroyedEvent.class)
    public void onSessionDestroyed(SessionDestroyedEvent event) {
        log.info("Session destroyed: {} (type: {})", 
            event.getSessionId(), 
            event.getClass().getSimpleName());
    }
}

Practical Demonstration

The following controller creates and destroys sessions to demonstrate event emission:

package com.example.demo.controller;

import com.example.demo.model.entity.UserProfile;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;

@Slf4j
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserSessionController {

    private final RedisIndexedSessionRepository sessionRepository;

    @PostMapping("/create")
    public UserProfile createUserSession(HttpSession session, 
                                         @RequestParam("id") Integer userId) {
        UserProfile profile = new UserProfile(userId, "JohnDoe", 2);
        session.setAttribute("userProfile", profile);
        log.info("Created user profile in session: {}", profile);
        return profile;
    }

    @DeleteMapping("/session")
    public void invalidateSession(HttpSession session) {
        String sessionId = session.getId();
        session.removeAttribute("userProfile");
        log.info("Removed user profile from session");
        sessionRepository.deleteById(sessionId);
    }
}

Invoking these endpoints triggers corresponding session events processed by the registered listeners.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.