• spring boot定制Jackson ObjectMapper,为什么不生效


    先说结论:

    项目中定制了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 }
    View Code

    然后将该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;
        }
    }
    
    
  • 相关阅读:
    yum命令报错 yum update File "/usr/bin/yum", line 30 except KeyboardInterrupt, e: --CentOS7.5
    CentOS7.5下安装Python3.7 --python3
    CentOS7.5安装Python3.7报错:configure: error: no acceptable C compiler found in $PATH --Python3
    ubuntu18.04.2LTS下安装和配置MySql数据库 --ubuntu
    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! --主机密钥验证失败
    iphone使用linux命令apt-get也没有问题
    百度网盘下载器 PanDownload v2.0
    iPhone越狱cydia源大全
    php判断网站收录情况
    linux下C语言三种get输入方式
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/8602382.html
Copyright © 2020-2023  润新知