先说结论:
项目中定制了spring 的redisTemplate,而这个template没有使用我自定义的Jackson ObjectMapper。所以不生效。
下面是详细过程:
起因是spring boot项目加入了shiro,我打算使用redis去存储shiro的会话,方便以后横向扩展。
参考了网上的实现后,决定通过扩展org.apache.shiro.session.mgt.eis.AbstractSessionDAO来实现。
以下是实现代码:
1 package com.ceiec.baseplatform.config; 2 3 import com.ceiec.baseplatform.redis.StringKeyRedisTemplate; 4 import org.apache.commons.collections.CollectionUtils; 5 import org.apache.shiro.session.Session; 6 import org.apache.shiro.session.UnknownSessionException; 7 import org.apache.shiro.session.mgt.eis.AbstractSessionDAO; 8 import org.slf4j.Logger; 9 import org.slf4j.LoggerFactory; 10 import org.springframework.beans.factory.annotation.Autowired; 11 import org.springframework.stereotype.Component; 12 13 import java.io.Serializable; 14 import java.util.Collection; 15 import java.util.concurrent.TimeUnit; 16 17 @Component 18 public class RedisSessionDAO extends AbstractSessionDAO { 19 private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class); 20 21 @SuppressWarnings("rawtypes") 22 @Autowired 23 private StringKeyRedisTemplate<String, Object> redisTemplate; 24 25 private static final String DEFAULT_SESSION_KEY_PREFIX = "shirosession:"; 26 27 private String keyPrefix = DEFAULT_SESSION_KEY_PREFIX; 28 29 private long expireTime = 120000; 30 31 public RedisSessionDAO() { 32 super(); 33 } 34 35 public RedisSessionDAO(long expireTime) { 36 super(); 37 this.expireTime = expireTime; 38 } 39 40 @Override // 更新session 41 public void update(Session session) throws UnknownSessionException { 42 System.out.println("===============update================"); 43 if (session == null || session.getId() == null) { 44 return; 45 } 46 session.setTimeout(expireTime); 47 String key = getKey(session); 48 redisTemplate.opsForValue().set(key, session, expireTime, TimeUnit.MILLISECONDS); 49 } 50 51 private String getKey(Session session) { 52 return this.keyPrefix + String.valueOf(session.getId()); 53 } 54 private String getSessionIdKey(String sessionId) { 55 return this.keyPrefix + String.valueOf(sessionId); 56 } 57 58 @Override // 删除session 59 public void delete(Session session) { 60 System.out.println("===============delete================"); 61 if (null == session) { 62 return; 63 } 64 redisTemplate.opsForValue().getOperations().delete(getKey(session)); 65 } 66 67 @Override 68 // 获取活跃的session,可以用来统计在线人数,如果要实现这个功能,可以在将session加入redis时指定一个session前缀,统计的时候则使用keys("session-prefix*")的方式来模糊查找redis中所有的session集合 69 public Collection<Session> getActiveSessions() { 70 // System.out.println("==============getActiveSessions================="); 71 // return redisTemplate.keys("*"); 72 return CollectionUtils.EMPTY_COLLECTION; 73 } 74 75 @Override// 加入session 76 protected Serializable doCreate(Session session) { 77 System.out.println("===============doCreate================"); 78 Serializable sessionId = this.generateSessionId(session); 79 this.assignSessionId(session, sessionId); 80 81 redisTemplate.opsForValue().set(getKey(session), session, expireTime, TimeUnit.MILLISECONDS); 82 return sessionId; 83 } 84 85 @Override// 读取session 86 protected Session doReadSession(Serializable sessionId) { 87 System.out.println("==============doReadSession================="); 88 if (sessionId == null) { 89 return null; 90 } 91 return (Session) redisTemplate.opsForValue().get(getSessionIdKey(String.valueOf(sessionId))); 92 } 93 94 public long getExpireTime() { 95 return expireTime; 96 } 97 98 public void setExpireTime(long expireTime) { 99 this.expireTime = expireTime; 100 } 101 102 103 }
然后将该RedisSessionDao注册到sessionManager等,这个不在本文范围内,有兴趣可搜索相关shiro配置。
@Autowired private RedisSessionDAO redisSessionDAO; @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(redisSessionDAO); securityManager.setSessionManager(sessionManager); //设置realm. securityManager.setRealm(myShiroRealm()); return securityManager; }
一切看起来不错。运行,登录,然后,报错了。
现在不太能重现那个错误,大概是,登录时,会把org.apache.shiro.session.mgt.SimpleSession的实例写入到redis。
其中有一个方法如下:
/**
* @since 0.9
*/
public boolean isValid() {
return !isStopped() && !isExpired();
}
序列化时,会序列化一个valid:true的属性到json中。
而在后续读会话时,会反序列化session。然后报错,提示不认识valid属性。
于是在网上查询spring boot如何定制objectMapper去忽略不认识的属性,
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
按照网上说法和文档,如果只要替换ObjectMapper的话,只要按照下面说的这样去定义一个@Bean和@Primary标注的class。
但是在按照上述方法去配置后,发现没有效果。
后边想了很久。。。。突然发现自己的项目中,因为不想用jdk的序列化器,所以自定义了
Spring的RedisTemplate,去使用jackson的序列化器。
而这个template中可能没有使用我自定义的ObjectMapper。
后边发现,果然如此。
然后修改后如下:
package com.ceiec.baseplatform.redis; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.connection.DefaultStringRedisConnection; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.stereotype.Component; import java.io.IOException; /** * desc: redis操作类 * @author: * creat_date: 2018/1/4 * creat_time: 17:18 **/ @Component public class StringKeyRedisTemplate<K, V> extends RedisTemplate<K, V> { /** * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)} * and {@link #afterPropertiesSet()} still need to be called. */ public StringKeyRedisTemplate() { } /** * Constructs a new <code>StringRedisTemplate</code> instance ready to be used. * * @param connectionFactory connection factory for creating new connections */ @Autowired public StringKeyRedisTemplate(RedisConnectionFactory connectionFactory) { RedisSerializer<String> stringSerializer = new StringRedisSerializer(); //设置key序列化器 setKeySerializer(stringSerializer); setHashKeySerializer(stringSerializer); //设置value的序列化器 ObjectMapper mapper = jacksonObjectMapper(); setValueSerializer(new GenericJackson2JsonRedisSerializer(mapper)); setHashValueSerializer(new GenericJackson2JsonRedisSerializer(mapper)); setConnectionFactory(connectionFactory); afterPropertiesSet(); } @Override protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) { return new DefaultStringRedisConnection(connection); } public ObjectMapper jacksonObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); logger.info("construct complete! " + objectMapper); return objectMapper; } }