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;
    }
}

 

 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM