Solution to invalid spring session configuration serialization for redis

The reason for the problem: after using springsession to automatically add the setting attribute to redis, it will appear hexadecimal garbled code in front of the value. Even if redis serialization configuration is configured, it is useless, because spring session creates a redis itself, and the default is new JdkSerializationRedisSerializer();, Therefore, to solve this problem, we must start from the root!

The best way to solve the problem is to understand its principle, so let's start with spring session and solve the problem step by step
If you want to use session, you must get it in the first step

// request is of type HttpServletRequest, which is obtained by passing parameters to the method
HttpSession session = request.getSession();

Let's go deeper

We can know HttpServletRequest If it is an interface, there must be an implementation class
 So in so many implementation classes, which one is that?
Since you want to use springsession,So the implementation class must be related to this. Why can we use this class instead of other classes? Due to Taoism, I can't understand its principle now, but I found it on the implementation class order Annotation, the default value is 0+50 ,I wonder if this is the reason!
Let's take a look at this class


@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {


	private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {

		@Override
		public HttpSessionWrapper getSession(boolean create) {  // create = true
			// Get current session
			HttpSessionWrapper currentSession = getCurrentSession();
			if (currentSession != null) {
				return currentSession;
			}
			// Get a session
			S requestedSession = getRequestedSession();
			//Not empty, set something for it
			if (requestedSession != null) {
				if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
					requestedSession.setLastAccessedTime(Instant.now());
					this.requestedSessionIdValid = true;
					currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
					currentSession.markNotNew();
					setCurrentSession(currentSession);
					return currentSession;
				}
			}
			else {
				// This is an invalid session id. No need to ask again if
				// request.getSession is invoked for the duration of this request
				if (SESSION_LOGGER.isDebugEnabled()) {
					SESSION_LOGGER.debug(
							"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
				}
				setAttribute(INVALID_SESSION_ID_ATTR, "true");
			}
			if (!create) {
				return null;
			}
			// Is the session log open
			if (SESSION_LOGGER.isDebugEnabled()) {
				SESSION_LOGGER.debug(
						"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
								+ SESSION_LOGGER_NAME,
						new RuntimeException("For debugging purposes only (not an error)"));
			}
			
			// When we first logged in, we must have run this step
			// Get session
			S session = SessionRepositoryFilter.this.sessionRepository.createSession();
			
			// Set session timeout
			session.setLastAccessedTime(Instant.now());
			// The current session is actually created by encapsulating it with the Servlet context using HttpSessionWrapper
			currentSession = new HttpSessionWrapper(session, getServletContext());
			setCurrentSession(currentSession);
			return currentSession;
		}

		@Override
		public HttpSessionWrapper getSession() {
			return getSession(true);
		}

		}

	}
}

After talking for a long time, it has nothing to do with the title!
Don't worry, everyone, we'll be there soon!!!
From this, we can know that the session is composed of

SessionRepositoryFilter.this.sessionRepository.createSession(); 

If it is created, some parameters must be configured before creation. Is there a method to configure serialization for configuration parameters.

public class RedisIndexedSessionRepository
		implements FindByIndexNameSessionRepository<RedisIndexedSessionRepository.RedisSession>, MessageListener {
		// Omitted code above
		private RedisSerializer<Object> defaultSerializer = new JdkSerializationRedisSerializer();
		// The following code is omitted
}

I'm sure you can see it at a glance. This shows that the default serialization used during session configuration is JdkSerializationRedisSerializer. This can also explain why when we use session to add a String, there is a String of hexadecimal garbled code in front of the String in redis (please allow me to call it this way).
Now that we have found this configuration, can we modify it???
Let's go back to the origin of spring. If you want to fill in the attributes of the class, you can either use the set method of the elements that are used in the final principle (excluding the constructor). So let's look for the following configuration classes

I only got the important code. If you want to know more about it, please find it yourself

@Configuration(proxyBeanMethods = false)
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
		implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
		
		// dafault serialization 
		private RedisSerializer<Object> defaultRedisSerializer;
		
	// Look at this familiar handle, which is the RedisIndexedSessionRepository class we mentioned above
	// Only bean annotation is used here
	@Bean
	public RedisIndexedSessionRepository sessionRepository() {
		RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
		RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate);
		sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
		if (this.indexResolver != null) {
			sessionRepository.setIndexResolver(this.indexResolver);
		}

		// Come on, come on
		// Climax on
		// That's the point
		// Let's see. If the default serialization is not empty, set it to use
		if (this.defaultRedisSerializer != null) {
			sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
		}
		sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
		if (StringUtils.hasText(this.redisNamespace)) {
			sessionRepository.setRedisKeyNamespace(this.redisNamespace);
		}
		sessionRepository.setFlushMode(this.flushMode);
		sessionRepository.setSaveMode(this.saveMode);
		int database = resolveDatabase();
		sessionRepository.setDatabase(database);
		this.sessionRepositoryCustomizers
				.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
		return sessionRepository;
	}


	// Seeing this, you will be able to guess what the solution is!
	@Autowired(required = false)
	@Qualifier("springSessionDefaultRedisSerializer")
	public void setDefaultRedisSerializer(RedisSerializer<Object> defaultRedisSerializer) {
		this.defaultRedisSerializer = defaultRedisSerializer;
	}
}

Yes, just create a configuration class, set the serialization method, and then let spring inject it automatically
Just do it

@Configuration
public class RedisConfig {

	// Here is to set the redis serialization method, which can be ignored
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }

	// This is the way to set session serialization
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
		// Because I use fastjason
		// If you want to use something else, please set it yourself, that is, return the serialization class when returning
        return new FastJsonRedisSerializer(Object.class);
    }
}

Then, the value setting is in. Let me talk about how I get the value

		// Using JSON, String is converted to class because of stringredistemplate Get returns byte [], so I forcibly convert it into a String!!!
        UserVO userVO1 = JSON.parseObject((String) stringRedisTemplate.opsForHash().get(key, "sessionAttr:UserVo"), UserVO.class);
        System.out.println(userVO1.getUsername());

Here, the problem is solved and the knowledge is learned. Then point a praise and collect it!!!

Keywords: Redis Spring Spring Boot

Added by mobiuz on Wed, 09 Mar 2022 16:25:54 +0200